swarph-cli 0.48.0__tar.gz → 0.48.2__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.48.0/src/swarph_cli.egg-info → swarph_cli-0.48.2}/PKG-INFO +1 -1
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/pyproject.toml +1 -1
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/mesh.py +186 -41
- {swarph_cli-0.48.0 → swarph_cli-0.48.2/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar.py +18 -1
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar_cursor_decoupled.py +10 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar_wake_survives_read.py +10 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_cli.py +10 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_state_split.py +8 -0
- swarph_cli-0.48.2/tests/test_tmux_wake_submit_verify.py +537 -0
- swarph_cli-0.48.0/tests/test_tmux_wake_submit_verify.py +0 -272
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/LICENSE +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/README.md +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/setup.cfg +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_group_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_import_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_init_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_main.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_security.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_app.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.48.0 → swarph_cli-0.48.2}/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.48.
|
|
3
|
+
Version: 0.48.2
|
|
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.48.
|
|
7
|
+
version = "0.48.2"
|
|
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" }
|
|
@@ -1045,8 +1045,13 @@ class Sink:
|
|
|
1045
1045
|
def __init__(self, name: str):
|
|
1046
1046
|
self.name = name
|
|
1047
1047
|
|
|
1048
|
-
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> bool:
|
|
1049
|
-
"""Push `dms` (may be a bounded/empty replay).
|
|
1048
|
+
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
|
|
1049
|
+
"""Push `dms` (may be a bounded/empty replay). Three outcomes:
|
|
1050
|
+
True == delivered (cursor advances); False == FAILED (loud, counted —
|
|
1051
|
+
the sink is probably gone); None == DEFERRED (still owed, but neither
|
|
1052
|
+
delivered nor failed — e.g. TmuxSink's politeness gate finding a
|
|
1053
|
+
human mid-write. A deferral never increments consecutive_failures:
|
|
1054
|
+
a busy composer is not a dead sink)."""
|
|
1050
1055
|
raise NotImplementedError
|
|
1051
1056
|
|
|
1052
1057
|
def observe(self, state: "MonitorState", ledger: dict, window: list) -> bool:
|
|
@@ -1134,6 +1139,16 @@ class TmuxSink(Sink):
|
|
|
1134
1139
|
('check meshcheck mesh…' ×7 observed live on cursor-lin). If the pane
|
|
1135
1140
|
observably still HOLDS an unsubmitted wake, the gate sends a single
|
|
1136
1141
|
verified Enter nudge instead of new text — the anti-stack retry.
|
|
1142
|
+
|
|
1143
|
+
POLITENESS GATE (commander, 2026-08-24, same day): even a verified wake
|
|
1144
|
+
INJECTS BLIND — '-l' appends to whatever sits in the composer, so a wake
|
|
1145
|
+
landing mid-keystroke merges into the human's half-typed line. The gate:
|
|
1146
|
+
no inject and no nudge unless the composer is OBSERVED in a compatible
|
|
1147
|
+
state ("clear" to inject, "wake"-only to nudge). A BUSY composer DEFERS
|
|
1148
|
+
(deliver returns None): the wake stays owed, the cursor does not advance,
|
|
1149
|
+
and no failure is counted — a human typing is not a dead sink. An
|
|
1150
|
+
UNREADABLE composer FAILS without a keystroke: never type blind, and a
|
|
1151
|
+
dead pane must keep the failure loud.
|
|
1137
1152
|
"""
|
|
1138
1153
|
|
|
1139
1154
|
is_push = True
|
|
@@ -1142,7 +1157,7 @@ class TmuxSink(Sink):
|
|
|
1142
1157
|
super().__init__(f"tmux:{target}")
|
|
1143
1158
|
self.target = target
|
|
1144
1159
|
|
|
1145
|
-
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> bool:
|
|
1160
|
+
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
|
|
1146
1161
|
led = state.ledger(self.name)
|
|
1147
1162
|
if led.get("wake_outstanding"):
|
|
1148
1163
|
# Lazy: watchdog imports mesh module-level, so the reverse must
|
|
@@ -1154,18 +1169,47 @@ class TmuxSink(Sink):
|
|
|
1154
1169
|
if unread == 0:
|
|
1155
1170
|
led["wake_outstanding"] = False # drained since — re-arm
|
|
1156
1171
|
else:
|
|
1157
|
-
|
|
1158
|
-
if
|
|
1172
|
+
composer = _composer_state(self.target)
|
|
1173
|
+
if composer == "wake":
|
|
1159
1174
|
_tmux_enter(self.target) # one verified nudge, no new text
|
|
1160
1175
|
return True
|
|
1161
|
-
if
|
|
1176
|
+
if composer == "clear":
|
|
1162
1177
|
# Wake submitted; the cell simply hasn't drained yet. The
|
|
1163
1178
|
# wake did its job — re-injecting would only stack.
|
|
1164
1179
|
return True
|
|
1180
|
+
if composer == "busy":
|
|
1181
|
+
# Human text shares the composer (possibly merged into our
|
|
1182
|
+
# wake) — an Enter here submits THEIR line (#403's shape).
|
|
1183
|
+
return None
|
|
1165
1184
|
return False # pane unreadable: keep the failure loud
|
|
1185
|
+
# Fresh path, gated on the OBSERVED composer (gpt-ops REVISE, #312):
|
|
1186
|
+
# "wake" — wake text already sits ALONE in the composer (a monitor
|
|
1187
|
+
# restart loses wake_outstanding; the ledger flag is gone but the
|
|
1188
|
+
# keystrokes landed). Nudge it with one Enter — injecting here would
|
|
1189
|
+
# stack a second copy, the exact defect the gate exists to prevent.
|
|
1190
|
+
# "busy" DEFERS (None — a human mid-write is not a dead sink).
|
|
1191
|
+
# Unreadable FAILS (False) without a single keystroke: we never type
|
|
1192
|
+
# blind into a pane, and a dead pane must stay loud — deferring it
|
|
1193
|
+
# would silently freeze the dead-sink alarm the ledger exists to ring.
|
|
1194
|
+
composer = _composer_state(self.target)
|
|
1195
|
+
if composer == "wake":
|
|
1196
|
+
if _tmux_enter(self.target):
|
|
1197
|
+
led["wake_outstanding"] = True
|
|
1198
|
+
return True
|
|
1199
|
+
return False
|
|
1200
|
+
if composer == "busy":
|
|
1201
|
+
return None
|
|
1202
|
+
if composer != "clear":
|
|
1203
|
+
return False
|
|
1166
1204
|
# Module-global lookup on purpose: the sidecar regression suites patch
|
|
1167
1205
|
# `mesh._tmux_wake`, and a `from`-import here would silently bypass them.
|
|
1168
1206
|
ok = _tmux_wake(self.target)
|
|
1207
|
+
if ok is None:
|
|
1208
|
+
# The human ADOPTED the composer mid-settle (gpt-ops, #312 round
|
|
1209
|
+
# 5): the wake text never submitted as ours. DEFER — the cursor
|
|
1210
|
+
# must NOT advance, or the wake is silently lost when the human
|
|
1211
|
+
# never sends their line.
|
|
1212
|
+
return None
|
|
1169
1213
|
if ok:
|
|
1170
1214
|
led["wake_outstanding"] = True
|
|
1171
1215
|
return True
|
|
@@ -1514,15 +1558,17 @@ _WAKE_SETTLE_S = 0.6
|
|
|
1514
1558
|
_WAKE_SUBMIT_ATTEMPTS = 4
|
|
1515
1559
|
|
|
1516
1560
|
|
|
1517
|
-
def
|
|
1518
|
-
"""
|
|
1519
|
-
(
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1561
|
+
def _capture_pane_lines(target: str) -> Optional[list[str]]:
|
|
1562
|
+
"""The pane's non-empty lines, or None when the pane is UNREADABLE
|
|
1563
|
+
(capture error / non-zero rc). Shared by the wake verifier and the
|
|
1564
|
+
politeness gate — None must always fail closed, never "probably fine".
|
|
1565
|
+
|
|
1566
|
+
NOT a fixed tail window: cursor's TUI renders chrome BELOW the composer
|
|
1567
|
+
(task count, model/status bar, '~' — measured live on cursor-lin
|
|
1568
|
+
2026-08-24: the composer sits 4 non-empty lines from the bottom), so a
|
|
1569
|
+
tail-3 lands entirely in the chrome and reads every cursor pane as
|
|
1570
|
+
unknown. The composer is identified structurally instead — see
|
|
1571
|
+
_composer_line."""
|
|
1526
1572
|
try:
|
|
1527
1573
|
r = subprocess.run(
|
|
1528
1574
|
["tmux", "capture-pane", "-p", "-t", target],
|
|
@@ -1534,21 +1580,98 @@ def _wake_still_pending(target: str) -> Optional[bool]:
|
|
|
1534
1580
|
return None
|
|
1535
1581
|
if r.returncode != 0:
|
|
1536
1582
|
return None
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1583
|
+
return [ln for ln in (r.stdout or "").splitlines() if ln.strip()]
|
|
1584
|
+
|
|
1585
|
+
|
|
1586
|
+
_COMPOSER_MARKERS = (">", "›", "→")
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
def _composer_line(lines: list[str]) -> Optional[str]:
|
|
1590
|
+
"""The LAST non-empty line starting with a composer marker — by TUI
|
|
1591
|
+
layout the composer is always the bottom-most prompt line; everything
|
|
1592
|
+
below it is chrome (status bars never start with a marker), and
|
|
1593
|
+
everything above is history. Cursor echoes submitted prompts as
|
|
1594
|
+
'│ ○ text │' and claude/codex as '> text' ABOVE the composer, so the
|
|
1595
|
+
last-marker rule is what keeps history out of the reading. Markers:
|
|
1596
|
+
'>' claude, '›' codex (gpt-ops live capture), '→' cursor (measured live
|
|
1597
|
+
on cursor-lin 2026-08-24: the composer renders '→ Add a follow-up')."""
|
|
1598
|
+
composer = None
|
|
1599
|
+
for ln in lines:
|
|
1600
|
+
s = ln.strip()
|
|
1601
|
+
if s.startswith(_COMPOSER_MARKERS):
|
|
1602
|
+
composer = s
|
|
1603
|
+
return composer
|
|
1604
|
+
|
|
1605
|
+
|
|
1606
|
+
def _wake_still_pending(target: str) -> Optional[bool]:
|
|
1607
|
+
"""Projection of the four-way composer state (gpt-ops, PR #312 round 4):
|
|
1608
|
+
|
|
1609
|
+
True = the composer holds ONLY wake text (one unsubmitted wake, or the
|
|
1610
|
+
pre-fix stacked concatenation 'check meshcheck mesh' — both are drained
|
|
1611
|
+
by the retry Enter). False = anything else OBSERVED: a clear composer
|
|
1612
|
+
(the wake submitted) OR human text in the line — including wake text
|
|
1613
|
+
MERGED with human text ('> check mesh half-typed', typed during the
|
|
1614
|
+
settle). Merged must stop the loop WITHOUT another Enter: that Enter
|
|
1615
|
+
would submit the human's half-typed line (#403's shape). The wake is
|
|
1616
|
+
then out of our hands — the human owns the line, and the busy-deferral
|
|
1617
|
+
in TmuxSink.deliver keeps the wake owed until they send it. None =
|
|
1618
|
+
UNKNOWN (unreadable / no composer line recognized) — and unknown must
|
|
1619
|
+
FAIL CLOSED (gpt-ops, PR #306): an unverified retry Enter is not a
|
|
1620
|
+
no-op, it is a keypress into a pane whose composer may hold a human's
|
|
1621
|
+
half-typed line. Unknown stops the loop and owes the wake; it never
|
|
1622
|
+
earns another Enter."""
|
|
1623
|
+
state = _composer_state(target)
|
|
1624
|
+
if state is None:
|
|
1625
|
+
return None
|
|
1626
|
+
return state == "wake"
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
#: cursor's EMPTY composer is not bare: it renders the marker plus a dimmed
|
|
1630
|
+
#: placeholder ('→ Add a follow-up', measured live on cursor-lin 2026-08-24).
|
|
1631
|
+
#: Treating any text-after-marker as busy would defer every wake forever on
|
|
1632
|
+
#: cursor; treating the placeholder as human text is the only false positive
|
|
1633
|
+
#: it can produce, and the failure direction there is a wake never sent —
|
|
1634
|
+
#: visible in the log, never a keystroke into someone's line.
|
|
1635
|
+
_CURSOR_COMPOSER_PLACEHOLDER = "Add a follow-up"
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
def _composer_state(target: str) -> Optional[str]:
|
|
1639
|
+
"""What the pane's composer LINE holds, four-way (the politeness gate):
|
|
1640
|
+
|
|
1641
|
+
"clear" — a composer prompt observed EMPTY (bare marker, or cursor's
|
|
1642
|
+
placeholder). Safe to inject.
|
|
1643
|
+
"wake" — the composer holds ONLY wake text (one wake, or the pre-fix
|
|
1644
|
+
stacked concatenation 'check meshcheck mesh'). Safe to
|
|
1645
|
+
nudge with ONE Enter; injecting more text would stack.
|
|
1646
|
+
"busy" — the composer holds anything else: a human mid-write, or
|
|
1647
|
+
human text merged into our wake. Neither inject nor Enter.
|
|
1648
|
+
None — pane unreadable or no composer line recognized. UNKNOWN,
|
|
1649
|
+
and unknown fails closed: we never type blind into a pane.
|
|
1650
|
+
|
|
1651
|
+
The composer line comes from _composer_line (last marker line in the
|
|
1652
|
+
capture — cursor renders chrome BELOW the composer, so no fixed tail
|
|
1653
|
+
window can find it).
|
|
1654
|
+
"""
|
|
1655
|
+
lines = _capture_pane_lines(target)
|
|
1656
|
+
if lines is None:
|
|
1657
|
+
return None
|
|
1658
|
+
composer = _composer_line(lines)
|
|
1659
|
+
if composer is None:
|
|
1660
|
+
return None
|
|
1661
|
+
content = composer[1:].strip()
|
|
1662
|
+
# cursor right-aligns run-state hints ('ctrl+c to stop') on the composer
|
|
1663
|
+
# ROW, so the placeholder is a PREFIX of the line while the agent runs,
|
|
1664
|
+
# not the whole line. A human message beginning with the exact placeholder
|
|
1665
|
+
# text is the accepted false positive — the only failure it can produce
|
|
1666
|
+
# is an inject alongside contrived text, never a silent deferral.
|
|
1667
|
+
if not content or content.startswith(_CURSOR_COMPOSER_PLACEHOLDER):
|
|
1668
|
+
return "clear"
|
|
1669
|
+
# A human who types exactly 'check mesh' by hand is indistinguishable
|
|
1670
|
+
# from our own unsubmitted wake — and submitting it runs the same
|
|
1671
|
+
# command, so the collision is harmless in that direction only.
|
|
1672
|
+
if content.replace(_WAKE_PROMPT, "").strip() == "":
|
|
1673
|
+
return "wake"
|
|
1674
|
+
return "busy"
|
|
1552
1675
|
|
|
1553
1676
|
|
|
1554
1677
|
def _tmux_enter(target: str) -> bool:
|
|
@@ -1567,7 +1690,7 @@ def _tmux_enter(target: str) -> bool:
|
|
|
1567
1690
|
return False
|
|
1568
1691
|
|
|
1569
1692
|
|
|
1570
|
-
def _tmux_wake(target: str) -> bool:
|
|
1693
|
+
def _tmux_wake(target: str) -> Optional[bool]:
|
|
1571
1694
|
"""Inject the wake prompt and confirm it SUBMITTED (#533).
|
|
1572
1695
|
|
|
1573
1696
|
Was: Codex's blind double-Enter, on the assumption that a second Enter in
|
|
@@ -1577,13 +1700,16 @@ def _tmux_wake(target: str) -> bool:
|
|
|
1577
1700
|
prompt on cursor-lin, 2026-08-24, three times in a row.
|
|
1578
1701
|
|
|
1579
1702
|
Now: inject, settle, then Enter-and-verify in a bounded loop — re-Enter
|
|
1580
|
-
only while the
|
|
1703
|
+
only while the composer is OBSERVED holding wake text alone. Codex's
|
|
1581
1704
|
second Enter falls out naturally (it fires only when the first did not
|
|
1582
|
-
submit).
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1705
|
+
submit). Three outcomes: True on an observed-clear composer (submitted);
|
|
1706
|
+
None when the human ADOPTED the composer mid-settle (wake text merged
|
|
1707
|
+
with their typing — another Enter would submit their line, so the loop
|
|
1708
|
+
stops, and the sink DEFERS: the cursor must not advance on a wake that
|
|
1709
|
+
never submitted as ours — gpt-ops, #312 round 5); False when the pane
|
|
1710
|
+
could not be verified (the wake stays owed). An UNREADABLE pane (capture
|
|
1711
|
+
failure) fails closed — no unverified retries, no claimed success; the
|
|
1712
|
+
wake stays owed and the ledger retries the whole wake later.
|
|
1587
1713
|
"""
|
|
1588
1714
|
try:
|
|
1589
1715
|
subprocess.run(
|
|
@@ -1603,8 +1729,8 @@ def _tmux_wake(target: str) -> bool:
|
|
|
1603
1729
|
text=True, encoding="utf-8", errors="replace",
|
|
1604
1730
|
)
|
|
1605
1731
|
time.sleep(_WAKE_SETTLE_S)
|
|
1606
|
-
|
|
1607
|
-
if
|
|
1732
|
+
state = _composer_state(target)
|
|
1733
|
+
if state is None:
|
|
1608
1734
|
print(
|
|
1609
1735
|
"[monitor] tmux wake unverifiable (capture failed); "
|
|
1610
1736
|
"failing closed — wake stays owed",
|
|
@@ -1612,8 +1738,16 @@ def _tmux_wake(target: str) -> bool:
|
|
|
1612
1738
|
flush=True,
|
|
1613
1739
|
)
|
|
1614
1740
|
return False
|
|
1615
|
-
if
|
|
1741
|
+
if state == "clear":
|
|
1616
1742
|
return True
|
|
1743
|
+
if state == "busy":
|
|
1744
|
+
# The human typed into the wake mid-settle (gpt-ops, #312
|
|
1745
|
+
# round 5): the wake text never submitted as ours, so this
|
|
1746
|
+
# must NOT acknowledge delivery — return None and let the
|
|
1747
|
+
# sink DEFER, keeping the cursor back so a later poll
|
|
1748
|
+
# retries the wake when the composer clears.
|
|
1749
|
+
return None
|
|
1750
|
+
# "wake": still sitting unsubmitted — loop another Enter.
|
|
1617
1751
|
print(
|
|
1618
1752
|
f"[monitor] tmux wake still unsubmitted after "
|
|
1619
1753
|
f"{_WAKE_SUBMIT_ATTEMPTS} Enters; wake stays owed",
|
|
@@ -1805,7 +1939,18 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
1805
1939
|
f"delivering the newest {len(dms)} (limit "
|
|
1806
1940
|
f"{state.replay_limit}) -- see {state.inbox_log_path}",
|
|
1807
1941
|
file=sys.stderr, flush=True)
|
|
1808
|
-
|
|
1942
|
+
outcome = sink.deliver(state, dms, observed)
|
|
1943
|
+
if outcome is None:
|
|
1944
|
+
# DEFERRED (e.g. TmuxSink's politeness gate): the wake stays
|
|
1945
|
+
# owed — the cursor does NOT advance — but a deferral is not
|
|
1946
|
+
# a failure: no count, no alarm, no ledger write. Retried on
|
|
1947
|
+
# the next poll like any owed delivery.
|
|
1948
|
+
print(f"{state.log_prefix} delivery DEFERRED for {sink.name} "
|
|
1949
|
+
f"(composer holds human text); wake stays owed, "
|
|
1950
|
+
f"no failure counted",
|
|
1951
|
+
flush=True)
|
|
1952
|
+
continue
|
|
1953
|
+
if outcome:
|
|
1809
1954
|
led["last_delivered_id"] = observed
|
|
1810
1955
|
led["last_delivery_at"] = now
|
|
1811
1956
|
led["consecutive_failures"] = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.48.
|
|
3
|
+
Version: 0.48.2
|
|
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
|
|
@@ -6,9 +6,19 @@ import json
|
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
9
11
|
from swarph_cli.commands import mesh
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
@pytest.fixture(autouse=True)
|
|
15
|
+
def _clean_composer(monkeypatch):
|
|
16
|
+
"""These tests pin ledger/cursor mechanics, not the politeness gate —
|
|
17
|
+
default every composer to OBSERVED-CLEAN so the gate stays out of the
|
|
18
|
+
way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
|
|
19
|
+
monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
|
|
20
|
+
|
|
21
|
+
|
|
12
22
|
def _state(tmp_path: Path) -> mesh.MeshSidecarState:
|
|
13
23
|
return mesh.MeshSidecarState(
|
|
14
24
|
self_name="gpt-ops",
|
|
@@ -39,12 +49,19 @@ def test_tmux_wake_sends_literal_prompt_then_submits_and_verifies(monkeypatch):
|
|
|
39
49
|
lambda command, **kwargs: calls.append((command, kwargs)) or Result(),
|
|
40
50
|
)
|
|
41
51
|
monkeypatch.setattr(mesh.time, "sleep", lambda _s: None)
|
|
52
|
+
# The verifier is now a projection of the four-way composer state; the
|
|
53
|
+
# autouse fixture stubs that state. Record the consultation instead of
|
|
54
|
+
# expecting a raw capture-pane call in the sequence.
|
|
55
|
+
seen = []
|
|
56
|
+
monkeypatch.setattr(
|
|
57
|
+
mesh, "_composer_state",
|
|
58
|
+
lambda t: seen.append(t) or "clear")
|
|
42
59
|
|
|
43
60
|
assert mesh._tmux_wake("gpt-lc:0.0") is True
|
|
61
|
+
assert seen == ["gpt-lc:0.0"] # verified through the composer gate
|
|
44
62
|
assert [command for command, _ in calls] == [
|
|
45
63
|
["tmux", "send-keys", "-t", "gpt-lc:0.0", "-l", "check mesh"],
|
|
46
64
|
["tmux", "send-keys", "-t", "gpt-lc:0.0", "Enter"],
|
|
47
|
-
["tmux", "capture-pane", "-p", "-t", "gpt-lc:0.0"],
|
|
48
65
|
]
|
|
49
66
|
|
|
50
67
|
|
|
@@ -17,9 +17,19 @@ succeeds. Waking is DELIVERY; the cursor is BOOKKEEPING.
|
|
|
17
17
|
|
|
18
18
|
Run: venv/bin/python -m pytest tests/test_mesh_sidecar_cursor_decoupled.py -v
|
|
19
19
|
"""
|
|
20
|
+
import pytest
|
|
21
|
+
|
|
20
22
|
from swarph_cli.commands import mesh
|
|
21
23
|
|
|
22
24
|
|
|
25
|
+
@pytest.fixture(autouse=True)
|
|
26
|
+
def _clean_composer(monkeypatch):
|
|
27
|
+
"""These tests pin cursor/ledger decoupling, not the politeness gate —
|
|
28
|
+
default every composer to OBSERVED-CLEAN so the gate stays out of the
|
|
29
|
+
way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
|
|
30
|
+
monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
|
|
31
|
+
|
|
32
|
+
|
|
23
33
|
def _state(tmp_path, wake_min_interval_s=0):
|
|
24
34
|
return mesh.MeshSidecarState(
|
|
25
35
|
self_name="lab-ovh",
|
|
@@ -9,9 +9,19 @@ Run: venv/bin/python -m pytest tests/test_mesh_sidecar_wake_survives_read.py -v
|
|
|
9
9
|
"""
|
|
10
10
|
import urllib.parse
|
|
11
11
|
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
12
14
|
from swarph_cli.commands import mesh
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
@pytest.fixture(autouse=True)
|
|
18
|
+
def _clean_composer(monkeypatch):
|
|
19
|
+
"""These tests pin ledger/cursor mechanics, not the politeness gate —
|
|
20
|
+
default every composer to OBSERVED-CLEAN so the gate stays out of the
|
|
21
|
+
way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
|
|
22
|
+
monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
|
|
23
|
+
|
|
24
|
+
|
|
15
25
|
def _state(tmp_path, wake_min_interval_s=0):
|
|
16
26
|
return mesh.MeshSidecarState(
|
|
17
27
|
self_name="grok-researcher",
|
|
@@ -16,9 +16,19 @@ from __future__ import annotations
|
|
|
16
16
|
import json
|
|
17
17
|
import os
|
|
18
18
|
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
19
21
|
from swarph_cli.commands import mesh, monitor
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
@pytest.fixture(autouse=True)
|
|
25
|
+
def _clean_composer(monkeypatch):
|
|
26
|
+
"""These tests pin CLI/ledger mechanics, not the politeness gate —
|
|
27
|
+
default every composer to OBSERVED-CLEAN so the gate stays out of the
|
|
28
|
+
way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
|
|
29
|
+
monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
|
|
30
|
+
|
|
31
|
+
|
|
22
32
|
def _env(monkeypatch):
|
|
23
33
|
monkeypatch.setenv("SWARPH_SELF", "lab-ovh")
|
|
24
34
|
monkeypatch.setenv("MESH_GATEWAY_TOKEN", "tok")
|
|
@@ -25,6 +25,14 @@ import pytest
|
|
|
25
25
|
from swarph_cli.commands import mesh
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
@pytest.fixture(autouse=True)
|
|
29
|
+
def _clean_composer(monkeypatch):
|
|
30
|
+
"""These tests pin ledger/cursor mechanics, not the politeness gate —
|
|
31
|
+
default every composer to OBSERVED-CLEAN so the gate stays out of the
|
|
32
|
+
way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
|
|
33
|
+
monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
|
|
34
|
+
|
|
35
|
+
|
|
28
36
|
def _state(tmp_path, sinks, *, min_interval_s=0, self_name="lab-ovh", replay_limit=50):
|
|
29
37
|
return mesh.MonitorState(
|
|
30
38
|
self_name=self_name,
|