swarph-cli 0.71.0__tar.gz → 0.72.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.71.0/src/swarph_cli.egg-info → swarph_cli-0.72.0}/PKG-INFO +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/pyproject.toml +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/mesh.py +229 -7
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/monitor.py +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/spawn.py +144 -3
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/stall_alert.py +11 -3
- {swarph_cli-0.71.0 → swarph_cli-0.72.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- swarph_cli-0.72.0/tests/test_1013_spawn_channel.py +177 -0
- swarph_cli-0.72.0/tests/test_1017_muse_tui.py +113 -0
- swarph_cli-0.72.0/tests/test_484_codex_queue_sink.py +149 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_960_channel.py +14 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_state_split.py +6 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_stall_alert.py +13 -2
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/LICENSE +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/README.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/setup.cfg +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_1006_board_publisher.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_main.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_security.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_version_command.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.71.0 → swarph_cli-0.72.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.72.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.72.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" }
|
|
@@ -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
|
|
|
@@ -2475,12 +2604,38 @@ def _is_cursor_composer_row(line: str) -> bool:
|
|
|
2475
2604
|
return row.startswith("→") and not row.startswith("▎")
|
|
2476
2605
|
|
|
2477
2606
|
|
|
2607
|
+
def _is_muse_rule(line: str) -> bool:
|
|
2608
|
+
"""A muse composer rule is a run of ``─``, measured on 1.4.1."""
|
|
2609
|
+
s = line.strip()
|
|
2610
|
+
return len(s) >= 8 and "─" in s and set(s) <= {"─"}
|
|
2611
|
+
|
|
2612
|
+
|
|
2613
|
+
def _is_muse_status(line: str) -> bool:
|
|
2614
|
+
"""The status under the lower rule. Measured: ``muse-spark-… · … · YOLO``."""
|
|
2615
|
+
return "muse-spark" in line and "·" in line
|
|
2616
|
+
|
|
2617
|
+
|
|
2618
|
+
def _is_muse_composer_row(lines: list[str], index: int) -> bool:
|
|
2619
|
+
"""The live muse composer is a ``❯`` row between two rules, with the
|
|
2620
|
+
model status under the lower rule. A transcript ``❯`` above that box
|
|
2621
|
+
has no rule on both sides, so it is history."""
|
|
2622
|
+
row = lines[index].strip()
|
|
2623
|
+
if not row.startswith("❯"):
|
|
2624
|
+
return False
|
|
2625
|
+
if index == 0 or index + 1 >= len(lines):
|
|
2626
|
+
return False
|
|
2627
|
+
if not (_is_muse_rule(lines[index - 1]) and _is_muse_rule(lines[index + 1])):
|
|
2628
|
+
return False
|
|
2629
|
+
return any(_is_muse_status(ln) for ln in lines[index + 2:index + 6])
|
|
2630
|
+
|
|
2631
|
+
|
|
2478
2632
|
def _bottom_tui(lines: list[str]) -> "str | None":
|
|
2479
2633
|
"""Who owns the bottom-most composer row in the non-empty tail.
|
|
2480
2634
|
|
|
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
|
|
2635
|
+
``"cursor"``, ``"grok"``, ``"opencode"``, or ``"muse"``. A match above
|
|
2636
|
+
that tail is scrollback. The lower composer wins, so a quoted grok box
|
|
2637
|
+
or a quoted ``┃ Build ·`` row above the cursor composer does not take
|
|
2638
|
+
the pane.
|
|
2484
2639
|
"""
|
|
2485
2640
|
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2486
2641
|
for i in range(len(tail) - 1, -1, -1):
|
|
@@ -2490,12 +2645,15 @@ def _bottom_tui(lines: list[str]) -> "str | None":
|
|
|
2490
2645
|
return "grok"
|
|
2491
2646
|
if _is_opencode_composer_row(tail[i]):
|
|
2492
2647
|
return "opencode"
|
|
2648
|
+
if _is_muse_composer_row(tail, i):
|
|
2649
|
+
return "muse"
|
|
2493
2650
|
# A permission dialog is taller than the composer tail. Look further
|
|
2494
2651
|
# for that one row. A mode row or a cursor/grok composer in the same
|
|
2495
2652
|
# stretch is a quote, not a reason to take the pane.
|
|
2496
2653
|
wider = _nonempty_tail(lines, _OPENCODE_PERMISSION_TAIL)
|
|
2497
2654
|
for i in range(len(wider) - 1, -1, -1):
|
|
2498
|
-
if _is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
|
|
2655
|
+
if (_is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
|
|
2656
|
+
or _is_muse_composer_row(wider, i)):
|
|
2499
2657
|
return None
|
|
2500
2658
|
if not _is_opencode_composer_row(wider[i]):
|
|
2501
2659
|
continue
|
|
@@ -2645,6 +2803,51 @@ def _grok_composer_state(lines: list[str]) -> Optional[str]:
|
|
|
2645
2803
|
return "busy"
|
|
2646
2804
|
|
|
2647
2805
|
|
|
2806
|
+
def _is_muse_pane(lines: list[str]) -> bool:
|
|
2807
|
+
"""Muse 1.4.1: the ``❯`` row between two rules is the bottom composer."""
|
|
2808
|
+
return bool(lines) and _bottom_tui(lines) == "muse"
|
|
2809
|
+
|
|
2810
|
+
|
|
2811
|
+
def _muse_input(lines: list[str]) -> Optional[str]:
|
|
2812
|
+
"""Text after the live ``❯``, '' when that row is empty, None if the
|
|
2813
|
+
bottom composer is not muse. A transcript ``❯`` above the rules is not
|
|
2814
|
+
this row."""
|
|
2815
|
+
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2816
|
+
for i in range(len(tail) - 1, -1, -1):
|
|
2817
|
+
if (_is_cursor_composer_row(tail[i]) or _is_grok_composer_row(tail, i)
|
|
2818
|
+
or _is_opencode_composer_row(tail[i])):
|
|
2819
|
+
return None
|
|
2820
|
+
if _is_muse_composer_row(tail, i):
|
|
2821
|
+
return tail[i].strip()[1:].strip()
|
|
2822
|
+
return None
|
|
2823
|
+
|
|
2824
|
+
|
|
2825
|
+
def _muse_composer_state(lines: list[str]) -> Optional[str]:
|
|
2826
|
+
text = _muse_input(lines)
|
|
2827
|
+
if text is None:
|
|
2828
|
+
return None
|
|
2829
|
+
if not text:
|
|
2830
|
+
return "clear"
|
|
2831
|
+
if _only_wake_text(text):
|
|
2832
|
+
return "wake"
|
|
2833
|
+
return "busy"
|
|
2834
|
+
|
|
2835
|
+
|
|
2836
|
+
def _muse_running(lines: list[str]) -> bool:
|
|
2837
|
+
"""A muse turn in progress. 1.4.1 draws ``esc to interrupt`` on the
|
|
2838
|
+
status row above the composer (``Double checking (Ns · esc to interrupt)``).
|
|
2839
|
+
The card also names a ``Thinking`` row; either one means a keystroke
|
|
2840
|
+
would land in the turn, not the idle composer. The phrase leaves the
|
|
2841
|
+
visible screen when the turn ends."""
|
|
2842
|
+
if not _is_muse_pane(lines):
|
|
2843
|
+
return False
|
|
2844
|
+
for ln in lines:
|
|
2845
|
+
s = ln.strip()
|
|
2846
|
+
if "esc to interrupt" in s or s == "Thinking" or s.startswith("Thinking "):
|
|
2847
|
+
return True
|
|
2848
|
+
return False
|
|
2849
|
+
|
|
2850
|
+
|
|
2648
2851
|
def _wake_prompt_for(lines: Optional[list[str]]) -> str:
|
|
2649
2852
|
if lines and _is_grok_pane(lines):
|
|
2650
2853
|
return _GROK_WAKE_PROMPT
|
|
@@ -2653,6 +2856,19 @@ def _wake_prompt_for(lines: Optional[list[str]]) -> str:
|
|
|
2653
2856
|
return _WAKE_PROMPT
|
|
2654
2857
|
|
|
2655
2858
|
|
|
2859
|
+
def _sink_failure_detail(target: str) -> str:
|
|
2860
|
+
"""Why a delivery returned False.
|
|
2861
|
+
|
|
2862
|
+
A captured pane with no known composer is an unrecognised TUI. A missing
|
|
2863
|
+
pane stays the old 'probably gone' line. Callers print this phrase as
|
|
2864
|
+
the failure line.
|
|
2865
|
+
"""
|
|
2866
|
+
lines = _capture_pane_lines(target)
|
|
2867
|
+
if lines and _bottom_tui(lines) is None and _composer_line(lines) is None:
|
|
2868
|
+
return "unrecognised TUI"
|
|
2869
|
+
return "the sink is probably gone (session restart / renamed target)"
|
|
2870
|
+
|
|
2871
|
+
|
|
2656
2872
|
def _agent_running(target: str) -> Optional[bool]:
|
|
2657
2873
|
"""True while a keystroke would miss the idle composer.
|
|
2658
2874
|
|
|
@@ -2673,6 +2889,8 @@ def _agent_running(target: str) -> Optional[bool]:
|
|
|
2673
2889
|
return _opencode_running(lines)
|
|
2674
2890
|
if _is_grok_pane(lines):
|
|
2675
2891
|
return _grok_running(lines)
|
|
2892
|
+
if _is_muse_pane(lines):
|
|
2893
|
+
return _muse_running(lines)
|
|
2676
2894
|
tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
|
|
2677
2895
|
composer = _composer_line(tail)
|
|
2678
2896
|
if composer is None or not composer.startswith("→"):
|
|
@@ -2721,6 +2939,8 @@ def _composer_state(target: str) -> Optional[str]:
|
|
|
2721
2939
|
return _opencode_composer_state(lines)
|
|
2722
2940
|
if _is_grok_pane(lines):
|
|
2723
2941
|
return _grok_composer_state(lines)
|
|
2942
|
+
if _is_muse_pane(lines):
|
|
2943
|
+
return _muse_composer_state(lines)
|
|
2724
2944
|
composer = _composer_line(lines)
|
|
2725
2945
|
if composer is not None and composer.startswith("→"):
|
|
2726
2946
|
# A cursor marker above the bottom tail is a quoted row. The
|
|
@@ -3125,10 +3345,12 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
3125
3345
|
else:
|
|
3126
3346
|
# A dead sink is VISIBLE instead of silently freezing anything.
|
|
3127
3347
|
led["consecutive_failures"] = int(led["consecutive_failures"]) + 1
|
|
3348
|
+
detail = getattr(sink, "failure_reason", None) or (
|
|
3349
|
+
"the sink is probably gone (session restart / renamed target)"
|
|
3350
|
+
)
|
|
3128
3351
|
print(f"{state.log_prefix} DELIVERY FAILED to {sink.name} "
|
|
3129
3352
|
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)",
|
|
3353
|
+
f"id {observed} were still observed and archived; {detail}",
|
|
3132
3354
|
file=sys.stderr, flush=True)
|
|
3133
3355
|
changed = True
|
|
3134
3356
|
|
|
@@ -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,95 @@ 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
|
+
def _resolve_channel(cell: Cell) -> str:
|
|
397
|
+
"""Effective channel mode: $SWARPH_CHANNEL wins (incl. "off"), else the
|
|
398
|
+
cell.yaml `channel` field (kept by swarph-shared in the forward-compat
|
|
399
|
+
`extra` bag), else "" (absent). Never launches anything; callers validate.
|
|
400
|
+
"""
|
|
401
|
+
env_mode = os.environ.get("SWARPH_CHANNEL") or ""
|
|
402
|
+
if env_mode:
|
|
403
|
+
return env_mode
|
|
404
|
+
extra = getattr(cell, "extra", None) or {}
|
|
405
|
+
value = extra.get("channel", "") if isinstance(extra, dict) else ""
|
|
406
|
+
if value is None:
|
|
407
|
+
return ""
|
|
408
|
+
if not isinstance(value, str):
|
|
409
|
+
raise CellError(
|
|
410
|
+
f"cell.yaml: 'channel' must be a string; got "
|
|
411
|
+
f"{type(value).__name__}."
|
|
412
|
+
)
|
|
413
|
+
return value
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _channel_marketplace_dir() -> Path:
|
|
417
|
+
"""Install location of the `swarph` Claude marketplace (the allowlist).
|
|
418
|
+
|
|
419
|
+
Resolved through ~/.claude/plugins/known_marketplaces.json, the same
|
|
420
|
+
registry claude reads — never a second hardcoded copy that drifts.
|
|
421
|
+
"""
|
|
422
|
+
registry = Path.home() / ".claude" / "plugins" / "known_marketplaces.json"
|
|
423
|
+
try:
|
|
424
|
+
data = json.loads(registry.read_text(encoding="utf-8"))
|
|
425
|
+
except (OSError, ValueError) as exc:
|
|
426
|
+
raise CellError(
|
|
427
|
+
f"cell.yaml: channel 'allowlisted' needs the swarph marketplace, "
|
|
428
|
+
f"but its registry is unreadable: {registry} ({exc})."
|
|
429
|
+
) from exc
|
|
430
|
+
try:
|
|
431
|
+
location = data["swarph"]["installLocation"]
|
|
432
|
+
except (KeyError, TypeError) as exc:
|
|
433
|
+
raise CellError(
|
|
434
|
+
f"cell.yaml: channel 'allowlisted' needs the swarph marketplace, "
|
|
435
|
+
f"but it is not registered in {registry}."
|
|
436
|
+
) from exc
|
|
437
|
+
path = Path(location)
|
|
438
|
+
if not path.is_dir():
|
|
439
|
+
raise CellError(
|
|
440
|
+
f"cell.yaml: channel 'allowlisted' needs the swarph marketplace "
|
|
441
|
+
f"at {path}, which is not a directory."
|
|
442
|
+
)
|
|
443
|
+
return path
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _validate_channel(cell: Cell, mode: str) -> str:
|
|
447
|
+
"""Fail closed on any channel mode the provider cannot consume.
|
|
448
|
+
|
|
449
|
+
Returns the mode unchanged. "" and "off" are valid everywhere (no-op).
|
|
450
|
+
Raises CellError naming the field (and the file, for a missing
|
|
451
|
+
marketplace) so the operator fixes config, never debugs a deaf cell.
|
|
452
|
+
"""
|
|
453
|
+
if mode in ("", "off"):
|
|
454
|
+
return mode
|
|
455
|
+
provider = cell.provider
|
|
456
|
+
if provider == "claude":
|
|
457
|
+
if mode not in _CLAUDE_CHANNELS:
|
|
458
|
+
raise CellError(
|
|
459
|
+
f"cell.yaml: 'channel' {mode!r} is not valid for provider "
|
|
460
|
+
f"'claude'. Valid values: {sorted(_CLAUDE_CHANNELS | {'off'})}."
|
|
461
|
+
)
|
|
462
|
+
if mode == "allowlisted":
|
|
463
|
+
_channel_marketplace_dir()
|
|
464
|
+
return mode
|
|
465
|
+
if provider == "muse":
|
|
466
|
+
if mode not in _MUSE_CHANNELS:
|
|
467
|
+
raise CellError(
|
|
468
|
+
f"cell.yaml: 'channel' {mode!r} is not valid for provider "
|
|
469
|
+
f"'muse'. Valid values: {sorted(_MUSE_CHANNELS | {'off'})}."
|
|
470
|
+
)
|
|
471
|
+
return mode
|
|
472
|
+
raise CellError(
|
|
473
|
+
f"cell.yaml: 'channel' {mode!r} is only valid for providers "
|
|
474
|
+
f"'claude' and 'muse'; provider is {provider!r}."
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
380
478
|
def _build_claude_argv(
|
|
381
479
|
cell: Cell,
|
|
382
480
|
session_id: str,
|
|
@@ -400,7 +498,7 @@ def _build_claude_argv(
|
|
|
400
498
|
argv.extend(["--append-system-prompt", starter])
|
|
401
499
|
|
|
402
500
|
argv.extend(passthrough)
|
|
403
|
-
mode =
|
|
501
|
+
mode = _validate_channel(cell, _resolve_channel(cell))
|
|
404
502
|
if mode == "allowlisted":
|
|
405
503
|
argv.extend(["--channels", "plugin:swarph@swarph"])
|
|
406
504
|
elif mode == "dev":
|
|
@@ -447,7 +545,10 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
|
|
|
447
545
|
env = scrub_env_for_subprocess()
|
|
448
546
|
env["SWARPH_SPAWN"] = "1"
|
|
449
547
|
env["SWARPH_SELF"] = cell.name
|
|
450
|
-
mode =
|
|
548
|
+
mode = _resolve_channel(cell)
|
|
549
|
+
# NOTE: the marketplace/validity gate lives in _validate_channel, which
|
|
550
|
+
# _build_claude_argv (and the muse env builder) run. This base stays a
|
|
551
|
+
# pure reflection of the resolved mode so every provider's env agrees.
|
|
451
552
|
if mode in {"allowlisted", "dev"}:
|
|
452
553
|
env["SWARPH_CHANNEL_CELL"] = cell.name
|
|
453
554
|
else:
|
|
@@ -475,6 +576,22 @@ def _claude_env(cell: Cell) -> dict[str, str]:
|
|
|
475
576
|
return env
|
|
476
577
|
|
|
477
578
|
|
|
579
|
+
def _muse_env(cell: Cell) -> dict[str, str]:
|
|
580
|
+
"""Launch env for an interactive ``muse`` session.
|
|
581
|
+
|
|
582
|
+
Claude billing scrub + identity stamp (shared), plus the external-agent
|
|
583
|
+
ingress gate when the cell takes the `ingress` channel. The gate must be
|
|
584
|
+
in the environment AT SESSION LAUNCH — it cannot be enabled mid-session
|
|
585
|
+
(measured on card #961: list/send without it reads "external agent
|
|
586
|
+
ingress is unavailable"). `_validate_channel` already ran at the
|
|
587
|
+
run_spawn choke point; the mode read here agrees with it by construction.
|
|
588
|
+
"""
|
|
589
|
+
env = _claude_env(cell)
|
|
590
|
+
if _resolve_channel(cell) == "ingress":
|
|
591
|
+
env["MUSE_EXPERIMENTAL_EXTERNAL_AGENT_INGRESS"] = "on"
|
|
592
|
+
return env
|
|
593
|
+
|
|
594
|
+
|
|
478
595
|
def _agy_env(cell: Cell) -> dict[str, str]:
|
|
479
596
|
"""Subscription-billing env for an ``agy`` (antigravity/Gemini) session.
|
|
480
597
|
|
|
@@ -2706,6 +2823,8 @@ class MuseMembrane(ClaudeMembrane):
|
|
|
2706
2823
|
|
|
2707
2824
|
name = "muse"
|
|
2708
2825
|
|
|
2826
|
+
env_builder = staticmethod(_muse_env)
|
|
2827
|
+
|
|
2709
2828
|
def apply_task_injection(self, cell: Cell, argv: list[str], text: str) -> None:
|
|
2710
2829
|
# muse has no --append-system-prompt; the prompt is POSITIONAL. Appended
|
|
2711
2830
|
# only when this is a FRESH session — `muse resume` takes no prompt, so
|
|
@@ -3184,6 +3303,16 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
|
|
|
3184
3303
|
was_generated = False
|
|
3185
3304
|
effective_role: Optional[str] = None
|
|
3186
3305
|
|
|
3306
|
+
# Channel gate BEFORE session minting: an invalid channel mode must fail
|
|
3307
|
+
# before load_or_create_session_id persists anything (a refused spawn
|
|
3308
|
+
# must not leave a minted sidecar behind). One choke point for every
|
|
3309
|
+
# provider; builders below see only validated modes.
|
|
3310
|
+
try:
|
|
3311
|
+
_validate_channel(cell, _resolve_channel(cell))
|
|
3312
|
+
except CellError as exc:
|
|
3313
|
+
print(f"swarph spawn: {exc}", file=sys.stderr)
|
|
3314
|
+
return 1
|
|
3315
|
+
|
|
3187
3316
|
if membrane.uses_pinned_session():
|
|
3188
3317
|
# When user typed a slot-role (e.g. `swarph spawn drop-on-meta-edge-2`)
|
|
3189
3318
|
# the cell.yaml resolved to the BASE file (drop-on-meta-edge.yaml) so
|
|
@@ -3265,6 +3394,18 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
|
|
|
3265
3394
|
)
|
|
3266
3395
|
return 0
|
|
3267
3396
|
|
|
3397
|
+
if args.print_resolved:
|
|
3398
|
+
# Resolved-launch capture: argv + the env keys swarph adds, as JSON
|
|
3399
|
+
# on stdout, then exit BEFORE resolve_binary — no binary needed, no
|
|
3400
|
+
# session started. Banner stays on stderr so stdout parses clean.
|
|
3401
|
+
# env_added is a DIFF against the parent env (not the full env) so
|
|
3402
|
+
# no secret the operator exported leaks into the capture.
|
|
3403
|
+
built_env = membrane.spawn_env(cell)
|
|
3404
|
+
added = {k: v for k, v in built_env.items()
|
|
3405
|
+
if os.environ.get(k) != v}
|
|
3406
|
+
print(json.dumps({"argv": spawn_argv, "env_added": added}))
|
|
3407
|
+
return 0
|
|
3408
|
+
|
|
3268
3409
|
provider_bin = membrane.resolve_binary()
|
|
3269
3410
|
if provider_bin is None:
|
|
3270
3411
|
print(membrane.binary_not_found_message(), file=sys.stderr)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""stall_alert — surface a cell whose live session is perpetually busy so its
|
|
2
|
-
undelivered DMs don't rot silently. Exponential backoff (
|
|
2
|
+
undelivered DMs don't rot silently. Exponential backoff (60,120,240,480…) prevents
|
|
3
3
|
the linear-flood failure (the 145-DM incident, feedback_modal_stalls_cell_wake).
|
|
4
|
+
Measured 2026-09-30/10-01: 7 of 7 stall rows fired on busy builder cells that
|
|
5
|
+
drained on their own within 7-25 min. The first alert waits 60 ticks (30 min
|
|
6
|
+
at the 30s poll) so a long build is not paged at 3 min.
|
|
4
7
|
|
|
5
8
|
Card #989: the surface is one obligation held by lab-ovh, never a DM to
|
|
6
9
|
commander. A second stall tick while that row is open opens nothing new. A
|
|
@@ -15,7 +18,7 @@ import urllib.parse
|
|
|
15
18
|
import urllib.request
|
|
16
19
|
from swarph_cli.console_safe import print_safe
|
|
17
20
|
|
|
18
|
-
_STALL_FIRST =
|
|
21
|
+
_STALL_FIRST = 60 # first alert after this many consecutive deferred ticks
|
|
19
22
|
_CARD = 989
|
|
20
23
|
_HOLDER = "lab-ovh"
|
|
21
24
|
# lab-ovh's own stall cannot escalate to lab-ovh. Another orchestrator holds it.
|
|
@@ -25,7 +28,12 @@ _PREFIX = f"STALL card #{_CARD}"
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
def is_alert_tick(deferred_ticks: int) -> bool:
|
|
28
|
-
"""True exactly at
|
|
31
|
+
"""True exactly at 60, 120, 240, 480 … — first at _STALL_FIRST, doubling.
|
|
32
|
+
|
|
33
|
+
Measured 2026-09-30/10-01: 7 of 7 stall rows fired on busy builder cells
|
|
34
|
+
that drained on their own within 7-25 min. At 6 ticks (3 min on a 30s
|
|
35
|
+
poll) every long build tripped the alert.
|
|
36
|
+
"""
|
|
29
37
|
if deferred_ticks < _STALL_FIRST or deferred_ticks % _STALL_FIRST != 0:
|
|
30
38
|
return False
|
|
31
39
|
k = deferred_ticks // _STALL_FIRST
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.72.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
|
|
@@ -187,6 +187,8 @@ src/swarph_cli/systemd/swarph-watchdog.service
|
|
|
187
187
|
src/swarph_cli/systemd/swarph-watchdog.timer
|
|
188
188
|
tests/test_1006_board_publisher.py
|
|
189
189
|
tests/test_1006_session_board.py
|
|
190
|
+
tests/test_1013_spawn_channel.py
|
|
191
|
+
tests/test_1017_muse_tui.py
|
|
190
192
|
tests/test_124_register_merge.py
|
|
191
193
|
tests/test_126_pending_from_complete.py
|
|
192
194
|
tests/test_132_ensure_monitor_state_dir.py
|
|
@@ -207,6 +209,7 @@ tests/test_397_router_preflight.py
|
|
|
207
209
|
tests/test_402_one_identity_resolver.py
|
|
208
210
|
tests/test_467b_onboard_operator_credential.py
|
|
209
211
|
tests/test_472_register_caller_binding.py
|
|
212
|
+
tests/test_484_codex_queue_sink.py
|
|
210
213
|
tests/test_523_guide.py
|
|
211
214
|
tests/test_524_conftest_hermeticity.py
|
|
212
215
|
tests/test_532_accept_flag.py
|