swarph-cli 0.69.6__tar.gz → 0.69.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.69.6/src/swarph_cli.egg-info → swarph_cli-0.69.8}/PKG-INFO +1 -1
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/pyproject.toml +1 -1
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/mesh.py +30 -14
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/ensure_monitor.sh +9 -61
- {swarph_cli-0.69.6 → swarph_cli-0.69.8/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/SOURCES.txt +2 -0
- swarph_cli-0.69.8/tests/test_1006_session_board.py +291 -0
- swarph_cli-0.69.8/tests/test_726_grok_limit_screen.py +60 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_960_ensure_monitor_heartbeat.py +47 -16
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/LICENSE +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/README.md +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/setup.cfg +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_group_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_import_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_init_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_main.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_security.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_app.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_version_command.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.69.6 → swarph_cli-0.69.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.69.
|
|
3
|
+
Version: 0.69.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.69.
|
|
7
|
+
version = "0.69.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" }
|
|
@@ -2306,23 +2306,39 @@ def _grok_input(lines: list[str]) -> Optional[str]:
|
|
|
2306
2306
|
return after.strip()
|
|
2307
2307
|
|
|
2308
2308
|
|
|
2309
|
-
|
|
2310
|
-
|
|
2309
|
+
# One grok screen, measured on the live 0.2.51 pane (52 rows). Lines above
|
|
2310
|
+
# this window are scrollback. A transcript that merely quotes the limit
|
|
2311
|
+
# strings is not the limit screen.
|
|
2312
|
+
_GROK_SCREEN_ROWS = 52
|
|
2313
|
+
|
|
2311
2314
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2315
|
+
def _grok_block_reason(lines: list[str]) -> Optional[str]:
|
|
2316
|
+
"""A rate or usage limit screen is not an idle composer and not a dead pane.
|
|
2317
|
+
|
|
2318
|
+
Match the screen by where it is drawn, not by a quote anywhere in the
|
|
2319
|
+
capture. 1.0.44 draws a ``┃`` modal ``You hit your free usage limit``
|
|
2320
|
+
(no Grok footer). 0.2.51 draws a status line that starts ``Retry failed:``
|
|
2321
|
+
on the current screen, under the composer. A cursor transcript that
|
|
2322
|
+
quotes those sentences, and a grok scrollback that still contains them
|
|
2323
|
+
above an idle screen, are not that screen.
|
|
2317
2324
|
"""
|
|
2318
|
-
|
|
2319
|
-
|
|
2325
|
+
screen = lines[-_GROK_SCREEN_ROWS:]
|
|
2326
|
+
# Opencode draws its transcript in ┃ rows, so a quoted sentence there is
|
|
2327
|
+
# not the grok modal. The modal identifies itself (Upgrade to SuperGrok)
|
|
2328
|
+
# or the pane is already grok.
|
|
2329
|
+
modal = "\n".join(ln for ln in screen if "┃" in ln).lower()
|
|
2330
|
+
if ("you hit your free usage limit" in modal
|
|
2331
|
+
and (_is_grok_pane(screen) or "upgrade to supergrok" in modal)):
|
|
2320
2332
|
return "grok-usage-limit"
|
|
2321
|
-
if
|
|
2322
|
-
return
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2333
|
+
if not _is_grok_pane(screen):
|
|
2334
|
+
return None
|
|
2335
|
+
for ln in screen:
|
|
2336
|
+
stripped = ln.strip()
|
|
2337
|
+
low = stripped.lower()
|
|
2338
|
+
if not stripped.startswith("Retry failed:"):
|
|
2339
|
+
continue
|
|
2340
|
+
if "rate limit" in low:
|
|
2341
|
+
return "grok-rate-limit"
|
|
2326
2342
|
return "grok-retry-failed"
|
|
2327
2343
|
return None
|
|
2328
2344
|
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.69.
|
|
3
|
+
Version: 0.69.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
|
|
@@ -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
|
|
@@ -224,6 +225,7 @@ tests/test_716_timeline_one_path.py
|
|
|
224
225
|
tests/test_723_grok_0251.py
|
|
225
226
|
tests/test_724_ps1_ascii_or_bom.py
|
|
226
227
|
tests/test_725_stdio_reconfigure.py
|
|
228
|
+
tests/test_726_grok_limit_screen.py
|
|
227
229
|
tests/test_729_dm_once.py
|
|
228
230
|
tests/test_729_wake_watchdog.py
|
|
229
231
|
tests/test_734_dreaming_enrich_degrade.py
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""card #1006 — CLI session board. The desktop tool names must not appear."""
|
|
2
|
+
import json
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
8
|
+
SKILL = ROOT / "plugins" / "swarph" / "skills" / "lej-session-board"
|
|
9
|
+
FIX = ROOT / "tests" / "fixtures" / "session-board"
|
|
10
|
+
SCRIPT = SKILL / "scripts" / "build_board.py"
|
|
11
|
+
|
|
12
|
+
# Names that exist only on Claude Desktop. Referencing one is a fail.
|
|
13
|
+
DESKTOP_TOOLS = (
|
|
14
|
+
"mcp__ccd_session_mgmt__",
|
|
15
|
+
"list_sessions",
|
|
16
|
+
"list_events",
|
|
17
|
+
"archive_session",
|
|
18
|
+
"set_pinned",
|
|
19
|
+
"get_session",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _skill_texts():
|
|
24
|
+
files = [SKILL / "SKILL.md", SKILL / "references" / "coordination-rule.md", SCRIPT]
|
|
25
|
+
return {p.relative_to(SKILL).as_posix(): p.read_text(encoding="utf-8") for p in files}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def desktop_hits(text: str) -> list[str]:
|
|
29
|
+
return [name for name in DESKTOP_TOOLS if name in text]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_desktop_tool_name_is_not_referenced():
|
|
33
|
+
for rel, text in _skill_texts().items():
|
|
34
|
+
assert desktop_hits(text) == [], rel
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_the_scanner_fails_when_a_desktop_tool_name_is_added():
|
|
38
|
+
poisoned = "call mcp__ccd_session_mgmt__list_sessions here"
|
|
39
|
+
assert "mcp__ccd_session_mgmt__" in desktop_hits(poisoned)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_license_and_attribution_kept():
|
|
43
|
+
lic = (SKILL / "LICENSE").read_text(encoding="utf-8")
|
|
44
|
+
assert "MIT License" in lic
|
|
45
|
+
assert "Jonathan Edwards" in lic
|
|
46
|
+
readme = (SKILL / "README.md").read_text(encoding="utf-8")
|
|
47
|
+
assert "Jonathan Edwards" in readme
|
|
48
|
+
assert "MIT" in readme
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_coordination_rule_uses_listagents_and_sendmessage_by_name():
|
|
52
|
+
text = (SKILL / "references" / "coordination-rule.md").read_text(encoding="utf-8")
|
|
53
|
+
assert "ListAgents" in text
|
|
54
|
+
assert "SendMessage" in text
|
|
55
|
+
assert "by the name" in text
|
|
56
|
+
assert "approves building, not deploying" in text
|
|
57
|
+
assert desktop_hits(text) == []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _run_board():
|
|
61
|
+
proc = subprocess.run(
|
|
62
|
+
[sys.executable, str(SCRIPT),
|
|
63
|
+
"--listing", str(FIX / "listagents-lab.txt"),
|
|
64
|
+
"--rows", str(FIX / "commander-170.json")],
|
|
65
|
+
check=True, capture_output=True, text=True,
|
|
66
|
+
)
|
|
67
|
+
return json.loads(proc.stdout)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _validate(board: dict):
|
|
71
|
+
"""The fields references/data-schema.md says the page reads."""
|
|
72
|
+
schema = (SKILL / "references" / "data-schema.md").read_text(encoding="utf-8")
|
|
73
|
+
for field in ("updated", "projectFolder", "manager", "sessions", "groups"):
|
|
74
|
+
assert field in schema
|
|
75
|
+
assert field in board
|
|
76
|
+
assert isinstance(board["sessions"], list) and board["sessions"]
|
|
77
|
+
for sess in board["sessions"]:
|
|
78
|
+
for field in ("id", "name", "state", "tone", "where", "questions"):
|
|
79
|
+
assert field in sess
|
|
80
|
+
assert sess["tone"] in ("needs", "done", "")
|
|
81
|
+
assert 2 <= len(sess["where"]) <= 4
|
|
82
|
+
for q in sess["questions"]:
|
|
83
|
+
for field in ("id", "to", "title", "options", "to_node", "in_session"):
|
|
84
|
+
assert field in q
|
|
85
|
+
assert field in schema
|
|
86
|
+
assert "card" in schema and "obligation" in schema
|
|
87
|
+
assert q["to_node"] == q["to"]
|
|
88
|
+
assert isinstance(q["in_session"], bool)
|
|
89
|
+
assert 2 <= len(q["options"]) <= 3
|
|
90
|
+
assert sum(1 for opt in q["options"] if opt.get("rec")) == 1
|
|
91
|
+
for opt in q["options"]:
|
|
92
|
+
assert opt["label"] and opt["text"]
|
|
93
|
+
for group in board["groups"]:
|
|
94
|
+
assert "title" in group and "items" in group
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_skill_maps_a_name_through_the_sessions_file_and_lists_shell():
|
|
98
|
+
text = (SKILL / "SKILL.md").read_text(encoding="utf-8")
|
|
99
|
+
step = text.split("## 1. Take the roster", 1)[1].split("## 2.", 1)[0]
|
|
100
|
+
assert "~/.claude/sessions/<pid>.json" in step
|
|
101
|
+
assert "name" in step and "sessionId" in step and "cwd" in step
|
|
102
|
+
assert "`shell`" in step
|
|
103
|
+
assert "in progress" in step.lower() or "In progress" in step
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_shell_status_is_in_progress():
|
|
107
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
108
|
+
import build_board
|
|
109
|
+
listing = "This session is lab-ovh [1e23f6]\npeer [abc123] · interactive · shell · running a command\n"
|
|
110
|
+
board = build_board.build(listing, [])
|
|
111
|
+
sess = next(s for s in board["sessions"] if s["name"] == "peer")
|
|
112
|
+
assert sess["bucket"] == "in_progress"
|
|
113
|
+
assert sess["tone"] == ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_schema_documents_the_four_new_question_fields():
|
|
117
|
+
schema = (SKILL / "references" / "data-schema.md").read_text(encoding="utf-8")
|
|
118
|
+
for field in ("to_node", "card", "obligation", "in_session"):
|
|
119
|
+
assert f"`{field}`" in schema
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_skill_builds_schema_valid_json_without_desktop_tools():
|
|
123
|
+
board = _run_board()
|
|
124
|
+
_validate(board)
|
|
125
|
+
assert board["manager"] == "lab-ovh"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_lab_roster_sorts_offline_remote_control_into_stale():
|
|
129
|
+
listing = (FIX / "listagents-lab.txt").read_text(encoding="utf-8")
|
|
130
|
+
board = _run_board()
|
|
131
|
+
by_id = {s["id"]: s for s in board["sessions"]}
|
|
132
|
+
# every offline Remote Control row from the real listing is stale
|
|
133
|
+
seen = 0
|
|
134
|
+
for line in listing.splitlines():
|
|
135
|
+
if "Remote Control" in line and "offline" in line:
|
|
136
|
+
sid = line.split("[", 1)[1].split("]", 1)[0]
|
|
137
|
+
assert by_id[sid]["bucket"] == "stale"
|
|
138
|
+
assert by_id[sid]["tone"] == "done"
|
|
139
|
+
seen += 1
|
|
140
|
+
assert seen >= 40
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_droplet_section_carries_the_commander_row_with_a_recommendation():
|
|
144
|
+
board = _run_board()
|
|
145
|
+
droplet = next(s for s in board["sessions"] if s["name"] == "droplet")
|
|
146
|
+
assert droplet["bucket"] == "needs_followup"
|
|
147
|
+
assert droplet["tone"] == "needs"
|
|
148
|
+
q = droplet["questions"][0]
|
|
149
|
+
assert "687" in q["title"] or "rebalancer" in q["title"]
|
|
150
|
+
rec = [opt for opt in q["options"] if opt["rec"]]
|
|
151
|
+
assert len(rec) == 1
|
|
152
|
+
assert rec[0]["label"] == "Retire"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_filling_the_template_does_not_change_the_markup():
|
|
156
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
157
|
+
import build_board
|
|
158
|
+
template = (SKILL / "assets" / "board-template.html").read_text(encoding="utf-8")
|
|
159
|
+
board = _run_board()
|
|
160
|
+
# the page does not read the sort bucket
|
|
161
|
+
public = json.loads(json.dumps(board))
|
|
162
|
+
for sess in public["sessions"]:
|
|
163
|
+
sess.pop("bucket", None)
|
|
164
|
+
filled = build_board.fill_template(template, public)
|
|
165
|
+
start = template.find('<script type="application/json" id="board-data">')
|
|
166
|
+
end = template.find("</script>", start)
|
|
167
|
+
assert template[:start] == filled[:start]
|
|
168
|
+
assert template[end:] == filled[filled.find("</script>", start):]
|
|
169
|
+
data = json.loads(filled[filled.find(">", start) + 1:filled.find("</script>", start)])
|
|
170
|
+
_validate(data)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_envelope_header_and_new_fields():
|
|
174
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
175
|
+
import build_board
|
|
176
|
+
board = _run_board()
|
|
177
|
+
body = build_board.envelope(board)
|
|
178
|
+
header, payload = body.split("\n", 1)
|
|
179
|
+
assert header == "SWARPH-BOARD v1"
|
|
180
|
+
parsed = json.loads(payload)
|
|
181
|
+
_validate(parsed)
|
|
182
|
+
droplet = next(s for s in parsed["sessions"] if s["name"] == "droplet")
|
|
183
|
+
q = droplet["questions"][0]
|
|
184
|
+
assert q["to_node"] == "droplet"
|
|
185
|
+
assert q["card"] == 687
|
|
186
|
+
assert q["obligation"] == 170
|
|
187
|
+
assert q["in_session"] is False
|
|
188
|
+
assert "bucket" not in droplet
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_unchanged_board_sends_nothing():
|
|
192
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
193
|
+
import build_board
|
|
194
|
+
board = _run_board()
|
|
195
|
+
sent = []
|
|
196
|
+
assert build_board.publish_if_changed(board, build_board.envelope(board), sent.append) is False
|
|
197
|
+
assert sent == []
|
|
198
|
+
# a clock-only difference is not a change
|
|
199
|
+
later = json.loads(json.dumps(board))
|
|
200
|
+
later["updated"] = "2099-01-01 00:00 UTC"
|
|
201
|
+
assert build_board.publish_if_changed(later, build_board.envelope(board), sent.append) is False
|
|
202
|
+
assert sent == []
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_a_changed_board_sends_the_envelope_once():
|
|
206
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
207
|
+
import build_board
|
|
208
|
+
board = _run_board()
|
|
209
|
+
changed = json.loads(json.dumps(board))
|
|
210
|
+
droplet = next(s for s in changed["sessions"] if s["name"] == "droplet")
|
|
211
|
+
droplet["questions"][0]["title"] = "A different question"
|
|
212
|
+
sent = []
|
|
213
|
+
assert build_board.publish_if_changed(changed, build_board.envelope(board), sent.append) is True
|
|
214
|
+
assert len(sent) == 1
|
|
215
|
+
assert sent[0].startswith("SWARPH-BOARD v1\n")
|
|
216
|
+
assert "A different question" in sent[0]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_deploy_accept_sets_in_session_and_every_question_has_to_node():
|
|
220
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
221
|
+
import build_board
|
|
222
|
+
listing = (FIX / "listagents-lab.txt").read_text(encoding="utf-8")
|
|
223
|
+
rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
|
|
224
|
+
rows.append({
|
|
225
|
+
"cell": "droplet",
|
|
226
|
+
"card_id": 9001,
|
|
227
|
+
"obligation_id": 9001,
|
|
228
|
+
"title": "Production deploy of the board tab",
|
|
229
|
+
"accept": "The commander types the production deploy go in that session. This row is a hard gate.",
|
|
230
|
+
"options": [
|
|
231
|
+
{"label": "Go", "text": "go", "rec": True},
|
|
232
|
+
{"label": "Hold", "text": "hold", "rec": False},
|
|
233
|
+
],
|
|
234
|
+
})
|
|
235
|
+
board = build_board.build(listing, rows)
|
|
236
|
+
questions = []
|
|
237
|
+
for sess in board["sessions"]:
|
|
238
|
+
questions.extend(sess["questions"])
|
|
239
|
+
assert questions
|
|
240
|
+
assert all(q.get("to_node") for q in questions)
|
|
241
|
+
deploy = next(q for q in questions if q["title"] == "Production deploy of the board tab")
|
|
242
|
+
retire = next(q for q in questions if q["obligation"] == 170)
|
|
243
|
+
assert deploy["in_session"] is True
|
|
244
|
+
assert deploy["to_node"] == "droplet"
|
|
245
|
+
assert deploy["card"] == 9001
|
|
246
|
+
assert retire["in_session"] is False
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_an_answer_dm_drops_that_question():
|
|
250
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
251
|
+
import build_board
|
|
252
|
+
rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
|
|
253
|
+
title = rows[0]["title"]
|
|
254
|
+
kept = build_board.drop_answered(
|
|
255
|
+
rows, [("commander", f"Re: {title}\nRetire.\n")], commander="commander")
|
|
256
|
+
assert kept == []
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_only_the_commander_can_drop_a_question():
|
|
260
|
+
"""A Re: line from any other cell must leave the question up.
|
|
261
|
+
|
|
262
|
+
On 61c5f49 drop_answered took bare bodies and dropped the row for any sender.
|
|
263
|
+
"""
|
|
264
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
265
|
+
import build_board
|
|
266
|
+
rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
|
|
267
|
+
title = rows[0]["title"]
|
|
268
|
+
body = f"Re: {title}\nRetire.\n"
|
|
269
|
+
kept = build_board.drop_answered(rows, [("lab-ovh", body)], commander="commander")
|
|
270
|
+
assert len(kept) == 1
|
|
271
|
+
assert kept[0]["title"] == title
|
|
272
|
+
cleared = build_board.drop_answered(rows, [("commander", body)], commander="commander")
|
|
273
|
+
assert cleared == []
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_redeploy_is_in_session_and_deployment_notes_are_not():
|
|
277
|
+
sys.path.insert(0, str(SCRIPT.parent))
|
|
278
|
+
import build_board
|
|
279
|
+
assert build_board._in_session({"accept": "redeploy the gateway"}) is True
|
|
280
|
+
assert build_board._in_session({"accept": "deployment notes for the board"}) is False
|
|
281
|
+
assert build_board._in_session({"accept": "production deploy go"}) is True
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_skill_step_4_names_the_sender_and_the_trigger_words():
|
|
285
|
+
text = (SKILL / "SKILL.md").read_text(encoding="utf-8")
|
|
286
|
+
step = text.split("## 4. Answers", 1)[1].split("## ", 1)[0]
|
|
287
|
+
assert "from_node" in step
|
|
288
|
+
assert "commander" in step
|
|
289
|
+
assert "any other cell" in step
|
|
290
|
+
for word in ("deploy", "redeploy", "hard gate"):
|
|
291
|
+
assert word in step
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""card #961 — the limit check must be the limit screen, not a quote.
|
|
2
|
+
|
|
3
|
+
These assertions fail on 1d55233: that head treats any line containing the
|
|
4
|
+
limit sentences as a grok block, including a cursor transcript and grok
|
|
5
|
+
scrollback above an idle screen.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from swarph_cli.commands import mesh
|
|
10
|
+
|
|
11
|
+
FIX723 = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "grok-723"
|
|
12
|
+
FIX726 = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "grok-726"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def lines(root, name):
|
|
16
|
+
return (root / name).read_text(encoding="utf-8").splitlines()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_cursor_transcript_quoting_both_limits_is_not_blocked():
|
|
20
|
+
pane = lines(FIX726, "cursor-transcript.txt")
|
|
21
|
+
text = "\n".join(pane)
|
|
22
|
+
assert "rate limit" in text
|
|
23
|
+
assert "You hit your free usage limit" in text
|
|
24
|
+
assert "Models matching" in text
|
|
25
|
+
assert mesh._is_grok_pane(pane) is False
|
|
26
|
+
assert mesh._grok_block_reason(pane) is None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_opencode_transcript_quoting_the_usage_limit_is_not_blocked():
|
|
30
|
+
src = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "opencode-pane-idle-footer-0710.txt"
|
|
31
|
+
pane = src.read_text(encoding="utf-8").splitlines()
|
|
32
|
+
# Inside the current screen, on a ┃ transcript row, not a grok modal.
|
|
33
|
+
pane.insert(len(pane) - 8, " ┃ You hit your free usage limit")
|
|
34
|
+
assert mesh._is_opencode_pane(pane) is True
|
|
35
|
+
assert mesh._is_grok_pane(pane) is False
|
|
36
|
+
assert mesh._grok_block_reason(pane) is None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_grok_rate_limit_and_usage_modal_still_defer():
|
|
40
|
+
rate = lines(FIX723, "rate-limit.txt")
|
|
41
|
+
usage = lines(FIX723, "usage-limit.txt")
|
|
42
|
+
assert mesh._grok_block_reason(rate) == "grok-rate-limit"
|
|
43
|
+
assert mesh._grok_turn_finished(rate) is False
|
|
44
|
+
assert mesh._is_grok_pane(usage) is False
|
|
45
|
+
assert mesh._grok_block_reason(usage) == "grok-usage-limit"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_old_scrollback_limit_on_an_idle_grok_screen_reads_idle():
|
|
49
|
+
idle = lines(FIX723, "idle.txt")
|
|
50
|
+
scroll = [
|
|
51
|
+
" ❯ an earlier turn",
|
|
52
|
+
" Retry failed: You’ve hit the rate limit for your plan.",
|
|
53
|
+
" You hit your free usage limit",
|
|
54
|
+
]
|
|
55
|
+
# Push the quote above one screen. The idle composer stays.
|
|
56
|
+
pane = (scroll * 20) + idle
|
|
57
|
+
assert mesh._is_grok_pane(pane)
|
|
58
|
+
assert mesh._grok_block_reason(pane) is None
|
|
59
|
+
assert mesh._grok_composer_state(pane) == "clear"
|
|
60
|
+
assert mesh._grok_running(pane) is False
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
"""#960: a fresh channel heartbeat is
|
|
1
|
+
"""#960: a fresh channel heartbeat is the reader, not the writer.
|
|
2
2
|
|
|
3
|
-
channel-serve writes channel_heartbeat.json
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
starts only when the file is absent or older than 180s.
|
|
3
|
+
channel-serve writes channel_heartbeat.json and only reads inbox.log. The
|
|
4
|
+
pull monitor is the only writer. A fresh heartbeat must not stop that
|
|
5
|
+
monitor from starting. A live monitor (status 0) still starts nothing.
|
|
7
6
|
"""
|
|
8
7
|
from __future__ import annotations
|
|
9
8
|
|
|
@@ -61,11 +60,14 @@ def _run(tmp_path: Path, *, age: float | None):
|
|
|
61
60
|
return proc, calls
|
|
62
61
|
|
|
63
62
|
|
|
64
|
-
def
|
|
63
|
+
def test_fresh_heartbeat_with_no_monitor_starts(tmp_path):
|
|
64
|
+
"""The shim exits 2, so no monitor is running. A 10s-old heartbeat
|
|
65
|
+
still has to start the pull monitor. On main this asserts the opposite.
|
|
66
|
+
"""
|
|
65
67
|
proc, calls = _run(tmp_path, age=10)
|
|
66
68
|
assert proc.returncode == 0
|
|
67
|
-
assert "monitor start"
|
|
68
|
-
assert "under 180s" in proc.stdout
|
|
69
|
+
assert "monitor start" in calls
|
|
70
|
+
assert "under 180s" not in proc.stdout
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
def test_stale_heartbeat_still_starts(tmp_path):
|
|
@@ -107,10 +109,10 @@ def test_a_future_timestamp_an_hour_ahead_starts(tmp_path):
|
|
|
107
109
|
assert "monitor start" in calls
|
|
108
110
|
|
|
109
111
|
|
|
110
|
-
def
|
|
112
|
+
def test_a_timestamp_within_skew_still_starts_when_no_monitor(tmp_path):
|
|
111
113
|
proc, calls = _run_payload(tmp_path, {"ts": time.time() + 10})
|
|
112
114
|
assert proc.returncode == 0
|
|
113
|
-
assert "monitor start"
|
|
115
|
+
assert "monitor start" in calls
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
def test_a_fresh_heartbeat_with_a_dead_pid_starts(tmp_path):
|
|
@@ -120,12 +122,8 @@ def test_a_fresh_heartbeat_with_a_dead_pid_starts(tmp_path):
|
|
|
120
122
|
assert "monitor start" in calls
|
|
121
123
|
|
|
122
124
|
|
|
123
|
-
def
|
|
124
|
-
"""A live channel-
|
|
125
|
-
|
|
126
|
-
Where /proc is missing the command line cannot be checked, so any live pid
|
|
127
|
-
still counts, which is what this host can see.
|
|
128
|
-
"""
|
|
125
|
+
def test_a_fresh_heartbeat_with_a_live_reader_pid_still_starts(tmp_path):
|
|
126
|
+
"""A live channel-serve pid is the reader. It does not stand in for the monitor."""
|
|
129
127
|
child = None
|
|
130
128
|
if os.path.isdir("/proc/self"):
|
|
131
129
|
child = subprocess.Popen(
|
|
@@ -140,7 +138,40 @@ def test_a_fresh_heartbeat_with_a_live_pid_skips(tmp_path):
|
|
|
140
138
|
child.kill()
|
|
141
139
|
child.wait(timeout=5)
|
|
142
140
|
assert proc.returncode == 0
|
|
141
|
+
assert "monitor start" in calls
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_fresh_heartbeat_and_a_live_monitor_starts_nothing(tmp_path):
|
|
145
|
+
"""Status 0 is the writer. A fresh heartbeat beside it must not start a second one."""
|
|
146
|
+
bindir = tmp_path / "bin"
|
|
147
|
+
bindir.mkdir()
|
|
148
|
+
shim = bindir / "swarph"
|
|
149
|
+
shim.write_text(
|
|
150
|
+
"#!/usr/bin/env bash\n"
|
|
151
|
+
"printf '%s\\n' \"$*\" >> \"$FAKE_LOG\"\n"
|
|
152
|
+
"case \"$*\" in\n"
|
|
153
|
+
" *'monitor status'*) exit 0 ;;\n"
|
|
154
|
+
" *) exit 2 ;;\n"
|
|
155
|
+
"esac\n",
|
|
156
|
+
encoding="utf-8",
|
|
157
|
+
)
|
|
158
|
+
shim.chmod(0o755)
|
|
159
|
+
log = tmp_path / "calls.log"
|
|
160
|
+
side = tmp_path / "state" / "mesh-sidecar"
|
|
161
|
+
side.mkdir(parents=True)
|
|
162
|
+
(side / "channel_heartbeat.json").write_text(
|
|
163
|
+
json.dumps({"ts": time.time()}), encoding="utf-8")
|
|
164
|
+
env = dict(os.environ)
|
|
165
|
+
env["PATH"] = f"{bindir}{os.pathsep}{env.get('PATH', '')}"
|
|
166
|
+
env["SWARPH_SELF"] = "cursor-lin"
|
|
167
|
+
env["SWARPH_STATE_DIR"] = str(tmp_path / "state")
|
|
168
|
+
env["FAKE_LOG"] = str(log)
|
|
169
|
+
proc = subprocess.run(
|
|
170
|
+
[_bash(), str(SCRIPT)], env=env, capture_output=True, text=True, timeout=60)
|
|
171
|
+
calls = log.read_text(encoding="utf-8") if log.exists() else ""
|
|
172
|
+
assert proc.returncode == 0
|
|
143
173
|
assert "monitor start" not in calls
|
|
174
|
+
assert "monitor status" in calls
|
|
144
175
|
|
|
145
176
|
|
|
146
177
|
def test_pid_zero_starts(tmp_path):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|