swarph-cli 0.48.2__tar.gz → 0.49.0__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.2/src/swarph_cli.egg-info → swarph_cli-0.49.0}/PKG-INFO +1 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/pyproject.toml +1 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/brain_ask.py +27 -11
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/cell_selfcheck.py +10 -4
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/codegraph.py +2 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/codegraph_hook.py +10 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/daemon.py +6 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/event.py +5 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/init.py +6 -3
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/mesh.py +71 -15
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/monitor.py +3 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/onboard.py +6 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/ratify.py +5 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/watchdog.py +39 -14
- swarph_cli-0.49.0/src/swarph_cli/gateway_default.py +70 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/guide/GUIDE.md +1 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/codex-waker.default +6 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/peer-reply-drain.default +6 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_546_default_gateway.py +22 -32
- swarph_cli-0.49.0/tests/test_578_no_machine_specific_host_defaults.py +458 -0
- swarph_cli-0.49.0/tests/test_579_register_bootstrap_message.py +98 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_brain_ask_command.py +30 -5
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_content_composition.py +4 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_daemon_command.py +2 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_command.py +9 -2
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_onboard_command.py +4 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_ratify_command.py +5 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_shipped_units_match_the_cli.py +40 -8
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog.py +8 -4
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/LICENSE +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/README.md +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/setup.cfg +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_main.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_security.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.48.2 → swarph_cli-0.49.0}/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.
|
|
3
|
+
Version: 0.49.0
|
|
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.
|
|
7
|
+
version = "0.49.0"
|
|
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" }
|
|
@@ -10,7 +10,7 @@ verb so any cell can search the swarm's shared memory the same way. Two modes:
|
|
|
10
10
|
Stdlib-only. Config from the environment, mirroring ``swarph mesh``'s token model:
|
|
11
11
|
|
|
12
12
|
GBRAIN_MCP_URL gbrain MCP endpoint; falls back to SWARPH_BRAIN_MCP, else
|
|
13
|
-
/ SWARPH_BRAIN_MCP http
|
|
13
|
+
/ SWARPH_BRAIN_MCP http://<gbrain-host>:8792/mcp (no default — gbrain
|
|
14
14
|
binds no loopback; measured 2026-08-23, card #548)
|
|
15
15
|
GBRAIN_TOKEN read token; falls back to SWARPH_BRAIN_TOKEN, then to the
|
|
16
16
|
/ SWARPH_BRAIN_TOKEN mesh per-peer token (~/.config/swarph/<self>.peer_token).
|
|
@@ -35,23 +35,34 @@ from swarph_cli import tokens
|
|
|
35
35
|
from pathlib import Path
|
|
36
36
|
from typing import Optional
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
_DEFAULT_GBRAIN =
|
|
38
|
+
# NO MODULE-LEVEL ENDPOINT CONSTANT, ON PURPOSE. gbrain binds its tailnet IP
|
|
39
|
+
# only (127.0.0.1:8792 refuses even on gbrain's own box), so SWARPH_BRAIN_MCP must
|
|
40
|
+
# be set explicitly and no host ships as a default (#548 -> #578).
|
|
41
|
+
# `_DEFAULT_GBRAIN = os.environ.get("SWARPH_BRAIN_MCP")` used to live here and was
|
|
42
|
+
# read at IMPORT time, which froze the developer's shell into the module:
|
|
43
|
+
# MEASURED in seat-A review of PR #318 — with SWARPH_BRAIN_MCP exported,
|
|
44
|
+
# test_resolve_endpoint_refuses_rather_than_guessing_a_host FAILED, because the
|
|
45
|
+
# test can delenv the two call-time operands but not a value already captured.
|
|
46
|
+
# The env is read at CALL time in _resolve_endpoint below, and nowhere else.
|
|
42
47
|
_DEFAULT_TOPK = 6
|
|
43
48
|
|
|
44
49
|
|
|
45
|
-
def _resolve_endpoint() -> str:
|
|
46
|
-
"""Endpoint precedence: GBRAIN_MCP_URL > SWARPH_BRAIN_MCP
|
|
50
|
+
def _resolve_endpoint(explicit: str | None = None) -> str:
|
|
51
|
+
"""Endpoint precedence: GBRAIN_MCP_URL > SWARPH_BRAIN_MCP. No host default (#578).
|
|
47
52
|
|
|
48
53
|
The SWARPH_BRAIN_MCP fallback keeps the verb config-compatible with the
|
|
49
54
|
standalone ``swarph-brain-ask`` script (which reads SWARPH_BRAIN_*), so one
|
|
50
55
|
env config works with both.
|
|
51
56
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
from swarph_cli.gateway_default import require_gateway
|
|
58
|
+
|
|
59
|
+
return require_gateway(
|
|
60
|
+
explicit
|
|
61
|
+
or os.environ.get("GBRAIN_MCP_URL")
|
|
62
|
+
or os.environ.get("SWARPH_BRAIN_MCP"),
|
|
63
|
+
env="SWARPH_BRAIN_MCP",
|
|
64
|
+
what="gbrain MCP",
|
|
65
|
+
)
|
|
55
66
|
|
|
56
67
|
|
|
57
68
|
def _build_query_request(question: str, limit: int = _DEFAULT_TOPK) -> dict:
|
|
@@ -237,10 +248,15 @@ def run_brain_ask(argv: list) -> int:
|
|
|
237
248
|
help="top-k chunks to retrieve (default 6)")
|
|
238
249
|
parser.add_argument("--no-synth", action="store_true",
|
|
239
250
|
help="retrieval only — print raw chunks, skip prose synthesis")
|
|
240
|
-
parser.add_argument("--gateway", default=
|
|
251
|
+
parser.add_argument("--gateway", default=None,
|
|
241
252
|
help="gbrain MCP endpoint (env: GBRAIN_MCP_URL or SWARPH_BRAIN_MCP)")
|
|
242
253
|
parser.add_argument("--token-file", default=None, help="explicit read-token file")
|
|
243
254
|
args = parser.parse_args(argv)
|
|
255
|
+
# #578/#318-review: resolve the endpoint HERE, not as an argparse default.
|
|
256
|
+
# Evaluating it at parser-BUILD time made `--help` die and made the
|
|
257
|
+
# `--gateway` the error message tells you to pass unreachable, because the
|
|
258
|
+
# parser never got as far as parsing it. Same pattern as ratify:173.
|
|
259
|
+
args.gateway = _resolve_endpoint(args.gateway)
|
|
244
260
|
question = " ".join(args.question)
|
|
245
261
|
|
|
246
262
|
gw = os.environ.get("SWARPH_BRAIN_GATEWAY")
|
|
@@ -290,14 +290,20 @@ def unit_is_relevant(name: str, text: str) -> bool:
|
|
|
290
290
|
# cost of the circularity break, stated here rather than discovered.
|
|
291
291
|
|
|
292
292
|
# TAILNET IP, NOT localhost and NOT a MagicDNS name (commander, 2026-08-21).
|
|
293
|
-
# The mesh-gateway binds
|
|
293
|
+
# The mesh-gateway binds a tailnet IP ONLY; localhost has never been bound.
|
|
294
294
|
# MESH_GATEWAY_URL overrides — the escape hatch for anyone outside this mesh.
|
|
295
|
-
|
|
295
|
+
# Inlined, NOT imported from swarph_cli.gateway_default: this module is
|
|
296
|
+
# deliberately stdlib-only so it can run as a bare file in isolated mode
|
|
297
|
+
# (test_module_imports_are_stdlib_only). Same policy as #578, no import.
|
|
298
|
+
# NO import-time capture of os.environ here either: `_RESOLVER_DEFAULT_GATEWAY =
|
|
299
|
+
# os.environ.get("MESH_GATEWAY_URL")` used to back this resolver, which made the
|
|
300
|
+
# answer depend on the shell that IMPORTED the module rather than the env passed
|
|
301
|
+
# in. The env is the ARGUMENT — see #578's "model the environment as data".
|
|
296
302
|
|
|
297
303
|
|
|
298
304
|
def resolver_gateway(env: dict) -> str:
|
|
299
|
-
"""What the CLI would use: $MESH_GATEWAY_URL, else
|
|
300
|
-
return (env.get("MESH_GATEWAY_URL") or "").strip()
|
|
305
|
+
"""What the CLI would use: $MESH_GATEWAY_URL, else "" — no host ships (#578)."""
|
|
306
|
+
return (env.get("MESH_GATEWAY_URL") or "").strip()
|
|
301
307
|
|
|
302
308
|
|
|
303
309
|
def resolver_token(self_name: str, env: dict, home: Path) -> tuple[Optional[str], str]:
|
|
@@ -245,8 +245,8 @@ def _resolve_gateway(arg) -> str:
|
|
|
245
245
|
|
|
246
246
|
SWARPH_CODEGRAPH_GATEWAY <unset>
|
|
247
247
|
SWARPH_GATEWAY <unset>
|
|
248
|
-
SWARPH_BRAIN_GATEWAY http
|
|
249
|
-
MESH_GATEWAY_URL http
|
|
248
|
+
SWARPH_BRAIN_GATEWAY http://<host>:8788 <- brain-NAMED, MESH value
|
|
249
|
+
MESH_GATEWAY_URL http://<host>:8788 <- correct, and NOT consulted
|
|
250
250
|
|
|
251
251
|
The relay worked ONLY because a brain-named variable was mis-set to the mesh
|
|
252
252
|
gateway's address. Every link that resolved was unset or wrong-by-name, and the
|
|
@@ -36,8 +36,8 @@ import urllib.error
|
|
|
36
36
|
import urllib.request
|
|
37
37
|
from pathlib import Path
|
|
38
38
|
from typing import Optional
|
|
39
|
+
from swarph_cli.gateway_default import env_gateway
|
|
39
40
|
|
|
40
|
-
DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
41
41
|
TIMEOUT_S = 6
|
|
42
42
|
MAX_ROWS = 6
|
|
43
43
|
|
|
@@ -131,6 +131,14 @@ def query_gateway(term: str, gateway: str, token: str, limit: int = MAX_ROWS) ->
|
|
|
131
131
|
>>> AN UNREACHABLE OR REFUSING BACKEND IS AN ERROR, NOT AN EMPTY RESULT. <<<
|
|
132
132
|
Collapsing them is the defect this hook exists to avoid teaching.
|
|
133
133
|
"""
|
|
134
|
+
# #578: with no baked-in host, `gateway` can be "". Request() rejects a
|
|
135
|
+
# relative URL at CONSTRUCTION (below, outside the try), so an unconfigured
|
|
136
|
+
# backend would raise ValueError and take the whole turn down — against this
|
|
137
|
+
# entry point's "ALWAYS exits 0, must never fail a turn" contract.
|
|
138
|
+
# UNCONFIGURED is the same class as UNREACHABLE: an error, not empty.
|
|
139
|
+
if not (gateway or "").strip():
|
|
140
|
+
return {"error": "MESH_GATEWAY_URL is not set and swarph ships no default "
|
|
141
|
+
"gateway host (#578) — the graph was never asked"}
|
|
134
142
|
req = urllib.request.Request(
|
|
135
143
|
f"{gateway.rstrip('/')}/codegraph",
|
|
136
144
|
data=json.dumps({"query": term, "limit": limit}).encode(),
|
|
@@ -228,7 +236,7 @@ def run_codegraph_hook(argv: Optional[list] = None) -> int:
|
|
|
228
236
|
"""PostToolUse(Bash) entry point. ALWAYS exits 0 — it must never fail a turn."""
|
|
229
237
|
argv = list(argv or [])
|
|
230
238
|
self_name = os.environ.get("SWARPH_SELF", "").strip()
|
|
231
|
-
gateway =
|
|
239
|
+
gateway = env_gateway()
|
|
232
240
|
for i, a in enumerate(argv):
|
|
233
241
|
if a == "--as" and i + 1 < len(argv):
|
|
234
242
|
self_name = argv[i + 1]
|
|
@@ -52,6 +52,7 @@ from typing import Optional
|
|
|
52
52
|
from swarph_cli import session_bridge, stall_alert
|
|
53
53
|
from swarph_cli.console_safe import print_safe
|
|
54
54
|
from swarph_cli.delivery_queue import DeliveryQueue
|
|
55
|
+
from swarph_cli.gateway_default import env_gateway, require_gateway
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
_DEFAULT_POLL_S = 30
|
|
@@ -85,7 +86,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
85
86
|
)
|
|
86
87
|
p.add_argument(
|
|
87
88
|
"--gateway",
|
|
88
|
-
default=
|
|
89
|
+
default=env_gateway(),
|
|
89
90
|
help="mesh-gateway base URL.",
|
|
90
91
|
)
|
|
91
92
|
p.add_argument(
|
|
@@ -564,6 +565,10 @@ def run_daemon(argv: list[str]) -> int:
|
|
|
564
565
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
565
566
|
# Announce ownership of this state dir BEFORE any cursor work, so a
|
|
566
567
|
# concurrently-starting `swarph monitor` sees it and refuses (PR #139).
|
|
568
|
+
# #578: identity is resolved above first, so a missing --self reports THAT,
|
|
569
|
+
# not a missing env var. The gateway is only needed for the requests below.
|
|
570
|
+
args.gateway = require_gateway(getattr(args, "gateway", ""))
|
|
571
|
+
|
|
567
572
|
_write_daemon_pidfile(state_dir)
|
|
568
573
|
token = _resolve_token(args.token_file)
|
|
569
574
|
if not token:
|
|
@@ -67,9 +67,12 @@ def _post_channel(
|
|
|
67
67
|
"""
|
|
68
68
|
name = resolve_self_name(self_name)
|
|
69
69
|
token = resolve_token(name, token_file)
|
|
70
|
-
from .
|
|
70
|
+
from swarph_cli.gateway_default import env_gateway
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
# "" when unconfigured — post_json IS mesh._post_json, whose
|
|
73
|
+
# _require_absolute_gateway_url then refuses by name rather than raising
|
|
74
|
+
# `ValueError: unknown url type` from Request(). Checked in review of #318.
|
|
75
|
+
base = (gateway or env_gateway()).rstrip("/")
|
|
73
76
|
ev: dict = {"event": event, "payload": payload}
|
|
74
77
|
if chain_token is not None: # carry an explicit "" through; only omit when unset
|
|
75
78
|
ev["chain_token"] = chain_token
|
|
@@ -22,8 +22,8 @@ import yaml
|
|
|
22
22
|
|
|
23
23
|
from swarph_shared.cell import PEER_NAME_RE, parse_cell_dict
|
|
24
24
|
from swarph_cli.cell import CLI_ENABLED_PROVIDERS, CellError, cells_dir
|
|
25
|
+
from swarph_cli.gateway_default import env_gateway
|
|
25
26
|
|
|
26
|
-
_DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
27
27
|
_CODEX_SANDBOX_DEFAULT = "workspace-write"
|
|
28
28
|
_CODEX_SANDBOX_VALUES = ("workspace-write", "read-only")
|
|
29
29
|
|
|
@@ -128,7 +128,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
128
128
|
p.add_argument("--tmux", default=None)
|
|
129
129
|
p.add_argument("--cursor", default=None)
|
|
130
130
|
p.add_argument("--sandbox", default=None)
|
|
131
|
-
p.add_argument("--gateway", default=
|
|
131
|
+
p.add_argument("--gateway", default=env_gateway())
|
|
132
132
|
p.add_argument("--assisted-memory", dest="assisted_memory", default=None, metavar="REPO",
|
|
133
133
|
help="enable git-backed memory at REPO (SSH→HTTPS normalized)")
|
|
134
134
|
p.add_argument("--starter", default=None)
|
|
@@ -197,8 +197,11 @@ def run_init(argv: list[str]) -> int:
|
|
|
197
197
|
"cwd": str(cwd),
|
|
198
198
|
"tmux_session": tmux,
|
|
199
199
|
"cursor_path": cursor,
|
|
200
|
-
"mesh": {"gateway": args.gateway},
|
|
201
200
|
}
|
|
201
|
+
# Only when one was actually chosen: with the env unset args.gateway is ""
|
|
202
|
+
# (#578 ships no host), and writing that records a decision nobody made.
|
|
203
|
+
if args.gateway:
|
|
204
|
+
cell["mesh"] = {"gateway": args.gateway}
|
|
202
205
|
if sandbox is not None:
|
|
203
206
|
cell["sandbox"] = sandbox
|
|
204
207
|
if args.starter:
|
|
@@ -39,6 +39,7 @@ from typing import Optional
|
|
|
39
39
|
from .. import tokens
|
|
40
40
|
from ._content import ContentError, add_content_args, resolve_content
|
|
41
41
|
from ._display import sanitize_terminal
|
|
42
|
+
from swarph_cli.gateway_default import GatewayNotConfigured, env_gateway
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def _format_inbox_line(dm: dict) -> str:
|
|
@@ -51,17 +52,13 @@ def _format_inbox_line(dm: dict) -> str:
|
|
|
51
52
|
return f"id={dm.get('id')} {read} from={who} kind={kind} {content}"
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
# and no local collision; the IP needs only that tailscale is up, which is the real
|
|
62
|
-
# precondition anyway. MESH_GATEWAY_URL overrides it — that env var is the escape hatch
|
|
63
|
-
# for anyone outside this mesh, and optional inside it.
|
|
64
|
-
_DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
55
|
+
# NO MODULE-LEVEL GATEWAY CONSTANT (#578). #546 put the gateway's tailnet IP here,
|
|
56
|
+
# correctly at the time; that box was retired 2026-08-25 and the literal expired.
|
|
57
|
+
# The constant is gone rather than emptied: read at IMPORT time it captured whatever
|
|
58
|
+
# the packaging/importing shell exported, and MEASURED in seat-A review of PR #318 it
|
|
59
|
+
# still returned that value after the environment was cleared — the one channel by
|
|
60
|
+
# which a test could depend on a developer's shell. env_gateway() is called at
|
|
61
|
+
# parser-BUILD time instead (parsers are built per invocation).
|
|
65
62
|
_DEFAULT_POLL_S = 30
|
|
66
63
|
_BACKOFF_EMPTY_THRESHOLD = 5
|
|
67
64
|
_BACKOFF_EMPTY_SECONDS = 60
|
|
@@ -170,7 +167,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
170
167
|
)
|
|
171
168
|
peers.add_argument(
|
|
172
169
|
"--gateway",
|
|
173
|
-
default=
|
|
170
|
+
default=env_gateway(),
|
|
174
171
|
help="mesh-gateway base URL",
|
|
175
172
|
)
|
|
176
173
|
peers.add_argument("--token-file", default=None, help="explicit bearer token file")
|
|
@@ -200,7 +197,7 @@ def _add_common(p: argparse.ArgumentParser) -> None:
|
|
|
200
197
|
)
|
|
201
198
|
p.add_argument(
|
|
202
199
|
"--gateway",
|
|
203
|
-
default=
|
|
200
|
+
default=env_gateway(),
|
|
204
201
|
help="mesh-gateway base URL",
|
|
205
202
|
)
|
|
206
203
|
p.add_argument("--token-file", default=None, help="explicit bearer token file")
|
|
@@ -313,12 +310,69 @@ def _resolve_token(
|
|
|
313
310
|
)
|
|
314
311
|
if res is not None:
|
|
315
312
|
return res.token
|
|
313
|
+
# #579: the old message named the very file `register` exists to MINT, which
|
|
314
|
+
# reads as a chicken-and-egg to anyone bootstrapping a NEW peer (droplet hit
|
|
315
|
+
# exactly this onboarding friendly-coder, 2026-08-25). `swarph onboard`
|
|
316
|
+
# already explains the operator path; this now says the same thing.
|
|
317
|
+
#
|
|
318
|
+
# >>> THE BOOTSTRAP PARAGRAPH BELONGS TO `register` ALONE. <<< It first shipped
|
|
319
|
+
# unconditionally, so `mesh send`/`reply`/`inbox`/`sidecar` — which all reach here
|
|
320
|
+
# with the default allow_peer_token=True — told a user with a missing token that
|
|
321
|
+
# "minting it is what this command does", which is FALSE for send. Measured in
|
|
322
|
+
# seat-A review of PR #318. allow_peer_token=False is exactly the register path
|
|
323
|
+
# (:848 is its only caller), so it is the condition, not a new flag.
|
|
324
|
+
bootstrap = (
|
|
325
|
+
f"\n"
|
|
326
|
+
f" If you are BOOTSTRAPPING {self_name!r}, that token does not exist yet:\n"
|
|
327
|
+
f" minting it is what this command does, so it cannot also be the way in.\n"
|
|
328
|
+
f" Registering a peer that is not yourself is an OPERATOR action. Where the\n"
|
|
329
|
+
f" gateway enforces caller binding (MESH_CALLER_BINDING_ENFORCE=1, as this\n"
|
|
330
|
+
f" mesh's does) another cell's per-peer token 403s; where it does not, it is\n"
|
|
331
|
+
f" simply the wrong credential. `swarph gateway serve` does NOT enforce it by\n"
|
|
332
|
+
f" default — do not read the 403 as a guarantee.\n"
|
|
333
|
+
f" swarph mesh register --as {self_name} --token-file <operator-token>\n"
|
|
334
|
+
f" MESH_GATEWAY_TOKEN=<operator-token> swarph mesh register --as {self_name}"
|
|
335
|
+
) if not allow_peer_token else (
|
|
336
|
+
f"\n"
|
|
337
|
+
f" If it does not exist yet, {self_name!r} has no credential on this mesh —\n"
|
|
338
|
+
f" `swarph mesh register` (an OPERATOR action) is what mints one."
|
|
339
|
+
)
|
|
316
340
|
raise RuntimeError(
|
|
317
|
-
"cannot resolve mesh
|
|
318
|
-
f"
|
|
341
|
+
f"cannot resolve a mesh credential for {self_name!r}.\n"
|
|
342
|
+
f"\n"
|
|
343
|
+
f" looked for: ${{MESH_GATEWAY_TOKEN}}\n"
|
|
344
|
+
f" {_peer_token_path(self_name)}\n"
|
|
345
|
+
f"\n"
|
|
346
|
+
f" If {self_name!r} ALREADY EXISTS, its per-peer token is the right\n"
|
|
347
|
+
f" credential — place it at the path above (mode 600) and re-run.\n"
|
|
348
|
+
+ bootstrap
|
|
319
349
|
)
|
|
320
350
|
|
|
321
351
|
|
|
352
|
+
|
|
353
|
+
def _require_absolute_gateway_url(url: str) -> None:
|
|
354
|
+
"""#578: no gateway host ships as a default.
|
|
355
|
+
|
|
356
|
+
Guarding here rather than at the dispatcher is deliberate: the gateway is
|
|
357
|
+
needed only when a REQUEST is made. A dispatcher-level check also fired for
|
|
358
|
+
paths that never touch the network (sidecar target validation, --json shape
|
|
359
|
+
tests) and turned 10 failures into 26. The observable that means
|
|
360
|
+
"unconfigured" is a url that is not absolute — `/peers` instead of
|
|
361
|
+
`http://host:8788/peers` — so key on that.
|
|
362
|
+
"""
|
|
363
|
+
if not str(url).startswith(("http://", "https://")):
|
|
364
|
+
# GatewayNotConfigured (a RuntimeError) rather than a bare RuntimeError:
|
|
365
|
+
# cli used to carry TWO refusal shapes for one variable and one remedy —
|
|
366
|
+
# require_gateway at dispatch and this one at request time — so the package
|
|
367
|
+
# disagreed with itself about what "no gateway" raises. One family now.
|
|
368
|
+
raise GatewayNotConfigured(
|
|
369
|
+
"MESH_GATEWAY_URL is not set, and swarph ships no default gateway host.\n"
|
|
370
|
+
" A baked-in address expires the day that box is retired "
|
|
371
|
+
"(card #578; it happened on 2026-08-25).\n"
|
|
372
|
+
" Set MESH_GATEWAY_URL=http://<host>:<port>, or pass --gateway.\n"
|
|
373
|
+
f" (tried to request {url!r})"
|
|
374
|
+
)
|
|
375
|
+
|
|
322
376
|
def _post_json(
|
|
323
377
|
url: str,
|
|
324
378
|
body: dict,
|
|
@@ -326,6 +380,7 @@ def _post_json(
|
|
|
326
380
|
*,
|
|
327
381
|
timeout: float = 10.0,
|
|
328
382
|
) -> tuple[int, dict]:
|
|
383
|
+
_require_absolute_gateway_url(url)
|
|
329
384
|
data = json.dumps(body).encode("utf-8")
|
|
330
385
|
req = urllib.request.Request(
|
|
331
386
|
url,
|
|
@@ -355,6 +410,7 @@ def _http_get_json(
|
|
|
355
410
|
*,
|
|
356
411
|
timeout: float = 10.0,
|
|
357
412
|
) -> tuple[int, dict]:
|
|
413
|
+
_require_absolute_gateway_url(url)
|
|
358
414
|
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
359
415
|
try:
|
|
360
416
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
@@ -43,6 +43,8 @@ import sys
|
|
|
43
43
|
import time
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
|
|
46
|
+
from swarph_cli.gateway_default import env_gateway
|
|
47
|
+
|
|
46
48
|
from . import mesh
|
|
47
49
|
|
|
48
50
|
|
|
@@ -122,7 +124,7 @@ def _add_common(p: argparse.ArgumentParser) -> None:
|
|
|
122
124
|
p.add_argument("--state-dir", default=None, help="state directory")
|
|
123
125
|
p.add_argument(
|
|
124
126
|
"--gateway",
|
|
125
|
-
default=
|
|
127
|
+
default=env_gateway(),
|
|
126
128
|
help="mesh-gateway base URL",
|
|
127
129
|
)
|
|
128
130
|
p.add_argument("--token-file", default=None, help="explicit bearer token file")
|
|
@@ -35,6 +35,7 @@ from pathlib import Path
|
|
|
35
35
|
from typing import Optional
|
|
36
36
|
from swarph_cli import tokens
|
|
37
37
|
from swarph_cli.console_safe import print_safe
|
|
38
|
+
from swarph_cli.gateway_default import env_gateway, require_gateway
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
_HANDSHAKE_TEMPLATE = """\
|
|
@@ -111,8 +112,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
111
112
|
)
|
|
112
113
|
p.add_argument(
|
|
113
114
|
"--gateway",
|
|
114
|
-
default=
|
|
115
|
-
help="mesh-gateway base URL (default: $MESH_GATEWAY_URL
|
|
115
|
+
default=env_gateway(),
|
|
116
|
+
help="mesh-gateway base URL (default: $MESH_GATEWAY_URL; no host is shipped)",
|
|
116
117
|
)
|
|
117
118
|
p.add_argument(
|
|
118
119
|
"--token-file",
|
|
@@ -824,6 +825,9 @@ def run_onboard(argv: list[str]) -> int:
|
|
|
824
825
|
|
|
825
826
|
# ── Step 4: POST /peers/register ─────────────────────────────────
|
|
826
827
|
peer_url = args.url or f"http://{canonical}:8787"
|
|
828
|
+
# #578: the gateway is needed only for the REQUEST. Validate args first so a
|
|
829
|
+
# typo'd name reports the typo, not a missing env var.
|
|
830
|
+
args.gateway = require_gateway(getattr(args, "gateway", ""))
|
|
827
831
|
print_safe(f"[4/6] POST {args.gateway}/peers/register")
|
|
828
832
|
# >>> #124's GUARD, MET CLIENT-SIDE — #294's PATTERN, THE MISSING HALF. <<<
|
|
829
833
|
# The gateway 409s a re-register whose payload would DROP stored
|
|
@@ -26,6 +26,7 @@ import urllib.error
|
|
|
26
26
|
import urllib.request
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
from typing import Optional
|
|
29
|
+
from swarph_cli.gateway_default import env_gateway, require_gateway
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -57,7 +58,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
57
58
|
)
|
|
58
59
|
p.add_argument(
|
|
59
60
|
"--gateway",
|
|
60
|
-
default=
|
|
61
|
+
default=env_gateway(),
|
|
61
62
|
help="mesh-gateway base URL.",
|
|
62
63
|
)
|
|
63
64
|
p.add_argument(
|
|
@@ -167,6 +168,9 @@ def run_ratify(argv: list[str]) -> int:
|
|
|
167
168
|
return 1
|
|
168
169
|
|
|
169
170
|
# ── Step 2: caller-side gate (witness must itself be ratified) ───
|
|
171
|
+
# #578: the gateway is needed only for the REQUEST. Arg validation above runs
|
|
172
|
+
# first, so a bad name reports the bad name, not a missing env var.
|
|
173
|
+
args.gateway = require_gateway(getattr(args, "gateway", ""))
|
|
170
174
|
print(f"[2/6] verify witness {witness!r} is ratified")
|
|
171
175
|
status, witness_body = _http_json(
|
|
172
176
|
f"{args.gateway}/peers/{witness}", token=token
|
|
@@ -77,6 +77,7 @@ import urllib.request
|
|
|
77
77
|
from datetime import datetime, timedelta, timezone
|
|
78
78
|
from pathlib import Path
|
|
79
79
|
from typing import Optional
|
|
80
|
+
from swarph_cli.gateway_default import env_gateway
|
|
80
81
|
|
|
81
82
|
from swarph_cli.commands.mesh import _post_json
|
|
82
83
|
|
|
@@ -84,17 +85,13 @@ from swarph_cli.commands.mesh import _post_json
|
|
|
84
85
|
_DEFAULT_THRESHOLD_SEC = 1800 # 30 minutes
|
|
85
86
|
_DEFAULT_A1_RETRIES = 3
|
|
86
87
|
_DEFAULT_A1_BACKOFF_SEC = 60
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# and no local collision; the IP needs only that tailscale is up, which is the real
|
|
95
|
-
# precondition anyway. MESH_GATEWAY_URL overrides it — that env var is the escape hatch
|
|
96
|
-
# for anyone outside this mesh, and optional inside it.
|
|
97
|
-
_DEFAULT_GATEWAY_URL = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
88
|
+
# NO MODULE-LEVEL GATEWAY CONSTANT (#578). #546 put the gateway's tailnet IP here,
|
|
89
|
+
# correctly at the time; that box was retired 2026-08-25 and the literal expired.
|
|
90
|
+
# The constant is gone rather than emptied: read at IMPORT time it captured whatever
|
|
91
|
+
# the packaging/importing shell exported, and MEASURED in seat-A review of PR #318 it
|
|
92
|
+
# still returned that value after the environment was cleared — the one channel by
|
|
93
|
+
# which a test could depend on a developer's shell. env_gateway() is called at
|
|
94
|
+
# parser-BUILD time instead (parsers are built per invocation).
|
|
98
95
|
# F3 — tmux pane_activity gate threshold. If pane has activity within this
|
|
99
96
|
# many seconds, suppress A1 (session is working, not stalled). 600s (10min)
|
|
100
97
|
# is comfortably above legitimate-pause noise + comfortably below the
|
|
@@ -197,7 +194,7 @@ Flags:
|
|
|
197
194
|
--cursor PATH cursor JSON path; default $TMPDIR/<role>-cursor.json
|
|
198
195
|
fallback /tmp/lab-claude-cursor.json
|
|
199
196
|
--threshold SEC darkness threshold; default 1800 (30 min)
|
|
200
|
-
--gateway URL mesh-gateway URL for unread-DM check; default
|
|
197
|
+
--gateway URL mesh-gateway URL for unread-DM check; default $MESH_GATEWAY_URL
|
|
201
198
|
--tmux-session NAME tmux session name; default = cell role
|
|
202
199
|
--peer NAME mesh peer name for unread-DM query; default = cell name
|
|
203
200
|
--no-respawn A1 only; don't escalate to A2 (dry-run mode)
|
|
@@ -437,6 +434,28 @@ def _save_dm_wake_state(path: Path, state: dict) -> None:
|
|
|
437
434
|
pass
|
|
438
435
|
|
|
439
436
|
|
|
437
|
+
|
|
438
|
+
def _gateway_configured(gateway: str) -> bool:
|
|
439
|
+
"""False when no gateway is configured — degrade, do not crash.
|
|
440
|
+
|
|
441
|
+
#578 removed the baked-in host, so `gateway` can now be "". Every caller here
|
|
442
|
+
builds `urllib.request.Request(f"{gateway}/...")` OUTSIDE its try, and
|
|
443
|
+
Request() rejects a relative URL at CONSTRUCTION with ValueError, before any
|
|
444
|
+
I/O — so nothing catches it.
|
|
445
|
+
|
|
446
|
+
That matters most where it is least visible: `_run_local_check` asks for the
|
|
447
|
+
unread count BEFORE the A2 "process dead -> respawn regardless of unread"
|
|
448
|
+
decision, so an uncaught raise kills the tick before it can rescue anything.
|
|
449
|
+
On sys3 today both `swarph-watchdog.timer` and `swarph-watchdog-science.timer`
|
|
450
|
+
run every 5 minutes with `EnvironmentFile=-/etc/default/swarph-watchdog`, and
|
|
451
|
+
THAT FILE DOES NOT EXIST — no MESH_GATEWAY_URL in the unit env at all.
|
|
452
|
+
|
|
453
|
+
An unreachable gateway has always degraded to None here. An UNCONFIGURED one
|
|
454
|
+
now does the same, rather than becoming a new crash class.
|
|
455
|
+
Found by drop-on-meta-edge, seat-A review of PR #318.
|
|
456
|
+
"""
|
|
457
|
+
return bool((gateway or "").strip())
|
|
458
|
+
|
|
440
459
|
def _gateway_unread_count(gateway: str, peer: str, token: Optional[str]) -> Optional[int]:
|
|
441
460
|
"""Query gateway for unread DM count addressed to peer.
|
|
442
461
|
|
|
@@ -453,6 +472,8 @@ def _gateway_unread_count(gateway: str, peer: str, token: Optional[str]) -> Opti
|
|
|
453
472
|
caused a real reader to conclude a gateway/token outage makes A1 MORE
|
|
454
473
|
eager, when it makes A1 permanently inert for that cell instead.
|
|
455
474
|
"""
|
|
475
|
+
if not _gateway_configured(gateway):
|
|
476
|
+
return None
|
|
456
477
|
query = urllib.parse.urlencode({"to_node": peer, "unread_only": "true", "limit": 1})
|
|
457
478
|
url = f"{gateway.rstrip('/')}/messages?{query}"
|
|
458
479
|
req = urllib.request.Request(url)
|
|
@@ -510,6 +531,8 @@ def _gateway_recent_recovery_event(
|
|
|
510
531
|
What was missing was the wake-up mechanism — this function plus the
|
|
511
532
|
fall-through in run_check is the watchdog half of the loop.
|
|
512
533
|
"""
|
|
534
|
+
if not _gateway_configured(gateway):
|
|
535
|
+
return None
|
|
513
536
|
since_dt = datetime.now(timezone.utc) - timedelta(seconds=window_sec)
|
|
514
537
|
since_iso = since_dt.isoformat()
|
|
515
538
|
query = urllib.parse.urlencode(
|
|
@@ -589,6 +612,8 @@ def _stale_peers(
|
|
|
589
612
|
def _fetch_peers(gateway: str, token: str) -> list[dict]:
|
|
590
613
|
"""GET {gateway}/peers (Bearer token). Returns the peer list, or [] on any error
|
|
591
614
|
(never raises). The response may be a bare list OR {"peers": [...]} — handle both."""
|
|
615
|
+
if not _gateway_configured(gateway):
|
|
616
|
+
return None
|
|
592
617
|
url = f"{gateway.rstrip('/')}/peers"
|
|
593
618
|
req = urllib.request.Request(url)
|
|
594
619
|
if token:
|
|
@@ -1710,7 +1735,7 @@ def _execstart_flags(args: argparse.Namespace) -> str:
|
|
|
1710
1735
|
("--peer", "peer", None),
|
|
1711
1736
|
("--liveness-cmd", "liveness_cmd", None),
|
|
1712
1737
|
("--notify-peer", "notify_peer", None),
|
|
1713
|
-
("--gateway", "gateway",
|
|
1738
|
+
("--gateway", "gateway", env_gateway()),
|
|
1714
1739
|
("--threshold", "threshold", _DEFAULT_THRESHOLD_SEC),
|
|
1715
1740
|
("--pane-activity-threshold", "pane_activity_threshold",
|
|
1716
1741
|
_DEFAULT_PANE_ACTIVITY_THRESHOLD_SEC),
|
|
@@ -1914,7 +1939,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1914
1939
|
)
|
|
1915
1940
|
p.add_argument(
|
|
1916
1941
|
"--gateway",
|
|
1917
|
-
default=
|
|
1942
|
+
default=env_gateway(),
|
|
1918
1943
|
)
|
|
1919
1944
|
p.add_argument("--tmux-session", default=None)
|
|
1920
1945
|
p.add_argument("--peer", default=None)
|