swarph-cli 0.71.0__tar.gz → 0.72.1__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.71.0/src/swarph_cli.egg-info → swarph_cli-0.72.1}/PKG-INFO +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/pyproject.toml +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/mesh.py +240 -8
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/monitor.py +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/spawn.py +178 -3
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/stall_alert.py +11 -3
- {swarph_cli-0.71.0 → swarph_cli-0.72.1/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_1006_board_publisher.py +37 -0
- swarph_cli-0.72.1/tests/test_1013_spawn_channel.py +246 -0
- swarph_cli-0.72.1/tests/test_1017_muse_tui.py +129 -0
- swarph_cli-0.72.1/tests/test_484_codex_queue_sink.py +149 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_960_channel.py +14 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_state_split.py +6 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_stall_alert.py +13 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/LICENSE +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/README.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/setup.cfg +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_group_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_import_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_init_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_main.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_app.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_version_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.1}/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.72.1
|
|
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.72.1"
|
|
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" }
|
|
@@ -1482,6 +1482,7 @@ class TmuxSink(Sink):
|
|
|
1482
1482
|
|
|
1483
1483
|
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
|
|
1484
1484
|
led = state.ledger(self.name)
|
|
1485
|
+
self.failure_reason = None
|
|
1485
1486
|
# #723: a rate-limit or usage-limit screen is not deliverable. An
|
|
1486
1487
|
# empty composer beside "Retry failed: rate limit" used to inject
|
|
1487
1488
|
# again, and the 1.0.44 upgrade modal (no Grok footer) used to
|
|
@@ -1619,6 +1620,7 @@ class TmuxSink(Sink):
|
|
|
1619
1620
|
# Human text shares the composer (possibly merged into our
|
|
1620
1621
|
# wake) — an Enter here submits THEIR line (#403's shape).
|
|
1621
1622
|
return None
|
|
1623
|
+
self.failure_reason = _sink_failure_detail(self.target)
|
|
1622
1624
|
return False # pane unreadable: keep the failure loud
|
|
1623
1625
|
# Fresh path, gated on the OBSERVED composer (gpt-ops REVISE, #312):
|
|
1624
1626
|
# "wake" — wake text already sits ALONE in the composer (a monitor
|
|
@@ -1647,6 +1649,7 @@ class TmuxSink(Sink):
|
|
|
1647
1649
|
if composer == "busy":
|
|
1648
1650
|
return None
|
|
1649
1651
|
if composer != "clear":
|
|
1652
|
+
self.failure_reason = _sink_failure_detail(self.target)
|
|
1650
1653
|
return False
|
|
1651
1654
|
# #619: never inject mid-turn. A wake into a RUNNING TUI lands in
|
|
1652
1655
|
# cursor's follow-up queue, which is input-gated (measured live
|
|
@@ -1656,6 +1659,13 @@ class TmuxSink(Sink):
|
|
|
1656
1659
|
# after the turn ends injects into an IDLE composer, where Enter
|
|
1657
1660
|
# submits immediately. Cost: <= one poll interval of delay.
|
|
1658
1661
|
if _agent_running(self.target):
|
|
1662
|
+
seen = _capture_pane_lines(self.target)
|
|
1663
|
+
if seen and _muse_running(seen):
|
|
1664
|
+
print(
|
|
1665
|
+
f"[monitor] {self.name}: muse turn in progress — "
|
|
1666
|
+
"deferring, zero keys",
|
|
1667
|
+
flush=True,
|
|
1668
|
+
)
|
|
1659
1669
|
return None
|
|
1660
1670
|
# Module-global lookup on purpose: the sidecar regression suites patch
|
|
1661
1671
|
# `mesh._tmux_wake`, and a `from`-import here would silently bypass them.
|
|
@@ -1922,6 +1932,117 @@ class MuseSink(Sink):
|
|
|
1922
1932
|
return f"{count} DM{plural} not yet delivered to {self.name}"
|
|
1923
1933
|
|
|
1924
1934
|
|
|
1935
|
+
_CODEX_SEND_TIMEOUT_S = 120.0
|
|
1936
|
+
|
|
1937
|
+
|
|
1938
|
+
def _codex_send(thread: str, text: str, *,
|
|
1939
|
+
_run=subprocess.run,
|
|
1940
|
+
_which=shutil.which,
|
|
1941
|
+
_timeout: float = _CODEX_SEND_TIMEOUT_S,
|
|
1942
|
+
) -> tuple:
|
|
1943
|
+
"""One `codex queue --thread <thread> --message <text>`.
|
|
1944
|
+
|
|
1945
|
+
Returns (True, "") on a verified queue, else (False, NAMED reason).
|
|
1946
|
+
Every refusal is named — non-zero exit, missing binary, launch/timeout
|
|
1947
|
+
failure — so a dead wake path reads as a reason, never as silence.
|
|
1948
|
+
`_run`/`_which` are injectable so the result mapping is testable without
|
|
1949
|
+
a codex binary on PATH. Unlike `muse session-message send`, codex queue
|
|
1950
|
+
is a same-box queue file write: no ingress gate, no peer approval.
|
|
1951
|
+
"""
|
|
1952
|
+
prog = _which("codex")
|
|
1953
|
+
if not prog:
|
|
1954
|
+
return (False, "codex binary not found on PATH")
|
|
1955
|
+
argv = [prog, "queue", "--thread", thread, "--message", text]
|
|
1956
|
+
try:
|
|
1957
|
+
proc = _run(argv, capture_output=True, text=True,
|
|
1958
|
+
timeout=_timeout)
|
|
1959
|
+
except FileNotFoundError:
|
|
1960
|
+
return (False, "codex binary not found on PATH")
|
|
1961
|
+
except subprocess.TimeoutExpired:
|
|
1962
|
+
return (False,
|
|
1963
|
+
f"codex queue timed out after {_timeout:.0f}s")
|
|
1964
|
+
except OSError as exc:
|
|
1965
|
+
return (False, f"codex queue failed to launch: {exc}")
|
|
1966
|
+
if getattr(proc, "returncode", 1) != 0:
|
|
1967
|
+
out = ((getattr(proc, "stdout", None) or "")
|
|
1968
|
+
+ "\n" + (getattr(proc, "stderr", None) or ""))
|
|
1969
|
+
snippet = out.strip().splitlines()
|
|
1970
|
+
detail = snippet[-1][:200] if snippet else "no output"
|
|
1971
|
+
return (False,
|
|
1972
|
+
f"codex queue exited "
|
|
1973
|
+
f"{getattr(proc, 'returncode', '?')}: {detail}")
|
|
1974
|
+
return (True, "")
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
class CodexSink(Sink):
|
|
1978
|
+
"""Wake the live gpt-ops session with `codex queue` instead of typing.
|
|
1979
|
+
|
|
1980
|
+
Edge-triggered on the drain like MuseSink: while `wake_outstanding`
|
|
1981
|
+
stands (gateway unread > 0), further deliveries report delivered with NO
|
|
1982
|
+
new send; the next delivery after the drain sends again. There is no
|
|
1983
|
+
composer to read — codex queues into the session, it does not type —
|
|
1984
|
+
so there is no politeness gate, only the outstanding flag plus the
|
|
1985
|
+
gateway drain check. A refused or failed send returns False with a NAMED
|
|
1986
|
+
reason printed loud; it never advances the cursor in silence.
|
|
1987
|
+
"""
|
|
1988
|
+
|
|
1989
|
+
is_push = True
|
|
1990
|
+
|
|
1991
|
+
def __init__(self, thread: str):
|
|
1992
|
+
super().__init__(f"codex:{thread}")
|
|
1993
|
+
self.thread = thread
|
|
1994
|
+
|
|
1995
|
+
def _prompt(self, dms: list) -> str:
|
|
1996
|
+
# Full bodies cross: this prompt IS the delivery, and the cell must
|
|
1997
|
+
# answer the message it actually received, not a 160-char preview.
|
|
1998
|
+
lines = [
|
|
1999
|
+
f"You have {len(dms)} new mesh DM(s). "
|
|
2000
|
+
"Read each and act per your standing instructions.",
|
|
2001
|
+
"",
|
|
2002
|
+
]
|
|
2003
|
+
for dm in dms:
|
|
2004
|
+
lines.append(
|
|
2005
|
+
f"--- id={dm.get('id')} from={dm.get('from_node')} "
|
|
2006
|
+
f"kind={dm.get('kind')} ---"
|
|
2007
|
+
)
|
|
2008
|
+
lines.append(dm.get("content") or "")
|
|
2009
|
+
lines.append("")
|
|
2010
|
+
return "\n".join(lines)
|
|
2011
|
+
|
|
2012
|
+
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
|
|
2013
|
+
led = state.ledger(self.name)
|
|
2014
|
+
if led.get("wake_outstanding"):
|
|
2015
|
+
# Lazy: watchdog imports mesh module-level, so the reverse must
|
|
2016
|
+
# not. None (gateway error) reads as NOT-drained — re-arming on
|
|
2017
|
+
# an unreadable drain signal would re-open the stack.
|
|
2018
|
+
from swarph_cli.commands.watchdog import _gateway_unread_count
|
|
2019
|
+
if _gateway_unread_count(state.gateway, state.self_name,
|
|
2020
|
+
state.token) != 0:
|
|
2021
|
+
print(f"[monitor] {self.name}: delivery reported on a STANDING wake "
|
|
2022
|
+
f"(no send this poll)", flush=True)
|
|
2023
|
+
return True
|
|
2024
|
+
led["wake_outstanding"] = False # drained since — re-arm
|
|
2025
|
+
if not dms:
|
|
2026
|
+
# Nothing recoverable to hand over (log rotated) — do NOT claim
|
|
2027
|
+
# a delivery; the ledger stays and the gap stays visible.
|
|
2028
|
+
print(f"[monitor] {self.name}: {up_to_id} observed but no body "
|
|
2029
|
+
f"recoverable from inbox.log for the owed range", flush=True)
|
|
2030
|
+
return False
|
|
2031
|
+
# Module-global lookup on purpose: the sidecar regression suites patch
|
|
2032
|
+
# `mesh._codex_send`, and a `from`-import here would silently bypass them.
|
|
2033
|
+
ok, reason = _codex_send(self.thread, self._prompt(dms))
|
|
2034
|
+
if ok:
|
|
2035
|
+
led["wake_outstanding"] = True
|
|
2036
|
+
led["last_wake_injected_at"] = time.time()
|
|
2037
|
+
return True
|
|
2038
|
+
print(f"[monitor] {self.name}: codex send FAILED: {reason}", flush=True)
|
|
2039
|
+
return False
|
|
2040
|
+
|
|
2041
|
+
def pending_label(self, count: int) -> str:
|
|
2042
|
+
plural = "s" if count != 1 else ""
|
|
2043
|
+
return f"{count} DM{plural} not yet delivered to {self.name}"
|
|
2044
|
+
|
|
2045
|
+
|
|
1925
2046
|
def parse_sink(spec: str) -> Sink:
|
|
1926
2047
|
"""`--deliver SINK` -> Sink. Unknown or held specs RAISE; nothing no-ops."""
|
|
1927
2048
|
if spec == "pull":
|
|
@@ -1956,6 +2077,13 @@ def parse_sink(spec: str) -> Sink:
|
|
|
1956
2077
|
"sink 'muse:' needs a session, e.g. muse:<session-uuid>"
|
|
1957
2078
|
)
|
|
1958
2079
|
return MuseSink(session)
|
|
2080
|
+
if spec.startswith("codex:"):
|
|
2081
|
+
thread = spec[len("codex:"):]
|
|
2082
|
+
if not thread:
|
|
2083
|
+
raise MonitorSinkError(
|
|
2084
|
+
"sink 'codex:' needs a thread, e.g. codex:<session-uuid>"
|
|
2085
|
+
)
|
|
2086
|
+
return CodexSink(thread)
|
|
1959
2087
|
if spec.startswith("webhook:"):
|
|
1960
2088
|
# HELD by the commander: outward-facing egress, and a build greenlight
|
|
1961
2089
|
# does not clear an egress gate. It must EXIT, not silently no-op — a
|
|
@@ -1968,7 +2096,8 @@ def parse_sink(spec: str) -> Sink:
|
|
|
1968
2096
|
)
|
|
1969
2097
|
raise MonitorSinkError(
|
|
1970
2098
|
f"unknown sink {spec!r}; expected pull, none, stdout, tmux:<target>, "
|
|
1971
|
-
"tmux-notify:<target>, cursor-print:<cell>,
|
|
2099
|
+
"tmux-notify:<target>, cursor-print:<cell>, muse:<session>, "
|
|
2100
|
+
"or codex:<thread>"
|
|
1972
2101
|
)
|
|
1973
2102
|
|
|
1974
2103
|
|
|
@@ -2173,6 +2302,9 @@ _OPENCODE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
|
|
|
2173
2302
|
# Grok's TUI has the same shape (#715, grok 1.0.41): no push channel, so
|
|
2174
2303
|
# the wake is one line naming the pull tool and no DM body.
|
|
2175
2304
|
_GROK_WAKE_PROMPT = "check mesh: swarph_dm_unread"
|
|
2305
|
+
# Muse 1.4.1 has no push channel either. The wake names the pull tool,
|
|
2306
|
+
# same text the grok and opencode sinks inject.
|
|
2307
|
+
_MUSE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
|
|
2176
2308
|
# A finished turn keeps its ▣ header and adds a duration.
|
|
2177
2309
|
# Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
|
|
2178
2310
|
# 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
|
|
@@ -2310,7 +2442,7 @@ def _only_wake_text(content: str) -> bool:
|
|
|
2310
2442
|
human's line.
|
|
2311
2443
|
"""
|
|
2312
2444
|
rest = content
|
|
2313
|
-
for prompt in (_GROK_WAKE_PROMPT, _OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
|
|
2445
|
+
for prompt in (_MUSE_WAKE_PROMPT, _GROK_WAKE_PROMPT, _OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
|
|
2314
2446
|
rest = rest.replace(prompt, "")
|
|
2315
2447
|
return rest.strip() == ""
|
|
2316
2448
|
|
|
@@ -2475,12 +2607,43 @@ def _is_cursor_composer_row(line: str) -> bool:
|
|
|
2475
2607
|
return row.startswith("→") and not row.startswith("▎")
|
|
2476
2608
|
|
|
2477
2609
|
|
|
2610
|
+
def _is_muse_rule(line: str) -> bool:
|
|
2611
|
+
"""A muse composer rule is a run of ``─``, measured on 1.4.1."""
|
|
2612
|
+
s = line.strip()
|
|
2613
|
+
return len(s) >= 8 and "─" in s and set(s) <= {"─"}
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def _is_muse_status(line: str) -> bool:
|
|
2617
|
+
"""A status row under the lower rule.
|
|
2618
|
+
|
|
2619
|
+
Measured muse-spark is ``model · effort · path · YOLO``. An echo
|
|
2620
|
+
provider is ``echo · ~/x · YOLO``. The anchor is two or more `` · ``
|
|
2621
|
+
separators, not the model name. A model switch must not blind the sink.
|
|
2622
|
+
"""
|
|
2623
|
+
return line.count(" · ") >= 2
|
|
2624
|
+
|
|
2625
|
+
|
|
2626
|
+
def _is_muse_composer_row(lines: list[str], index: int) -> bool:
|
|
2627
|
+
"""The live muse composer is a ``❯`` row between two rules, with the
|
|
2628
|
+
model status under the lower rule. A transcript ``❯`` above that box
|
|
2629
|
+
has no rule on both sides, so it is history."""
|
|
2630
|
+
row = lines[index].strip()
|
|
2631
|
+
if not row.startswith("❯"):
|
|
2632
|
+
return False
|
|
2633
|
+
if index == 0 or index + 1 >= len(lines):
|
|
2634
|
+
return False
|
|
2635
|
+
if not (_is_muse_rule(lines[index - 1]) and _is_muse_rule(lines[index + 1])):
|
|
2636
|
+
return False
|
|
2637
|
+
return any(_is_muse_status(ln) for ln in lines[index + 2:index + 6])
|
|
2638
|
+
|
|
2639
|
+
|
|
2478
2640
|
def _bottom_tui(lines: list[str]) -> "str | None":
|
|
2479
2641
|
"""Who owns the bottom-most composer row in the non-empty tail.
|
|
2480
2642
|
|
|
2481
|
-
``"cursor"``, ``"grok"``, or ``"
|
|
2482
|
-
is scrollback. The lower composer wins, so a quoted grok box
|
|
2483
|
-
quoted ``┃ Build ·`` row above the cursor composer does not take
|
|
2643
|
+
``"cursor"``, ``"grok"``, ``"opencode"``, or ``"muse"``. A match above
|
|
2644
|
+
that tail is scrollback. The lower composer wins, so a quoted grok box
|
|
2645
|
+
or a quoted ``┃ Build ·`` row above the cursor composer does not take
|
|
2646
|
+
the pane.
|
|
2484
2647
|
"""
|
|
2485
2648
|
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2486
2649
|
for i in range(len(tail) - 1, -1, -1):
|
|
@@ -2490,12 +2653,15 @@ def _bottom_tui(lines: list[str]) -> "str | None":
|
|
|
2490
2653
|
return "grok"
|
|
2491
2654
|
if _is_opencode_composer_row(tail[i]):
|
|
2492
2655
|
return "opencode"
|
|
2656
|
+
if _is_muse_composer_row(tail, i):
|
|
2657
|
+
return "muse"
|
|
2493
2658
|
# A permission dialog is taller than the composer tail. Look further
|
|
2494
2659
|
# for that one row. A mode row or a cursor/grok composer in the same
|
|
2495
2660
|
# stretch is a quote, not a reason to take the pane.
|
|
2496
2661
|
wider = _nonempty_tail(lines, _OPENCODE_PERMISSION_TAIL)
|
|
2497
2662
|
for i in range(len(wider) - 1, -1, -1):
|
|
2498
|
-
if _is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
|
|
2663
|
+
if (_is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
|
|
2664
|
+
or _is_muse_composer_row(wider, i)):
|
|
2499
2665
|
return None
|
|
2500
2666
|
if not _is_opencode_composer_row(wider[i]):
|
|
2501
2667
|
continue
|
|
@@ -2645,14 +2811,74 @@ def _grok_composer_state(lines: list[str]) -> Optional[str]:
|
|
|
2645
2811
|
return "busy"
|
|
2646
2812
|
|
|
2647
2813
|
|
|
2814
|
+
def _is_muse_pane(lines: list[str]) -> bool:
|
|
2815
|
+
"""Muse 1.4.1: the ``❯`` row between two rules is the bottom composer."""
|
|
2816
|
+
return bool(lines) and _bottom_tui(lines) == "muse"
|
|
2817
|
+
|
|
2818
|
+
|
|
2819
|
+
def _muse_input(lines: list[str]) -> Optional[str]:
|
|
2820
|
+
"""Text after the live ``❯``, '' when that row is empty, None if the
|
|
2821
|
+
bottom composer is not muse. A transcript ``❯`` above the rules is not
|
|
2822
|
+
this row."""
|
|
2823
|
+
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2824
|
+
for i in range(len(tail) - 1, -1, -1):
|
|
2825
|
+
if (_is_cursor_composer_row(tail[i]) or _is_grok_composer_row(tail, i)
|
|
2826
|
+
or _is_opencode_composer_row(tail[i])):
|
|
2827
|
+
return None
|
|
2828
|
+
if _is_muse_composer_row(tail, i):
|
|
2829
|
+
return tail[i].strip()[1:].strip()
|
|
2830
|
+
return None
|
|
2831
|
+
|
|
2832
|
+
|
|
2833
|
+
def _muse_composer_state(lines: list[str]) -> Optional[str]:
|
|
2834
|
+
text = _muse_input(lines)
|
|
2835
|
+
if text is None:
|
|
2836
|
+
return None
|
|
2837
|
+
if not text:
|
|
2838
|
+
return "clear"
|
|
2839
|
+
if _only_wake_text(text):
|
|
2840
|
+
return "wake"
|
|
2841
|
+
return "busy"
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
def _muse_running(lines: list[str]) -> bool:
|
|
2845
|
+
"""A muse turn in progress. 1.4.1 draws ``esc to interrupt`` on the
|
|
2846
|
+
status row above the composer (``Double checking (Ns · esc to interrupt)``).
|
|
2847
|
+
The card also names a ``Thinking`` row; either one means a keystroke
|
|
2848
|
+
would land in the turn, not the idle composer. The phrase leaves the
|
|
2849
|
+
visible screen when the turn ends."""
|
|
2850
|
+
if not _is_muse_pane(lines):
|
|
2851
|
+
return False
|
|
2852
|
+
for ln in lines:
|
|
2853
|
+
s = ln.strip()
|
|
2854
|
+
if "esc to interrupt" in s or s == "Thinking" or s.startswith("Thinking "):
|
|
2855
|
+
return True
|
|
2856
|
+
return False
|
|
2857
|
+
|
|
2858
|
+
|
|
2648
2859
|
def _wake_prompt_for(lines: Optional[list[str]]) -> str:
|
|
2649
2860
|
if lines and _is_grok_pane(lines):
|
|
2650
2861
|
return _GROK_WAKE_PROMPT
|
|
2651
2862
|
if lines and _is_opencode_pane(lines):
|
|
2652
2863
|
return _OPENCODE_WAKE_PROMPT
|
|
2864
|
+
if lines and _is_muse_pane(lines):
|
|
2865
|
+
return _MUSE_WAKE_PROMPT
|
|
2653
2866
|
return _WAKE_PROMPT
|
|
2654
2867
|
|
|
2655
2868
|
|
|
2869
|
+
def _sink_failure_detail(target: str) -> str:
|
|
2870
|
+
"""Why a delivery returned False.
|
|
2871
|
+
|
|
2872
|
+
A captured pane with no known composer is an unrecognised TUI. A missing
|
|
2873
|
+
pane stays the old 'probably gone' line. Callers print this phrase as
|
|
2874
|
+
the failure line.
|
|
2875
|
+
"""
|
|
2876
|
+
lines = _capture_pane_lines(target)
|
|
2877
|
+
if lines and _bottom_tui(lines) is None and _composer_line(lines) is None:
|
|
2878
|
+
return "unrecognised TUI"
|
|
2879
|
+
return "the sink is probably gone (session restart / renamed target)"
|
|
2880
|
+
|
|
2881
|
+
|
|
2656
2882
|
def _agent_running(target: str) -> Optional[bool]:
|
|
2657
2883
|
"""True while a keystroke would miss the idle composer.
|
|
2658
2884
|
|
|
@@ -2673,6 +2899,8 @@ def _agent_running(target: str) -> Optional[bool]:
|
|
|
2673
2899
|
return _opencode_running(lines)
|
|
2674
2900
|
if _is_grok_pane(lines):
|
|
2675
2901
|
return _grok_running(lines)
|
|
2902
|
+
if _is_muse_pane(lines):
|
|
2903
|
+
return _muse_running(lines)
|
|
2676
2904
|
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2677
2905
|
composer = _composer_line(tail)
|
|
2678
2906
|
if composer is None or not composer.startswith("→"):
|
|
@@ -2721,6 +2949,8 @@ def _composer_state(target: str) -> Optional[str]:
|
|
|
2721
2949
|
return _opencode_composer_state(lines)
|
|
2722
2950
|
if _is_grok_pane(lines):
|
|
2723
2951
|
return _grok_composer_state(lines)
|
|
2952
|
+
if _is_muse_pane(lines):
|
|
2953
|
+
return _muse_composer_state(lines)
|
|
2724
2954
|
composer = _composer_line(lines)
|
|
2725
2955
|
if composer is not None and composer.startswith("→"):
|
|
2726
2956
|
# A cursor marker above the bottom tail is a quoted row. The
|
|
@@ -3125,10 +3355,12 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
3125
3355
|
else:
|
|
3126
3356
|
# A dead sink is VISIBLE instead of silently freezing anything.
|
|
3127
3357
|
led["consecutive_failures"] = int(led["consecutive_failures"]) + 1
|
|
3358
|
+
detail = getattr(sink, "failure_reason", None) or (
|
|
3359
|
+
"the sink is probably gone (session restart / renamed target)"
|
|
3360
|
+
)
|
|
3128
3361
|
print(f"{state.log_prefix} DELIVERY FAILED to {sink.name} "
|
|
3129
3362
|
f"({led['consecutive_failures']} consecutive) -- DMs up to "
|
|
3130
|
-
f"id {observed} were still observed and archived;
|
|
3131
|
-
f"is probably gone (session restart / renamed target)",
|
|
3363
|
+
f"id {observed} were still observed and archived; {detail}",
|
|
3132
3364
|
file=sys.stderr, flush=True)
|
|
3133
3365
|
changed = True
|
|
3134
3366
|
|
|
@@ -64,7 +64,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
64
64
|
default=[],
|
|
65
65
|
metavar="SINK",
|
|
66
66
|
help="repeatable; pull (default) | tmux:<target> | tmux-notify:<target> "
|
|
67
|
-
"| stdout | none | cursor-print:<cell> | muse:<session>. "
|
|
67
|
+
"| stdout | none | cursor-print:<cell> | muse:<session> | codex:<thread>. "
|
|
68
68
|
"Each sink gets its OWN delivery ledger.",
|
|
69
69
|
)
|
|
70
70
|
start.add_argument("--poll-s", type=int, default=mesh._DEFAULT_POLL_S)
|
|
@@ -64,7 +64,7 @@ _BANNER = """\
|
|
|
64
64
|
_USAGE = """\
|
|
65
65
|
Usage:
|
|
66
66
|
swarph spawn [<role-or-path>] [--onboarding PATH-OR-URL]
|
|
67
|
-
[--dry-run] [--no-starter] [--print-id]
|
|
67
|
+
[--dry-run] [--no-starter] [--print-id] [--print-resolved]
|
|
68
68
|
[-- provider-extra-args...]
|
|
69
69
|
|
|
70
70
|
Resolution (first match wins):
|
|
@@ -147,6 +147,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
147
147
|
action="store_true",
|
|
148
148
|
help="Print resolved session-id to stdout before exec.",
|
|
149
149
|
)
|
|
150
|
+
p.add_argument(
|
|
151
|
+
"--print-resolved",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help="Print the resolved provider argv + swarph-added env as JSON "
|
|
154
|
+
"and exit without exec (channel validation runs first; an invalid "
|
|
155
|
+
"channel exits non-zero naming the field). Named --print-resolved, "
|
|
156
|
+
"not --print, because argparse prefix-matching would route a bare "
|
|
157
|
+
"--print to --print-id and proceed to a REAL launch.",
|
|
158
|
+
)
|
|
150
159
|
p.add_argument(
|
|
151
160
|
"--new-instance",
|
|
152
161
|
action="store_true",
|
|
@@ -377,6 +386,126 @@ def _set_live_pin_safe(role: str) -> None:
|
|
|
377
386
|
file=sys.stderr)
|
|
378
387
|
|
|
379
388
|
|
|
389
|
+
#: cell.yaml `channel:` values valid per provider. "off"/absent means no
|
|
390
|
+
#: channel for every provider. Anything else on an unlisted provider fails
|
|
391
|
+
#: closed (a channel the provider cannot consume would start a deaf cell).
|
|
392
|
+
_CLAUDE_CHANNELS = frozenset({"allowlisted", "dev"})
|
|
393
|
+
_MUSE_CHANNELS = frozenset({"ingress"})
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# Providers whose spawn consumes $SWARPH_CHANNEL. Anything else ignores an
|
|
397
|
+
# inherited value (a Claude cell's shell carries allowlisted; spawning grok
|
|
398
|
+
# from it must not refuse). Measured 2026-10-01: 0.72.0 blamed cell.yaml
|
|
399
|
+
# for that env and exited on grok-researcher.yaml, which has no channel field.
|
|
400
|
+
_ENV_CHANNEL_PROVIDERS = frozenset({"claude", "muse"})
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _channel_where(source: str) -> str:
|
|
404
|
+
"""Name the place the mode came from. An env value must not read as cell.yaml."""
|
|
405
|
+
if source == "env":
|
|
406
|
+
return "SWARPH_CHANNEL"
|
|
407
|
+
return "cell.yaml: 'channel'"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _field_channel(cell: Cell) -> str:
|
|
411
|
+
extra = getattr(cell, "extra", None) or {}
|
|
412
|
+
value = extra.get("channel", "") if isinstance(extra, dict) else ""
|
|
413
|
+
if value is None:
|
|
414
|
+
return ""
|
|
415
|
+
if not isinstance(value, str):
|
|
416
|
+
raise CellError(
|
|
417
|
+
f"cell.yaml: 'channel' must be a string; got "
|
|
418
|
+
f"{type(value).__name__}."
|
|
419
|
+
)
|
|
420
|
+
return value
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _resolve_channel(cell: Cell) -> tuple[str, str]:
|
|
424
|
+
"""Effective channel mode and where it came from.
|
|
425
|
+
|
|
426
|
+
``$SWARPH_CHANNEL`` wins (incl. "off") only for providers that support
|
|
427
|
+
it: claude (allowlisted/dev/off) and muse (ingress/off). Other providers
|
|
428
|
+
ignore the inherited env, as before 0.72.0, and the cell.yaml field is
|
|
429
|
+
still read. Returns ``(mode, source)`` with source ``env``, ``cell.yaml``,
|
|
430
|
+
or ``""``. Never launches anything; callers validate.
|
|
431
|
+
"""
|
|
432
|
+
env_mode = os.environ.get("SWARPH_CHANNEL") or ""
|
|
433
|
+
provider = getattr(cell, "provider", None)
|
|
434
|
+
# No provider attribute: the stamp probes construct a name-only cell.
|
|
435
|
+
# Those are claude-shaped; the env still applies.
|
|
436
|
+
if env_mode and (provider is None or provider in _ENV_CHANNEL_PROVIDERS):
|
|
437
|
+
return env_mode, "env"
|
|
438
|
+
field = _field_channel(cell)
|
|
439
|
+
return field, ("cell.yaml" if field else "")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _channel_marketplace_dir(source: str = "cell.yaml") -> Path:
|
|
443
|
+
"""Install location of the `swarph` Claude marketplace (the allowlist).
|
|
444
|
+
|
|
445
|
+
Resolved through ~/.claude/plugins/known_marketplaces.json, the same
|
|
446
|
+
registry claude reads — never a second hardcoded copy that drifts.
|
|
447
|
+
``source`` names env vs cell.yaml so a missing registry is not blamed
|
|
448
|
+
on the file when the mode came from ``$SWARPH_CHANNEL``.
|
|
449
|
+
"""
|
|
450
|
+
where = _channel_where(source)
|
|
451
|
+
registry = Path.home() / ".claude" / "plugins" / "known_marketplaces.json"
|
|
452
|
+
try:
|
|
453
|
+
data = json.loads(registry.read_text(encoding="utf-8"))
|
|
454
|
+
except (OSError, ValueError) as exc:
|
|
455
|
+
raise CellError(
|
|
456
|
+
f"{where} 'allowlisted' needs the swarph marketplace, "
|
|
457
|
+
f"but its registry is unreadable: {registry} ({exc})."
|
|
458
|
+
) from exc
|
|
459
|
+
try:
|
|
460
|
+
location = data["swarph"]["installLocation"]
|
|
461
|
+
except (KeyError, TypeError) as exc:
|
|
462
|
+
raise CellError(
|
|
463
|
+
f"{where} 'allowlisted' needs the swarph marketplace, "
|
|
464
|
+
f"but it is not registered in {registry}."
|
|
465
|
+
) from exc
|
|
466
|
+
path = Path(location)
|
|
467
|
+
if not path.is_dir():
|
|
468
|
+
raise CellError(
|
|
469
|
+
f"{where} 'allowlisted' needs the swarph marketplace "
|
|
470
|
+
f"at {path}, which is not a directory."
|
|
471
|
+
)
|
|
472
|
+
return path
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _validate_channel(cell: Cell, mode: str, source: str = "cell.yaml") -> str:
|
|
476
|
+
"""Fail closed on any channel mode the provider cannot consume.
|
|
477
|
+
|
|
478
|
+
Returns the mode unchanged. "" and "off" are valid everywhere (no-op).
|
|
479
|
+
Raises CellError naming the source (``SWARPH_CHANNEL`` or the cell.yaml
|
|
480
|
+
field) and the file, for a missing marketplace, so the operator fixes
|
|
481
|
+
config, never debugs a deaf cell.
|
|
482
|
+
"""
|
|
483
|
+
if mode in ("", "off"):
|
|
484
|
+
return mode
|
|
485
|
+
where = _channel_where(source)
|
|
486
|
+
provider = cell.provider
|
|
487
|
+
if provider == "claude":
|
|
488
|
+
if mode not in _CLAUDE_CHANNELS:
|
|
489
|
+
raise CellError(
|
|
490
|
+
f"{where} {mode!r} is not valid for provider "
|
|
491
|
+
f"'claude'. Valid values: {sorted(_CLAUDE_CHANNELS | {'off'})}."
|
|
492
|
+
)
|
|
493
|
+
if mode == "allowlisted":
|
|
494
|
+
_channel_marketplace_dir(source)
|
|
495
|
+
return mode
|
|
496
|
+
if provider == "muse":
|
|
497
|
+
if mode not in _MUSE_CHANNELS:
|
|
498
|
+
raise CellError(
|
|
499
|
+
f"{where} {mode!r} is not valid for provider "
|
|
500
|
+
f"'muse'. Valid values: {sorted(_MUSE_CHANNELS | {'off'})}."
|
|
501
|
+
)
|
|
502
|
+
return mode
|
|
503
|
+
raise CellError(
|
|
504
|
+
f"{where} {mode!r} is only valid for providers "
|
|
505
|
+
f"'claude' and 'muse'; provider is {provider!r}."
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
380
509
|
def _build_claude_argv(
|
|
381
510
|
cell: Cell,
|
|
382
511
|
session_id: str,
|
|
@@ -400,7 +529,8 @@ def _build_claude_argv(
|
|
|
400
529
|
argv.extend(["--append-system-prompt", starter])
|
|
401
530
|
|
|
402
531
|
argv.extend(passthrough)
|
|
403
|
-
mode =
|
|
532
|
+
mode, source = _resolve_channel(cell)
|
|
533
|
+
mode = _validate_channel(cell, mode, source)
|
|
404
534
|
if mode == "allowlisted":
|
|
405
535
|
argv.extend(["--channels", "plugin:swarph@swarph"])
|
|
406
536
|
elif mode == "dev":
|
|
@@ -447,7 +577,10 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
|
|
|
447
577
|
env = scrub_env_for_subprocess()
|
|
448
578
|
env["SWARPH_SPAWN"] = "1"
|
|
449
579
|
env["SWARPH_SELF"] = cell.name
|
|
450
|
-
mode =
|
|
580
|
+
mode, _source = _resolve_channel(cell)
|
|
581
|
+
# NOTE: the marketplace/validity gate lives in _validate_channel, which
|
|
582
|
+
# _build_claude_argv (and the muse env builder) run. This base stays a
|
|
583
|
+
# pure reflection of the resolved mode so every provider's env agrees.
|
|
451
584
|
if mode in {"allowlisted", "dev"}:
|
|
452
585
|
env["SWARPH_CHANNEL_CELL"] = cell.name
|
|
453
586
|
else:
|
|
@@ -475,6 +608,23 @@ def _claude_env(cell: Cell) -> dict[str, str]:
|
|
|
475
608
|
return env
|
|
476
609
|
|
|
477
610
|
|
|
611
|
+
def _muse_env(cell: Cell) -> dict[str, str]:
|
|
612
|
+
"""Launch env for an interactive ``muse`` session.
|
|
613
|
+
|
|
614
|
+
Claude billing scrub + identity stamp (shared), plus the external-agent
|
|
615
|
+
ingress gate when the cell takes the `ingress` channel. The gate must be
|
|
616
|
+
in the environment AT SESSION LAUNCH — it cannot be enabled mid-session
|
|
617
|
+
(measured on card #961: list/send without it reads "external agent
|
|
618
|
+
ingress is unavailable"). `_validate_channel` already ran at the
|
|
619
|
+
run_spawn choke point; the mode read here agrees with it by construction.
|
|
620
|
+
"""
|
|
621
|
+
env = _claude_env(cell)
|
|
622
|
+
mode, _source = _resolve_channel(cell)
|
|
623
|
+
if mode == "ingress":
|
|
624
|
+
env["MUSE_EXPERIMENTAL_EXTERNAL_AGENT_INGRESS"] = "on"
|
|
625
|
+
return env
|
|
626
|
+
|
|
627
|
+
|
|
478
628
|
def _agy_env(cell: Cell) -> dict[str, str]:
|
|
479
629
|
"""Subscription-billing env for an ``agy`` (antigravity/Gemini) session.
|
|
480
630
|
|
|
@@ -2706,6 +2856,8 @@ class MuseMembrane(ClaudeMembrane):
|
|
|
2706
2856
|
|
|
2707
2857
|
name = "muse"
|
|
2708
2858
|
|
|
2859
|
+
env_builder = staticmethod(_muse_env)
|
|
2860
|
+
|
|
2709
2861
|
def apply_task_injection(self, cell: Cell, argv: list[str], text: str) -> None:
|
|
2710
2862
|
# muse has no --append-system-prompt; the prompt is POSITIONAL. Appended
|
|
2711
2863
|
# only when this is a FRESH session — `muse resume` takes no prompt, so
|
|
@@ -3184,6 +3336,17 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
|
|
|
3184
3336
|
was_generated = False
|
|
3185
3337
|
effective_role: Optional[str] = None
|
|
3186
3338
|
|
|
3339
|
+
# Channel gate BEFORE session minting: an invalid channel mode must fail
|
|
3340
|
+
# before load_or_create_session_id persists anything (a refused spawn
|
|
3341
|
+
# must not leave a minted sidecar behind). One choke point for every
|
|
3342
|
+
# provider; builders below see only validated modes.
|
|
3343
|
+
try:
|
|
3344
|
+
mode, source = _resolve_channel(cell)
|
|
3345
|
+
_validate_channel(cell, mode, source)
|
|
3346
|
+
except CellError as exc:
|
|
3347
|
+
print(f"swarph spawn: {exc}", file=sys.stderr)
|
|
3348
|
+
return 1
|
|
3349
|
+
|
|
3187
3350
|
if membrane.uses_pinned_session():
|
|
3188
3351
|
# When user typed a slot-role (e.g. `swarph spawn drop-on-meta-edge-2`)
|
|
3189
3352
|
# the cell.yaml resolved to the BASE file (drop-on-meta-edge.yaml) so
|
|
@@ -3265,6 +3428,18 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
|
|
|
3265
3428
|
)
|
|
3266
3429
|
return 0
|
|
3267
3430
|
|
|
3431
|
+
if args.print_resolved:
|
|
3432
|
+
# Resolved-launch capture: argv + the env keys swarph adds, as JSON
|
|
3433
|
+
# on stdout, then exit BEFORE resolve_binary — no binary needed, no
|
|
3434
|
+
# session started. Banner stays on stderr so stdout parses clean.
|
|
3435
|
+
# env_added is a DIFF against the parent env (not the full env) so
|
|
3436
|
+
# no secret the operator exported leaks into the capture.
|
|
3437
|
+
built_env = membrane.spawn_env(cell)
|
|
3438
|
+
added = {k: v for k, v in built_env.items()
|
|
3439
|
+
if os.environ.get(k) != v}
|
|
3440
|
+
print(json.dumps({"argv": spawn_argv, "env_added": added}))
|
|
3441
|
+
return 0
|
|
3442
|
+
|
|
3268
3443
|
provider_bin = membrane.resolve_binary()
|
|
3269
3444
|
if provider_bin is None:
|
|
3270
3445
|
print(membrane.binary_not_found_message(), file=sys.stderr)
|