swarph-cli 0.72.6__tar.gz → 0.72.8__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.72.6/src/swarph_cli.egg-info → swarph_cli-0.72.8}/PKG-INFO +1 -1
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/pyproject.toml +1 -1
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/mesh.py +35 -17
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/watchdog.py +113 -9
- {swarph_cli-0.72.6 → swarph_cli-0.72.8/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli.egg-info/SOURCES.txt +2 -0
- swarph_cli-0.72.8/tests/test_1052_opencode_deaf.py +105 -0
- swarph_cli-0.72.8/tests/test_1059_watchdog_unread_env.py +179 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog.py +2 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/LICENSE +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/README.md +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/setup.cfg +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/codex_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/rowclear_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/rowclear.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1006_board_publisher.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1010_strand_only.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1013_spawn_channel.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1017_muse_tui.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_1050_surrogate_wake.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_432_rowclear.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_484_codex_queue_sink.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_961_loud_stop.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_codex_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_board_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_group_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_import_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_init_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_main.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_security.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_service_app.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_service_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_version_command.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.72.6 → swarph_cli-0.72.8}/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.72.
|
|
3
|
+
Version: 0.72.8
|
|
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.72.
|
|
7
|
+
version = "0.72.8"
|
|
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" }
|
|
@@ -1515,7 +1515,14 @@ class TmuxSink(Sink):
|
|
|
1515
1515
|
# Opencode keeps the finished ▣ header on screen, so a deferral
|
|
1516
1516
|
# during the turn must not clear the flag: the next idle poll
|
|
1517
1517
|
# still owes a wake. Cursor's #620 clear stays.
|
|
1518
|
-
if _opencode_in_progress(self.target)
|
|
1518
|
+
if _opencode_in_progress(self.target):
|
|
1519
|
+
# Card #1052: name the real deferral cause — without this
|
|
1520
|
+
# the log says "composer holds human text" on a CLEAR
|
|
1521
|
+
# composer while a finished header misreads as live.
|
|
1522
|
+
self.deferred_reason = "opencode turn in progress"
|
|
1523
|
+
return None
|
|
1524
|
+
if _grok_in_progress(self.target):
|
|
1525
|
+
self.deferred_reason = "grok turn in progress"
|
|
1519
1526
|
return None
|
|
1520
1527
|
led["wake_outstanding"] = False
|
|
1521
1528
|
if led.get("wake_outstanding"):
|
|
@@ -2327,10 +2334,15 @@ _MUSE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
|
|
|
2327
2334
|
# A finished turn keeps its ▣ header and adds a duration.
|
|
2328
2335
|
# Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
|
|
2329
2336
|
# 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
|
|
2330
|
-
# 07:10Z).
|
|
2331
|
-
#
|
|
2337
|
+
# 07:10Z). Long turns read h/m-only: `· 4h 59m`, `· 12m`, `· 1h` (opencode,
|
|
2338
|
+
# card #1052 — the old regex needed trailing seconds and read these as
|
|
2339
|
+
# live for 32h). On a wide pane the same row continues into the sidebar,
|
|
2340
|
+
# so the duration is not the end of the line. An in-progress header has
|
|
2341
|
+
# no duration.
|
|
2332
2342
|
_OPENCODE_DONE = re.compile(
|
|
2333
|
-
r"·\s*(?:\d+h\s
|
|
2343
|
+
r"·\s*(?:(?:\d+h\s+)?(?:\d+m\s+)?\d+(?:\.\d+)?s"
|
|
2344
|
+
r"|\d+h(?:\s+\d+m)?"
|
|
2345
|
+
r"|\d+m)\b"
|
|
2334
2346
|
)
|
|
2335
2347
|
# Submit-verify bounds (#533): the settle pause lets the -l literal LAND in
|
|
2336
2348
|
# the composer before Enter can submit it (the blind gesture raced this), and
|
|
@@ -2504,31 +2516,37 @@ def _is_opencode_pane(lines: list[str]) -> bool:
|
|
|
2504
2516
|
return bool(lines) and _bottom_tui(lines) == "opencode"
|
|
2505
2517
|
|
|
2506
2518
|
|
|
2519
|
+
def _last_box_header(lines: list[str]) -> Optional[str]:
|
|
2520
|
+
"""The LAST ▣ header on screen. Scrollback keeps old headers (card
|
|
2521
|
+
#1052: a finished `4h 59m` header above the live tail); only the
|
|
2522
|
+
bottom-most one judges the turn."""
|
|
2523
|
+
for ln in reversed(lines):
|
|
2524
|
+
if ln.strip().startswith("▣"):
|
|
2525
|
+
return ln.strip()
|
|
2526
|
+
return None
|
|
2527
|
+
|
|
2528
|
+
|
|
2507
2529
|
def _opencode_running(lines: list[str]) -> bool:
|
|
2508
2530
|
"""True only while a turn is in progress.
|
|
2509
2531
|
|
|
2510
|
-
The footer shows ``esc interrupt``, and the ▣ header has no
|
|
2511
|
-
duration. A finished turn keeps the ▣ header and adds
|
|
2512
|
-
|
|
2532
|
+
The footer shows ``esc interrupt``, and the last ▣ header has no
|
|
2533
|
+
trailing duration. A finished turn keeps the ▣ header and adds a
|
|
2534
|
+
duration (``· <n>s`` or h/m-only like ``· 4h 59m``), with no
|
|
2535
|
+
interrupt hint. That pane is idle. The permission dialog is
|
|
2513
2536
|
not a turn; it has its own branch.
|
|
2514
2537
|
"""
|
|
2515
2538
|
if any("esc interrupt" in ln for ln in lines):
|
|
2516
2539
|
return True
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
if s.startswith("▣") and not _OPENCODE_DONE.search(s):
|
|
2520
|
-
return True
|
|
2521
|
-
return False
|
|
2540
|
+
last = _last_box_header(lines)
|
|
2541
|
+
return last is not None and not _OPENCODE_DONE.search(last)
|
|
2522
2542
|
|
|
2523
2543
|
|
|
2524
2544
|
def _opencode_turn_finished(lines: list[str]) -> bool:
|
|
2525
|
-
"""The ▣ header carried a duration and the interrupt hint is gone."""
|
|
2545
|
+
"""The last ▣ header carried a duration and the interrupt hint is gone."""
|
|
2526
2546
|
if _opencode_running(lines):
|
|
2527
2547
|
return False
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
for ln in lines
|
|
2531
|
-
)
|
|
2548
|
+
last = _last_box_header(lines)
|
|
2549
|
+
return last is not None and bool(_OPENCODE_DONE.search(last))
|
|
2532
2550
|
|
|
2533
2551
|
|
|
2534
2552
|
def _opencode_in_progress(target: str) -> bool:
|
|
@@ -468,6 +468,14 @@ def _gateway_configured(gateway: str) -> bool:
|
|
|
468
468
|
"""
|
|
469
469
|
return bool((gateway or "").strip())
|
|
470
470
|
|
|
471
|
+
def _resolve_unread_token(token: Optional[str]) -> Optional[str]:
|
|
472
|
+
"""Token precedence for the unread check: explicit arg (cron setups
|
|
473
|
+
pass env straight through), else the watchdog identity's token file
|
|
474
|
+
(card #1059 — template units have no env). The VALUE is never logged.
|
|
475
|
+
"""
|
|
476
|
+
return token or _read_service_token()
|
|
477
|
+
|
|
478
|
+
|
|
471
479
|
def _gateway_unread_count(gateway: str, peer: str, token: Optional[str]) -> Optional[int]:
|
|
472
480
|
"""Query gateway for unread DM count addressed to peer.
|
|
473
481
|
|
|
@@ -483,15 +491,15 @@ def _gateway_unread_count(gateway: str, peer: str, token: Optional[str]) -> Opti
|
|
|
483
491
|
the code it was documenting — found 2026-08-12 when the contradiction
|
|
484
492
|
caused a real reader to conclude a gateway/token outage makes A1 MORE
|
|
485
493
|
eager, when it makes A1 permanently inert for that cell instead.
|
|
494
|
+
|
|
495
|
+
Prefers the count-only endpoint (card #1059: no bodies cross the wire);
|
|
496
|
+
a 404 (gateway predates the endpoint) falls back to the list query so a
|
|
497
|
+
mixed fleet never goes blind from the client side alone.
|
|
486
498
|
"""
|
|
487
499
|
if not _gateway_configured(gateway):
|
|
488
500
|
return None
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
req = urllib.request.Request(url)
|
|
492
|
-
if token:
|
|
493
|
-
req.add_header("Authorization", f"Bearer {token}")
|
|
494
|
-
else:
|
|
501
|
+
token = _resolve_unread_token(token)
|
|
502
|
+
if not token:
|
|
495
503
|
# A missing token and a network outage both surface as None to the
|
|
496
504
|
# caller (correctly — the decision matrix treats them the same way),
|
|
497
505
|
# but they are different FACTS an operator would want to know apart.
|
|
@@ -503,10 +511,50 @@ def _gateway_unread_count(gateway: str, peer: str, token: Optional[str]) -> Opti
|
|
|
503
511
|
f"unread-count check may 401 if the gateway requires auth; "
|
|
504
512
|
f"A1 stays inert until one is set",
|
|
505
513
|
file=sys.stderr)
|
|
514
|
+
count = _gateway_unread_via_count(gateway, peer, token)
|
|
515
|
+
if count is not None:
|
|
516
|
+
return count
|
|
517
|
+
return _gateway_unread_via_list(gateway, peer, token)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _gateway_request(gateway: str, path: str, query: dict,
|
|
521
|
+
token: Optional[str]):
|
|
522
|
+
"""GET path+query with an optional Bearer token. Returns the decoded
|
|
523
|
+
JSON body, or (None, status) on transport failure — the status lets
|
|
524
|
+
callers tell a missing route (404: fall back) from a dead end."""
|
|
525
|
+
url = f"{gateway.rstrip('/')}{path}?{urllib.parse.urlencode(query)}"
|
|
526
|
+
req = urllib.request.Request(url)
|
|
527
|
+
if token:
|
|
528
|
+
req.add_header("Authorization", f"Bearer {token}")
|
|
506
529
|
try:
|
|
507
530
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
508
|
-
|
|
509
|
-
except
|
|
531
|
+
return json.loads(resp.read().decode("utf-8")), resp.status
|
|
532
|
+
except urllib.error.HTTPError as exc:
|
|
533
|
+
return None, exc.code
|
|
534
|
+
except (urllib.error.URLError, OSError, json.JSONDecodeError):
|
|
535
|
+
return None, None
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _gateway_unread_via_count(gateway: str, peer: str,
|
|
539
|
+
token: Optional[str]) -> Optional[int]:
|
|
540
|
+
"""Count-only read: {"to_node", "n"}. No bodies cross the wire, so a
|
|
541
|
+
least-privilege watchdog identity suffices (card #1059)."""
|
|
542
|
+
data, _status = _gateway_request(
|
|
543
|
+
gateway, "/messages/unread-count", {"to_node": peer}, token)
|
|
544
|
+
if isinstance(data, dict) and isinstance(data.get("n"), int):
|
|
545
|
+
return data["n"]
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _gateway_unread_via_list(gateway: str, peer: str,
|
|
550
|
+
token: Optional[str]) -> Optional[int]:
|
|
551
|
+
"""Pre-count-endpoint fallback: parse the list query. Kept so a mixed
|
|
552
|
+
fleet (new client, old gateway) degrades to body reads, never to
|
|
553
|
+
blindness — the 404 is the only trigger, never a 403/401."""
|
|
554
|
+
data, status = _gateway_request(
|
|
555
|
+
gateway, "/messages",
|
|
556
|
+
{"to_node": peer, "unread_only": "true", "limit": 1}, token)
|
|
557
|
+
if status == 404:
|
|
510
558
|
return None
|
|
511
559
|
# Gateway shape varies — handle both list and {"messages": [...]} forms
|
|
512
560
|
if isinstance(data, list):
|
|
@@ -1874,6 +1922,37 @@ def _execstart_flags(args: argparse.Namespace) -> str:
|
|
|
1874
1922
|
return (" " + " ".join(parts)) if parts else ""
|
|
1875
1923
|
|
|
1876
1924
|
|
|
1925
|
+
def _watchdog_service_token_path() -> Path:
|
|
1926
|
+
"""Where the watchdog identity's token lives. Overridable for tests
|
|
1927
|
+
and unusual homes; never a unit file, never argv (card #1059)."""
|
|
1928
|
+
return Path(os.environ.get(
|
|
1929
|
+
"SWARPH_WATCHDOG_TOKEN_FILE",
|
|
1930
|
+
"~/.config/swarph/watchdog-service.token")).expanduser()
|
|
1931
|
+
|
|
1932
|
+
|
|
1933
|
+
def _read_service_token() -> Optional[str]:
|
|
1934
|
+
"""The watchdog identity's token from its file, or None (absent /
|
|
1935
|
+
unreadable / blank). The VALUE never hits logs — callers log only
|
|
1936
|
+
whether one was found."""
|
|
1937
|
+
try:
|
|
1938
|
+
tok = _watchdog_service_token_path().read_text(
|
|
1939
|
+
encoding="utf-8").strip()
|
|
1940
|
+
except OSError:
|
|
1941
|
+
return None
|
|
1942
|
+
return tok or None
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
def _write_service_token_file(path: Path, token: str) -> None:
|
|
1946
|
+
"""Write the token file 0600 (parents created). Refuses multiline
|
|
1947
|
+
values — an env file line cannot hold them, and silently writing
|
|
1948
|
+
half a credential is worse than failing."""
|
|
1949
|
+
if "\n" in token:
|
|
1950
|
+
raise ValueError("token contains a newline")
|
|
1951
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1952
|
+
path.write_text(token.strip() + "\n", encoding="utf-8")
|
|
1953
|
+
os.chmod(path, 0o600)
|
|
1954
|
+
|
|
1955
|
+
|
|
1877
1956
|
def run_install_service(args: argparse.Namespace) -> int:
|
|
1878
1957
|
"""Install systemd timer + service for periodic watchdog --check.
|
|
1879
1958
|
|
|
@@ -1950,6 +2029,25 @@ def run_install_service(args: argparse.Namespace) -> int:
|
|
|
1950
2029
|
1,
|
|
1951
2030
|
)
|
|
1952
2031
|
|
|
2032
|
+
# Card #1059/ruling_1332: template units run without the installer's
|
|
2033
|
+
# env, so the unread check reads None and A1 stays inert (exit 7,
|
|
2034
|
+
# opencode + cursor-lin measured live). The watchdog identity's
|
|
2035
|
+
# token lives in a 0600 token FILE (path via SWARPH_WATCHDOG_TOKEN_FILE,
|
|
2036
|
+
# default ~/.config/swarph/watchdog-service.token) — never in a unit,
|
|
2037
|
+
# never in argv, never in the EnvironmentFile default. The installer
|
|
2038
|
+
# writes it from its own env; rotation is re-running the install.
|
|
2039
|
+
install_token = os.environ.get("MESH_GATEWAY_TOKEN")
|
|
2040
|
+
token_path = _watchdog_service_token_path()
|
|
2041
|
+
if install_token is not None and "\n" in install_token:
|
|
2042
|
+
print("ERROR: MESH_GATEWAY_TOKEN contains a newline — refusing "
|
|
2043
|
+
"to write the token file.", file=sys.stderr)
|
|
2044
|
+
return 4
|
|
2045
|
+
if not install_token:
|
|
2046
|
+
print("WARNING: MESH_GATEWAY_TOKEN not in installer env — "
|
|
2047
|
+
"template runs will exit 7 (noop_unread_unknown) until the "
|
|
2048
|
+
f"token file exists at {token_path}; re-run --install-service "
|
|
2049
|
+
"with it set.", file=sys.stderr)
|
|
2050
|
+
|
|
1953
2051
|
targets = [
|
|
1954
2052
|
(_SYSTEMD_UNIT_DIR / service_name, service_content),
|
|
1955
2053
|
(_SYSTEMD_UNIT_DIR / timer_name, timer_content),
|
|
@@ -1970,6 +2068,9 @@ def run_install_service(args: argparse.Namespace) -> int:
|
|
|
1970
2068
|
for path, content in targets:
|
|
1971
2069
|
print(f"\n# would write {path}:", file=sys.stderr)
|
|
1972
2070
|
print(content, file=sys.stderr)
|
|
2071
|
+
if install_token:
|
|
2072
|
+
print(f"\n# would write {token_path} (0600, value not shown):",
|
|
2073
|
+
file=sys.stderr)
|
|
1973
2074
|
if legacy_note:
|
|
1974
2075
|
print(f"\n{legacy_note}", file=sys.stderr)
|
|
1975
2076
|
print(
|
|
@@ -1992,7 +2093,10 @@ def run_install_service(args: argparse.Namespace) -> int:
|
|
|
1992
2093
|
for path, content in targets:
|
|
1993
2094
|
path.write_text(content, encoding="utf-8")
|
|
1994
2095
|
print(f"wrote {path}", file=sys.stderr)
|
|
1995
|
-
|
|
2096
|
+
if install_token:
|
|
2097
|
+
_write_service_token_file(token_path, install_token)
|
|
2098
|
+
print(f"wrote {token_path} (0600)", file=sys.stderr)
|
|
2099
|
+
except (OSError, PermissionError, ValueError) as exc:
|
|
1996
2100
|
print(f"ERROR: failed to write unit files: {exc}", file=sys.stderr)
|
|
1997
2101
|
return 5
|
|
1998
2102
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.72.
|
|
3
|
+
Version: 0.72.8
|
|
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
|
|
@@ -194,6 +194,8 @@ tests/test_1010_strand_only.py
|
|
|
194
194
|
tests/test_1013_spawn_channel.py
|
|
195
195
|
tests/test_1017_muse_tui.py
|
|
196
196
|
tests/test_1050_surrogate_wake.py
|
|
197
|
+
tests/test_1052_opencode_deaf.py
|
|
198
|
+
tests/test_1059_watchdog_unread_env.py
|
|
197
199
|
tests/test_124_register_merge.py
|
|
198
200
|
tests/test_126_pending_from_complete.py
|
|
199
201
|
tests/test_132_ensure_monitor_state_dir.py
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""#1052 — opencode wake DEAF 32h: _opencode_running read a FINISHED
|
|
2
|
+
'▣ ... · 4h 59m' header as a live turn (the done-regex needs trailing
|
|
3
|
+
seconds), so the monitor deferred forever, logging 'composer holds human
|
|
4
|
+
text' on a CLEAR composer.
|
|
5
|
+
|
|
6
|
+
Seams: the pure helpers on the two real captures (lab-ovh's 01:31Z and
|
|
7
|
+
17:18Z fixtures), plus the TmuxSink deferral rigged at the deliver()
|
|
8
|
+
boundary exactly like test_1010_strand_only's. Items: (1) the done-regex
|
|
9
|
+
accepts h/m-only durations; (2) only the LAST ▣ header judges; (3) the
|
|
10
|
+
opencode-in-progress deferral names its real reason.
|
|
11
|
+
|
|
12
|
+
The committed fixtures are the real captures with one customer
|
|
13
|
+
identifier redacted (a 6-letter tenant prefix -> acme, 7 spots each);
|
|
14
|
+
headers, composer state and control lines are byte-identical to the
|
|
15
|
+
originals.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import pathlib
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
|
|
23
|
+
import swarph_cli.commands.mesh as mesh
|
|
24
|
+
|
|
25
|
+
FIX = pathlib.Path(__file__).resolve().parent / "fixtures"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _lines(name: str) -> list[str]:
|
|
29
|
+
return (FIX / name).read_text(encoding="utf-8").splitlines()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
CAPTURES = ("opencode-deaf-0131Z.txt", "opencode-deaf-1718Z.txt")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize("name", CAPTURES)
|
|
36
|
+
def test_finished_captures_are_not_running(name: str):
|
|
37
|
+
"""Both real captures: the old scrollback '4h 59m' header is finished
|
|
38
|
+
and the newest '16.5s' header is finished — no live turn anywhere."""
|
|
39
|
+
lines = _lines(name)
|
|
40
|
+
assert any("4h 59m" in ln for ln in lines), "capture must hold the old header"
|
|
41
|
+
assert mesh._opencode_running(lines) is False
|
|
42
|
+
assert mesh._opencode_turn_finished(lines) is True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_bare_header_without_duration_still_running():
|
|
46
|
+
"""A header with NO duration is a live turn (or a mid-render one) —
|
|
47
|
+
the last-header rule must not call it finished."""
|
|
48
|
+
assert mesh._opencode_running(["▣ Build · DeepSeek V4 Pro (New)"]) is True
|
|
49
|
+
assert mesh._opencode_turn_finished(
|
|
50
|
+
["▣ Build · DeepSeek V4 Pro (New)"]) is False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_scrollback_done_does_not_mask_live_tail():
|
|
54
|
+
"""Only the LAST ▣ header judges: a finished scrollback header plus a
|
|
55
|
+
live (duration-less) tail header is still a running turn."""
|
|
56
|
+
lines = ["▣ Build · x · 4h 59m", "some output",
|
|
57
|
+
"▣ Build · x (no duration yet)"]
|
|
58
|
+
assert mesh._opencode_running(lines) is True
|
|
59
|
+
assert mesh._opencode_turn_finished(lines) is False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize("name", CAPTURES)
|
|
63
|
+
def test_h_m_only_durations_count_as_done(name: str):
|
|
64
|
+
"""Item 1 directly: '4h 59m', '12m', '1h' all match the done-regex."""
|
|
65
|
+
assert mesh._OPENCODE_DONE.search("▣ Build · x · 4h 59m")
|
|
66
|
+
assert mesh._OPENCODE_DONE.search("▣ Build · x · 12m")
|
|
67
|
+
assert mesh._OPENCODE_DONE.search("▣ Build · x · 1h")
|
|
68
|
+
assert not mesh._OPENCODE_DONE.search("▣ Build · x (New)")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class _StubState:
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.gateway = "http://gw:8788"
|
|
74
|
+
self.self_name = "opencode"
|
|
75
|
+
self.token = "tok"
|
|
76
|
+
self._ledgers: dict = {}
|
|
77
|
+
|
|
78
|
+
def ledger(self, name: str) -> dict:
|
|
79
|
+
return self._ledgers.setdefault(
|
|
80
|
+
name, {"last_delivered_id": 0, "last_delivery_at": 0.0,
|
|
81
|
+
"consecutive_failures": 0})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_opencode_deferral_names_its_real_reason(monkeypatch):
|
|
85
|
+
"""Item 3: the opencode-in-progress deferral sets deferred_reason, so
|
|
86
|
+
the monitor's DEFERRED line stops saying 'composer holds human text'.
|
|
87
|
+
Rigged at deliver(): wake outstanding, agent running, and the real
|
|
88
|
+
17:18Z capture with a LIVE tail (bare header + interrupt hint — the
|
|
89
|
+
documented mid-turn screen) so the real reader judges in-progress."""
|
|
90
|
+
lines = _lines("opencode-deaf-1718Z.txt") + [
|
|
91
|
+
" ▣ Build · DeepSeek V4.1 Flash",
|
|
92
|
+
" esc interrupt",
|
|
93
|
+
]
|
|
94
|
+
assert mesh._opencode_running(lines) is True, "rig must read live"
|
|
95
|
+
monkeypatch.setattr(mesh, "_capture_pane_lines", lambda t: lines)
|
|
96
|
+
monkeypatch.setattr(mesh, "_agent_running", lambda t: True)
|
|
97
|
+
state = _StubState()
|
|
98
|
+
led = state.ledger("tmux:opencode")
|
|
99
|
+
led["wake_outstanding"] = True
|
|
100
|
+
sink = mesh.TmuxSink("opencode")
|
|
101
|
+
assert sink.deliver(state, [{"id": 7}], 7) is None
|
|
102
|
+
assert "human text" not in (sink.deferred_reason or ""), \
|
|
103
|
+
sink.deferred_reason
|
|
104
|
+
assert "opencode" in (sink.deferred_reason or "").lower(), \
|
|
105
|
+
sink.deferred_reason
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""#1059 — template watchdog instances exit 7 (noop_unread_unknown):
|
|
2
|
+
systemd units run without MESH_GATEWAY_TOKEN, so unread reads None and
|
|
3
|
+
A1 stays permanently inert (opencode + cursor-lin, measured live).
|
|
4
|
+
|
|
5
|
+
Fix: --install-service writes the installer's MESH_GATEWAY_TOKEN into
|
|
6
|
+
the per-cell EnvironmentFile default (0600), never into the unit. The
|
|
7
|
+
check itself is unchanged — it already reads the token from env.
|
|
8
|
+
|
|
9
|
+
Seams: the dry-run preview (all three files print to stderr) for the
|
|
10
|
+
install shape, and run_check with a stubbed urlopen for the replay
|
|
11
|
+
(token flows into the Authorization header, unread is numeric, the
|
|
12
|
+
stale-cursor + live-session + idle-pane shape yields the A1 path).
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
from swarph_cli.commands import watchdog as wd
|
|
22
|
+
from swarph_cli.commands.watchdog import run_watchdog
|
|
23
|
+
|
|
24
|
+
TOKEN = "tok-1059-test"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _dry_sections(capsys, monkeypatch, *, token=True):
|
|
28
|
+
if token:
|
|
29
|
+
monkeypatch.setenv("MESH_GATEWAY_TOKEN", TOKEN)
|
|
30
|
+
else:
|
|
31
|
+
monkeypatch.delenv("MESH_GATEWAY_TOKEN", raising=False)
|
|
32
|
+
rc = run_watchdog(argv=["--install-service", "--cell", "opencode",
|
|
33
|
+
"--dry-run"])
|
|
34
|
+
assert rc == 0
|
|
35
|
+
err = capsys.readouterr().err
|
|
36
|
+
sections = {}
|
|
37
|
+
current = None
|
|
38
|
+
for line in err.splitlines():
|
|
39
|
+
if line.startswith("# would write "):
|
|
40
|
+
# Windows stringifies the target path with backslashes;
|
|
41
|
+
# normalize so the section match below holds everywhere.
|
|
42
|
+
current = line.replace("\\", "/")
|
|
43
|
+
sections[current] = []
|
|
44
|
+
elif current is not None:
|
|
45
|
+
sections[current].append(line)
|
|
46
|
+
return sections, err
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_install_writes_token_file_not_token_value(monkeypatch, capsys):
|
|
50
|
+
"""With a token in env, the preview names the 0600 token file but
|
|
51
|
+
never shows the value — and no file section carries a token line."""
|
|
52
|
+
sections, err = _dry_sections(capsys, monkeypatch, token=True)
|
|
53
|
+
assert TOKEN not in err, "token value leaked into preview"
|
|
54
|
+
body = "\n".join(l for lines in sections.values() for l in lines)
|
|
55
|
+
assert "MESH_GATEWAY_TOKEN=" not in body
|
|
56
|
+
assert "0600" in err
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_install_never_puts_token_in_unit(monkeypatch, capsys):
|
|
60
|
+
"""No token in ExecStart or anywhere in the .service content —
|
|
61
|
+
units are world-readable."""
|
|
62
|
+
sections, _ = _dry_sections(capsys, monkeypatch, token=True)
|
|
63
|
+
for key, lines in sections.items():
|
|
64
|
+
if key.endswith(".service:"):
|
|
65
|
+
assert TOKEN not in "\n".join(lines), key
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_token_file_round_trip_0600_and_newline_refused(monkeypatch, tmp_path):
|
|
69
|
+
"""The helper writes the value with mode 600 and refuses multiline
|
|
70
|
+
values instead of writing half a credential."""
|
|
71
|
+
import stat
|
|
72
|
+
import sys
|
|
73
|
+
monkeypatch.setenv("SWARPH_WATCHDOG_TOKEN_FILE",
|
|
74
|
+
str(tmp_path / "sub" / "watchdog-service.token"))
|
|
75
|
+
path = wd._watchdog_service_token_path()
|
|
76
|
+
wd._write_service_token_file(path, TOKEN)
|
|
77
|
+
assert path.read_text(encoding="utf-8") == TOKEN + "\n"
|
|
78
|
+
if sys.platform != "win32": # POSIX file-mode bits not representable on Windows
|
|
79
|
+
assert stat.S_IMODE(path.stat().st_mode) == 0o600
|
|
80
|
+
assert wd._read_service_token() == TOKEN
|
|
81
|
+
try:
|
|
82
|
+
wd._write_service_token_file(path, "a\nb")
|
|
83
|
+
except ValueError:
|
|
84
|
+
pass
|
|
85
|
+
else:
|
|
86
|
+
raise AssertionError("multiline token accepted")
|
|
87
|
+
assert path.read_text(encoding="utf-8") == TOKEN + "\n"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_install_without_token_warns_unread_blind(monkeypatch, capsys):
|
|
91
|
+
"""No token in env: loud warning (template runs would exit 7 on
|
|
92
|
+
unread), and no token line minted anywhere."""
|
|
93
|
+
sections, err = _dry_sections(capsys, monkeypatch, token=False)
|
|
94
|
+
body = "\n".join(l for lines in sections.values() for l in lines)
|
|
95
|
+
assert "MESH_GATEWAY_TOKEN=" not in body
|
|
96
|
+
assert "unread" in err.lower()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _FakeResp:
|
|
100
|
+
def __init__(self, payload: bytes, status: int = 200):
|
|
101
|
+
self._payload = payload
|
|
102
|
+
self.status = status
|
|
103
|
+
|
|
104
|
+
def read(self):
|
|
105
|
+
return self._payload
|
|
106
|
+
|
|
107
|
+
def __enter__(self):
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def __exit__(self, *exc):
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _rig_a1(monkeypatch, tmp_path, stub):
|
|
115
|
+
"""Shared A1 replay rig: token in env, stale cursor, live session,
|
|
116
|
+
idle pane. Returns (run, seen) where run() executes the check."""
|
|
117
|
+
seen = {}
|
|
118
|
+
monkeypatch.setenv("MESH_GATEWAY_TOKEN", TOKEN)
|
|
119
|
+
monkeypatch.setattr(urllib.request, "urlopen", stub(seen))
|
|
120
|
+
cursor = tmp_path / "cursor.json"
|
|
121
|
+
cursor.write_text("{}")
|
|
122
|
+
os.utime(cursor, (7200, 7200))
|
|
123
|
+
monkeypatch.setattr(wd, "_tmux_session_exists", lambda name: True)
|
|
124
|
+
monkeypatch.setattr(wd, "_pane_activity_age_sec", lambda name: 99999.0)
|
|
125
|
+
monkeypatch.setattr(wd, "_tmux_send_keys", lambda *a, **k: True)
|
|
126
|
+
log = tmp_path / "watch.log"
|
|
127
|
+
|
|
128
|
+
def run(capsys):
|
|
129
|
+
rc = run_watchdog(argv=[
|
|
130
|
+
"--check", "--cell", "opencode", "--no-respawn", "--verbose",
|
|
131
|
+
f"--cursor={cursor}", "--tmux-session", "opencode",
|
|
132
|
+
"--peer", "opencode", "--gateway", "http://gw:1",
|
|
133
|
+
"--liveness-cmd", "true", f"--log={log}",
|
|
134
|
+
"--threshold", "60", "--pane-activity-threshold", "60"])
|
|
135
|
+
return rc, capsys.readouterr()
|
|
136
|
+
return run, seen
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_replay_token_env_yields_a1(monkeypatch, tmp_path, capsys):
|
|
140
|
+
"""Accept replay: token in env, stale cursor, live session, idle
|
|
141
|
+
pane, count endpoint says 3 -> the A1 path with a numeric
|
|
142
|
+
unread_count — the Authorization header carries the token, and no
|
|
143
|
+
list query is ever made (no bodies cross the wire)."""
|
|
144
|
+
calls = []
|
|
145
|
+
|
|
146
|
+
def stub(seen):
|
|
147
|
+
def fake_urlopen(req, timeout=5):
|
|
148
|
+
seen["auth"] = req.get_header("Authorization")
|
|
149
|
+
calls.append(req.full_url)
|
|
150
|
+
return _FakeResp(json.dumps(
|
|
151
|
+
{"to_node": "opencode", "n": 3}).encode())
|
|
152
|
+
return fake_urlopen
|
|
153
|
+
|
|
154
|
+
run, seen = _rig_a1(monkeypatch, tmp_path, stub)
|
|
155
|
+
rc, out = run(capsys)
|
|
156
|
+
assert seen.get("auth") == f"Bearer {TOKEN}", seen
|
|
157
|
+
assert all("/messages/unread-count?" in u for u in calls), calls
|
|
158
|
+
assert '"decision": "a1_send_keys"' in out.out + out.err, out
|
|
159
|
+
assert '"unread_count": 3' in out.out + out.err, out
|
|
160
|
+
assert rc == 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_replay_count_404_falls_back_to_list(monkeypatch, tmp_path, capsys):
|
|
164
|
+
"""Mixed fleet: gateway predates the count endpoint (404) -> the
|
|
165
|
+
client falls back to the list query rather than going blind."""
|
|
166
|
+
def stub(seen):
|
|
167
|
+
def fake_urlopen(req, timeout=5):
|
|
168
|
+
if req.full_url.startswith("http://gw:1/messages/unread-count?"):
|
|
169
|
+
raise urllib.error.HTTPError(
|
|
170
|
+
req.full_url, 404, "nope", {}, None)
|
|
171
|
+
return _FakeResp(json.dumps(
|
|
172
|
+
{"messages": [{}, {}, {}]}).encode())
|
|
173
|
+
return fake_urlopen
|
|
174
|
+
|
|
175
|
+
run, _ = _rig_a1(monkeypatch, tmp_path, stub)
|
|
176
|
+
rc, out = run(capsys)
|
|
177
|
+
assert '"decision": "a1_send_keys"' in out.out + out.err, out
|
|
178
|
+
assert '"unread_count": 3' in out.out + out.err, out
|
|
179
|
+
assert rc == 1
|
|
@@ -1108,6 +1108,7 @@ def test_gateway_unread_count_warns_loudly_when_token_missing(monkeypatch, capsy
|
|
|
1108
1108
|
from swarph_cli.commands import watchdog
|
|
1109
1109
|
|
|
1110
1110
|
class _FakeResp:
|
|
1111
|
+
status = 200 # the transport seam reports status (404 = fall back)
|
|
1111
1112
|
def __enter__(self): return self
|
|
1112
1113
|
def __exit__(self, *a): return False
|
|
1113
1114
|
def read(self): return b'{"messages": []}'
|
|
@@ -1126,6 +1127,7 @@ def test_gateway_unread_count_silent_when_token_present(monkeypatch, capsys):
|
|
|
1126
1127
|
from swarph_cli.commands import watchdog
|
|
1127
1128
|
|
|
1128
1129
|
class _FakeResp:
|
|
1130
|
+
status = 200 # the transport seam reports status (404 = fall back)
|
|
1129
1131
|
def __enter__(self): return self
|
|
1130
1132
|
def __exit__(self, *a): return False
|
|
1131
1133
|
def read(self): return b'{"messages": []}'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|