swarph-cli 0.69.0__tar.gz → 0.69.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swarph_cli-0.69.0/src/swarph_cli.egg-info → swarph_cli-0.69.2}/PKG-INFO +1 -1
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/pyproject.toml +1 -1
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/mesh.py +197 -11
- {swarph_cli-0.69.0 → swarph_cli-0.69.2/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar.py +1 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_runstate_defer.py +2 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_submit_verify.py +277 -4
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/LICENSE +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/README.md +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/setup.cfg +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_group_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_import_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_init_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_main.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_security.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_app.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_version_command.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_windows_waker_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.69.
|
|
3
|
+
Version: 0.69.2
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.69.
|
|
7
|
+
version = "0.69.2"
|
|
8
8
|
description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1474,6 +1474,11 @@ class TmuxSink(Sink):
|
|
|
1474
1474
|
# observation and let the fresh path take over — it defers while
|
|
1475
1475
|
# running (#619) and injects at the first idle poll.
|
|
1476
1476
|
if led.get("wake_outstanding") and _agent_running(self.target):
|
|
1477
|
+
# Opencode keeps the finished ▣ header on screen, so a deferral
|
|
1478
|
+
# during the turn must not clear the flag: the next idle poll
|
|
1479
|
+
# still owes a wake. Cursor's #620 clear stays.
|
|
1480
|
+
if _opencode_in_progress(self.target):
|
|
1481
|
+
return None
|
|
1477
1482
|
led["wake_outstanding"] = False
|
|
1478
1483
|
if led.get("wake_outstanding"):
|
|
1479
1484
|
# Lazy: watchdog imports mesh module-level, so the reverse must
|
|
@@ -1496,6 +1501,17 @@ class TmuxSink(Sink):
|
|
|
1496
1501
|
_tmux_enter(self.target) # one verified nudge, no new text
|
|
1497
1502
|
return True
|
|
1498
1503
|
if composer == "clear":
|
|
1504
|
+
# A finished opencode turn leaves a ▣ header with a
|
|
1505
|
+
# trailing duration and a clear box. The standing-wake
|
|
1506
|
+
# claim would swallow every later DM. Inject again.
|
|
1507
|
+
if _opencode_turn_finished_target(self.target):
|
|
1508
|
+
ok = _tmux_wake(self.target)
|
|
1509
|
+
if ok:
|
|
1510
|
+
_stamp_wake(led)
|
|
1511
|
+
return True
|
|
1512
|
+
if ok is None:
|
|
1513
|
+
return None
|
|
1514
|
+
return False
|
|
1499
1515
|
# #611: a clear composer proves the wake SUBMITTED only if
|
|
1500
1516
|
# the pane it was injected into is still the pane we are
|
|
1501
1517
|
# reading. The ledger flag survives a respawn; the wake
|
|
@@ -1979,6 +1995,19 @@ def _log_dm(state: MonitorState, dm: dict) -> None:
|
|
|
1979
1995
|
|
|
1980
1996
|
|
|
1981
1997
|
_WAKE_PROMPT = "check mesh"
|
|
1998
|
+
# Opencode has no push channel (#601). The wake is one line that names the
|
|
1999
|
+
# pull tool and carries no DM body, so the turn cannot answer mail it was
|
|
2000
|
+
# not shown. Longer than _WAKE_PROMPT, so wake-text detection strips this
|
|
2001
|
+
# one first.
|
|
2002
|
+
_OPENCODE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
|
|
2003
|
+
# A finished turn keeps its ▣ header and adds a duration.
|
|
2004
|
+
# Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
|
|
2005
|
+
# 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
|
|
2006
|
+
# 07:10Z). On a wide pane the same row continues into the sidebar, so the
|
|
2007
|
+
# duration is not the end of the line. An in-progress header has no duration.
|
|
2008
|
+
_OPENCODE_DONE = re.compile(
|
|
2009
|
+
r"·\s*(?:\d+h\s*)?(?:\d+m\s*)?\d+(?:\.\d+)?s\b"
|
|
2010
|
+
)
|
|
1982
2011
|
# Submit-verify bounds (#533): the settle pause lets the -l literal LAND in
|
|
1983
2012
|
# the composer before Enter can submit it (the blind gesture raced this), and
|
|
1984
2013
|
# the attempt bound keeps a never-submitting pane from being Enter-spammed
|
|
@@ -2072,20 +2101,168 @@ _COMPOSER_PLACEHOLDERS = ("Add a follow-up", "Ask Codex to do anything")
|
|
|
2072
2101
|
_CURSOR_RUN_HINT = "ctrl+c to stop"
|
|
2073
2102
|
|
|
2074
2103
|
|
|
2104
|
+
def _only_wake_text(content: str) -> bool:
|
|
2105
|
+
"""True when the composer holds wake text and nothing else.
|
|
2106
|
+
|
|
2107
|
+
The opencode prompt contains the cursor/claude prompt as a prefix, so
|
|
2108
|
+
the longer string is removed first. A stack of either prompt still
|
|
2109
|
+
counts as wake text (one Enter drains it). Anything left over is a
|
|
2110
|
+
human's line.
|
|
2111
|
+
"""
|
|
2112
|
+
rest = content
|
|
2113
|
+
for prompt in (_OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
|
|
2114
|
+
rest = rest.replace(prompt, "")
|
|
2115
|
+
return rest.strip() == ""
|
|
2116
|
+
|
|
2117
|
+
|
|
2118
|
+
def _opencode_mode_row(line: str) -> bool:
|
|
2119
|
+
"""The mode row under the input. ``--auto`` inserts a word, so the row
|
|
2120
|
+
is ``Build ·`` or ``Build auto ·`` (measured 1.18.33, both shapes).
|
|
2121
|
+
"""
|
|
2122
|
+
s = line.strip()
|
|
2123
|
+
if not s.startswith("┃") or "·" not in s:
|
|
2124
|
+
return False
|
|
2125
|
+
body = s[1:].strip()
|
|
2126
|
+
return body.startswith("Build") or body.startswith("Plan")
|
|
2127
|
+
|
|
2128
|
+
|
|
2129
|
+
def _opencode_permission_row(line: str) -> bool:
|
|
2130
|
+
"""The 1.18.33 dialog, drawn inside the box.
|
|
2131
|
+
|
|
2132
|
+
The row is ``┃ △ Permission required``. The mode row and the ``╹▀``
|
|
2133
|
+
border are gone on that screen, so this row is the whole signal.
|
|
2134
|
+
"""
|
|
2135
|
+
s = line.strip()
|
|
2136
|
+
return s.startswith("┃") and "△" in s and "Permission required" in s
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
def _is_opencode_pane(lines: list[str]) -> bool:
|
|
2140
|
+
"""The opencode composer box, measured on 1.18.33.
|
|
2141
|
+
|
|
2142
|
+
The input row is a ``┃`` line. The mode row under it names Build or
|
|
2143
|
+
Plan and a ``·``. The bottom of the box is ``╹▀``. The permission
|
|
2144
|
+
dialog keeps the ``┃`` row and drops the mode row and the border.
|
|
2145
|
+
Cursor and claude do not draw any of these.
|
|
2146
|
+
"""
|
|
2147
|
+
for ln in lines:
|
|
2148
|
+
s = ln.strip()
|
|
2149
|
+
if s.startswith("╹▀") or _opencode_mode_row(ln) or _opencode_permission_row(ln):
|
|
2150
|
+
return True
|
|
2151
|
+
return False
|
|
2152
|
+
|
|
2153
|
+
|
|
2154
|
+
def _opencode_running(lines: list[str]) -> bool:
|
|
2155
|
+
"""True only while a turn is in progress.
|
|
2156
|
+
|
|
2157
|
+
The footer shows ``esc interrupt``, and the ▣ header has no trailing
|
|
2158
|
+
duration. A finished turn keeps the ▣ header and adds ``· <n>s``,
|
|
2159
|
+
with no interrupt hint. That pane is idle. The permission dialog is
|
|
2160
|
+
not a turn; it has its own branch.
|
|
2161
|
+
"""
|
|
2162
|
+
if any("esc interrupt" in ln for ln in lines):
|
|
2163
|
+
return True
|
|
2164
|
+
for ln in lines:
|
|
2165
|
+
s = ln.strip()
|
|
2166
|
+
if s.startswith("▣") and not _OPENCODE_DONE.search(s):
|
|
2167
|
+
return True
|
|
2168
|
+
return False
|
|
2169
|
+
|
|
2170
|
+
|
|
2171
|
+
def _opencode_turn_finished(lines: list[str]) -> bool:
|
|
2172
|
+
"""The ▣ header carried a duration and the interrupt hint is gone."""
|
|
2173
|
+
if _opencode_running(lines):
|
|
2174
|
+
return False
|
|
2175
|
+
return any(
|
|
2176
|
+
ln.strip().startswith("▣") and _OPENCODE_DONE.search(ln.strip())
|
|
2177
|
+
for ln in lines
|
|
2178
|
+
)
|
|
2179
|
+
|
|
2180
|
+
|
|
2181
|
+
def _opencode_in_progress(target: str) -> bool:
|
|
2182
|
+
"""In-progress opencode turn. A deferral here keeps wake_outstanding."""
|
|
2183
|
+
lines = _capture_pane_lines(target)
|
|
2184
|
+
if not lines or not _is_opencode_pane(lines):
|
|
2185
|
+
return False
|
|
2186
|
+
return _opencode_running(lines)
|
|
2187
|
+
|
|
2188
|
+
|
|
2189
|
+
def _opencode_turn_finished_target(target: str) -> bool:
|
|
2190
|
+
lines = _capture_pane_lines(target)
|
|
2191
|
+
if not lines or not _is_opencode_pane(lines):
|
|
2192
|
+
return False
|
|
2193
|
+
return _opencode_turn_finished(lines)
|
|
2194
|
+
|
|
2195
|
+
|
|
2196
|
+
def _opencode_row_text(line: str) -> str:
|
|
2197
|
+
"""Text on one ``┃`` row, with a right-aligned footer cut off.
|
|
2198
|
+
|
|
2199
|
+
A long cwd:branch is drawn inside the box, right-aligned, and wraps
|
|
2200
|
+
across as many rows as the path needs. The gap before that footer is
|
|
2201
|
+
a run of 4 or more spaces. Composer text is the left side of the row.
|
|
2202
|
+
Leading spaces stay in place for the split, then the left side is
|
|
2203
|
+
stripped. Skipping a fixed number of rows cannot work: the row count
|
|
2204
|
+
is the path length over the box width.
|
|
2205
|
+
"""
|
|
2206
|
+
idx = line.find("┃")
|
|
2207
|
+
if idx < 0:
|
|
2208
|
+
return ""
|
|
2209
|
+
return re.split(r" {4,}", line[idx + 1:], maxsplit=1)[0].strip()
|
|
2210
|
+
|
|
2211
|
+
|
|
2212
|
+
def _opencode_input(lines: list[str]) -> Optional[str]:
|
|
2213
|
+
"""Composer text inside the box, '' when that box is empty, None if no box.
|
|
2214
|
+
|
|
2215
|
+
Walk up from the mode row. Each ``┃`` row contributes only the text
|
|
2216
|
+
before its footer gap. The walk stops at the first line that is not a
|
|
2217
|
+
``┃`` row — the top of the box — so a transcript ``┃`` row above the
|
|
2218
|
+
box is never composer text.
|
|
2219
|
+
"""
|
|
2220
|
+
mode_at = None
|
|
2221
|
+
for i, ln in enumerate(lines):
|
|
2222
|
+
if _opencode_mode_row(ln):
|
|
2223
|
+
mode_at = i
|
|
2224
|
+
if mode_at is None:
|
|
2225
|
+
return None
|
|
2226
|
+
for ln in reversed(lines[:mode_at]):
|
|
2227
|
+
if not ln.strip().startswith("┃"):
|
|
2228
|
+
return ""
|
|
2229
|
+
text = _opencode_row_text(ln)
|
|
2230
|
+
if text:
|
|
2231
|
+
return text
|
|
2232
|
+
return ""
|
|
2233
|
+
|
|
2234
|
+
|
|
2235
|
+
def _opencode_composer_state(lines: list[str]) -> Optional[str]:
|
|
2236
|
+
if any(_opencode_permission_row(ln) for ln in lines):
|
|
2237
|
+
return "busy"
|
|
2238
|
+
text = _opencode_input(lines)
|
|
2239
|
+
if text is None:
|
|
2240
|
+
return None
|
|
2241
|
+
if not text or text.startswith("Ask anything"):
|
|
2242
|
+
return "clear"
|
|
2243
|
+
if _only_wake_text(text):
|
|
2244
|
+
return "wake"
|
|
2245
|
+
return "busy"
|
|
2246
|
+
|
|
2247
|
+
|
|
2075
2248
|
def _agent_running(target: str) -> Optional[bool]:
|
|
2076
|
-
"""True
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2249
|
+
"""True while a keystroke would miss the idle composer.
|
|
2250
|
+
|
|
2251
|
+
Cursor: the composer row carries 'ctrl+c to stop' (measured live on
|
|
2252
|
+
cursor-lin 2026-08-24). A keystroke into that TUI lands in the
|
|
2253
|
+
follow-up queue, and the queue is input-gated (measured live
|
|
2254
|
+
2026-08-26, twice: queued wakes fired only when the human next typed).
|
|
2255
|
+
Opencode: ``esc interrupt`` or a ▣ header with no trailing duration.
|
|
2256
|
+
A finished ``· <n>s`` header is idle. None =
|
|
2257
|
+
pane or composer unreadable; callers treat unknown as NOT-running
|
|
2258
|
+
(the composer state was already established by then — this guard
|
|
2259
|
+
only vets the timing).
|
|
2085
2260
|
"""
|
|
2086
2261
|
lines = _capture_pane_lines(target)
|
|
2087
2262
|
if lines is None:
|
|
2088
2263
|
return None
|
|
2264
|
+
if _is_opencode_pane(lines):
|
|
2265
|
+
return _opencode_running(lines)
|
|
2089
2266
|
composer = _composer_line(lines)
|
|
2090
2267
|
if composer is None:
|
|
2091
2268
|
return None
|
|
@@ -2112,6 +2289,8 @@ def _composer_state(target: str) -> Optional[str]:
|
|
|
2112
2289
|
lines = _capture_pane_lines(target)
|
|
2113
2290
|
if lines is None:
|
|
2114
2291
|
return None
|
|
2292
|
+
if _is_opencode_pane(lines):
|
|
2293
|
+
return _opencode_composer_state(lines)
|
|
2115
2294
|
composer = _composer_line(lines)
|
|
2116
2295
|
if composer is None:
|
|
2117
2296
|
return None
|
|
@@ -2126,7 +2305,7 @@ def _composer_state(target: str) -> Optional[str]:
|
|
|
2126
2305
|
# A human who types exactly 'check mesh' by hand is indistinguishable
|
|
2127
2306
|
# from our own unsubmitted wake — and submitting it runs the same
|
|
2128
2307
|
# command, so the collision is harmless in that direction only.
|
|
2129
|
-
if content
|
|
2308
|
+
if _only_wake_text(content):
|
|
2130
2309
|
return "wake"
|
|
2131
2310
|
return "busy"
|
|
2132
2311
|
|
|
@@ -2199,8 +2378,11 @@ def _tmux_wake(target: str) -> Optional[bool]:
|
|
|
2199
2378
|
wake stays owed and the ledger retries the whole wake later.
|
|
2200
2379
|
"""
|
|
2201
2380
|
try:
|
|
2381
|
+
seen = _capture_pane_lines(target)
|
|
2382
|
+
prompt = (_OPENCODE_WAKE_PROMPT
|
|
2383
|
+
if seen and _is_opencode_pane(seen) else _WAKE_PROMPT)
|
|
2202
2384
|
subprocess.run(
|
|
2203
|
-
["tmux", "send-keys", "-t", target, "-l",
|
|
2385
|
+
["tmux", "send-keys", "-t", target, "-l", prompt],
|
|
2204
2386
|
check=True,
|
|
2205
2387
|
stdout=subprocess.DEVNULL,
|
|
2206
2388
|
stderr=subprocess.PIPE,
|
|
@@ -2234,6 +2416,10 @@ def _tmux_wake(target: str) -> Optional[bool]:
|
|
|
2234
2416
|
# sink DEFER, keeping the cursor back so a later poll
|
|
2235
2417
|
# retries the wake when the composer clears.
|
|
2236
2418
|
return None
|
|
2419
|
+
# "wake" while a turn is already on screen: another Enter stacks
|
|
2420
|
+
# a follow-up. Defer; the sink does not advance.
|
|
2421
|
+
if _agent_running(target):
|
|
2422
|
+
return None
|
|
2237
2423
|
# "wake": still sitting unsubmitted — loop another Enter.
|
|
2238
2424
|
print(
|
|
2239
2425
|
f"[monitor] tmux wake still unsubmitted after "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.69.
|
|
3
|
+
Version: 0.69.2
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -60,6 +60,7 @@ def test_tmux_wake_sends_literal_prompt_then_submits_and_verifies(monkeypatch):
|
|
|
60
60
|
assert mesh._tmux_wake("gpt-lc:0.0") is True
|
|
61
61
|
assert seen == ["gpt-lc:0.0"] # verified through the composer gate
|
|
62
62
|
assert [command for command, _ in calls] == [
|
|
63
|
+
["tmux", "capture-pane", "-p", "-t", "gpt-lc:0.0"],
|
|
63
64
|
["tmux", "send-keys", "-t", "gpt-lc:0.0", "-l", "check mesh"],
|
|
64
65
|
["tmux", "send-keys", "-t", "gpt-lc:0.0", "Enter"],
|
|
65
66
|
]
|
|
@@ -44,6 +44,8 @@ def _rig(monkeypatch, *, composer="clear", unread=3, session_created=None,
|
|
|
44
44
|
monkeypatch.setattr(mesh, "_composer_state", lambda t: composer)
|
|
45
45
|
monkeypatch.setattr(mesh, "_tmux_session_created", lambda t: session_created)
|
|
46
46
|
monkeypatch.setattr(mesh, "_agent_running", lambda t: running)
|
|
47
|
+
monkeypatch.setattr(mesh, "_opencode_in_progress", lambda t: False)
|
|
48
|
+
monkeypatch.setattr(mesh, "_opencode_turn_finished_target", lambda t: False)
|
|
47
49
|
monkeypatch.setattr(watchdog, "_gateway_unread_count",
|
|
48
50
|
lambda *a, **k: unread)
|
|
49
51
|
|
|
@@ -53,7 +53,9 @@ def test_second_enter_fires_only_when_the_first_did_not_submit(tmux):
|
|
|
53
53
|
"""The Codex shape, now verified instead of blind: first Enter leaves the
|
|
54
54
|
prompt in the composer, second clears it."""
|
|
55
55
|
calls, state = tmux
|
|
56
|
-
|
|
56
|
+
# First frame is the pre-inject look (prompt choice). The pending frame
|
|
57
|
+
# is what the first Enter left behind.
|
|
58
|
+
state["captures"] = ["> ", "> check mesh", "> "]
|
|
57
59
|
assert mesh._tmux_wake("pane") is True
|
|
58
60
|
assert enters(calls) == 2
|
|
59
61
|
|
|
@@ -62,7 +64,7 @@ def test_concatenated_backlog_is_detected_and_drained(tmux):
|
|
|
62
64
|
"""The observed defect shape: two wakes stacked unsubmitted. The substring
|
|
63
65
|
check catches the concatenation and the retry Enter drains it."""
|
|
64
66
|
calls, state = tmux
|
|
65
|
-
state["captures"] = ["> check meshcheck mesh", "> "]
|
|
67
|
+
state["captures"] = ["> ", "> check meshcheck mesh", "> "]
|
|
66
68
|
assert mesh._tmux_wake("pane") is True
|
|
67
69
|
assert enters(calls) == 2
|
|
68
70
|
|
|
@@ -101,7 +103,7 @@ def test_capture_failure_mid_loop_stops_the_loop(tmux, monkeypatch):
|
|
|
101
103
|
state["captures"] = ["> check mesh"] # first capture: still pending
|
|
102
104
|
|
|
103
105
|
def flaky(argv, **kw):
|
|
104
|
-
if argv[1] == "capture-pane" and state["i"] >
|
|
106
|
+
if argv[1] == "capture-pane" and state["i"] > 1:
|
|
105
107
|
raise OSError("tmux socket vanished")
|
|
106
108
|
if argv[1] == "capture-pane":
|
|
107
109
|
i = min(state["i"], len(state["captures"]) - 1)
|
|
@@ -161,7 +163,7 @@ def test_codex_composer_marker_counts_as_recognizable(tmux):
|
|
|
161
163
|
|
|
162
164
|
def test_cursor_composer_holding_the_wake_is_pending(tmux):
|
|
163
165
|
calls, state = tmux
|
|
164
|
-
state["captures"] = ["→ check mesh", "→ Add a follow-up"]
|
|
166
|
+
state["captures"] = ["→ Add a follow-up", "→ check mesh", "→ Add a follow-up"]
|
|
165
167
|
assert mesh._tmux_wake("pane") is True
|
|
166
168
|
assert enters(calls) == 2
|
|
167
169
|
|
|
@@ -199,6 +201,8 @@ def gate(monkeypatch):
|
|
|
199
201
|
lambda t: calls.__setitem__("enter", calls["enter"] + 1) or True)
|
|
200
202
|
monkeypatch.setattr(mesh, "_wake_still_pending", lambda t: box["pending"])
|
|
201
203
|
monkeypatch.setattr(mesh, "_composer_state", lambda t: box["composer"])
|
|
204
|
+
monkeypatch.setattr(mesh, "_opencode_in_progress", lambda t: False)
|
|
205
|
+
monkeypatch.setattr(mesh, "_opencode_turn_finished_target", lambda t: False)
|
|
202
206
|
import swarph_cli.commands.watchdog as wd
|
|
203
207
|
monkeypatch.setattr(wd, "_gateway_unread_count",
|
|
204
208
|
lambda g, p, t: box["unread"])
|
|
@@ -540,3 +544,272 @@ def test_engine_defers_on_adopted_mid_settle_with_real_tmux_sink(tmp_path, tmux,
|
|
|
540
544
|
out = capsys.readouterr()
|
|
541
545
|
assert "DEFERRED" in out.out
|
|
542
546
|
assert "DELIVERY FAILED" not in out.err
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# ── opencode 1.18.33, measured on a sacrificial pane (card #961) ──────────
|
|
550
|
+
#
|
|
551
|
+
# The composer is a ┃ box, not a > / › / → line. A ▣ spinner sits between
|
|
552
|
+
# the transcript and the box; lines above it are history. `esc interrupt`
|
|
553
|
+
# and `Permission required` mean a keystroke is the wrong key.
|
|
554
|
+
|
|
555
|
+
_OPENCODE_IDLE = """\
|
|
556
|
+
┃
|
|
557
|
+
┃ Ask anything… "What is the tech stack of this project?"
|
|
558
|
+
┃
|
|
559
|
+
┃ Build · DeepSeek V4 Pro (New) OpenCode Go
|
|
560
|
+
╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
561
|
+
tab agents
|
|
562
|
+
ctrl+p commands
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
_OPENCODE_HOLDING = """\
|
|
566
|
+
┃
|
|
567
|
+
┃ check mesh: swarph_dm_unread
|
|
568
|
+
┃
|
|
569
|
+
┃ Build · DeepSeek V4 Pro (New) OpenCode Go
|
|
570
|
+
╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
_OPENCODE_RUNNING = """\
|
|
574
|
+
┃ check mesh: swarph_dm_unread
|
|
575
|
+
┃ Click to expand
|
|
576
|
+
▣ Build · DeepSeek V4 Pro (New)
|
|
577
|
+
┃
|
|
578
|
+
┃
|
|
579
|
+
┃ Build · DeepSeek V4 Pro (New) OpenCode Go
|
|
580
|
+
esc interrupt
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
# 1.18.33 draws the dialog inside the box. The mode row and the bottom
|
|
584
|
+
# border are not on this screen (drop-on-meta-edge, card #961 post 54557).
|
|
585
|
+
_OPENCODE_PERMISSION = """\
|
|
586
|
+
┃
|
|
587
|
+
┃ △ Permission required
|
|
588
|
+
┃
|
|
589
|
+
Allow once
|
|
590
|
+
Allow always
|
|
591
|
+
Reject
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
_OPENCODE_FINISHED = """\
|
|
595
|
+
▣ Build · DeepSeek V4 Pro (New) · 3.3s
|
|
596
|
+
┃
|
|
597
|
+
┃ Ask anything… "What is the tech stack of this project?"
|
|
598
|
+
┃
|
|
599
|
+
┃ Build · DeepSeek V4 Pro (New) OpenCode Go
|
|
600
|
+
╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
601
|
+
tab agents ctrl+p commands
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def test_live_idle_footer_is_clear_and_not_running(tmux):
|
|
606
|
+
"""The 07:10Z pane: composer empty, transcript full of ┃ rows, footer
|
|
607
|
+
cwd on the ┃ row above the mode row, finished header `· 3m 4s`.
|
|
608
|
+
0.69.1 returns that path as composer text and treats the header as
|
|
609
|
+
still running."""
|
|
610
|
+
from pathlib import Path
|
|
611
|
+
fixture = (Path(__file__).resolve().parent / "fixtures"
|
|
612
|
+
/ "opencode-pane-idle-footer-0710.txt")
|
|
613
|
+
text = fixture.read_text(encoding="utf-8")
|
|
614
|
+
lines = text.splitlines()
|
|
615
|
+
assert any("Found the real blocker" in ln and ln.strip().startswith("┃")
|
|
616
|
+
for ln in lines)
|
|
617
|
+
_calls, state = tmux
|
|
618
|
+
state["captures"] = [text]
|
|
619
|
+
assert mesh._opencode_input(lines) == ""
|
|
620
|
+
assert mesh._composer_state("sac") == "clear"
|
|
621
|
+
assert mesh._agent_running("sac") is False
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def test_drop_six_captures_split_the_footer():
|
|
625
|
+
"""A wrapped cwd is a run of spaces, not a fixed number of rows.
|
|
626
|
+
Measured by drop-on-meta-edge on sacW 220x55 (card #961, #612)."""
|
|
627
|
+
from pathlib import Path
|
|
628
|
+
fix = Path(__file__).resolve().parent / "fixtures"
|
|
629
|
+
wide = (fix / "opencode-961" / "W2_turn2.txt").read_text(encoding="utf-8")
|
|
630
|
+
narrow = (fix / "opencode-961" / "G3_turn2.txt").read_text(encoding="utf-8")
|
|
631
|
+
live = (fix / "opencode-pane-idle-footer-0710.txt").read_text(encoding="utf-8")
|
|
632
|
+
assert mesh._opencode_input(wide.splitlines()) == ""
|
|
633
|
+
assert mesh._opencode_input(live.splitlines()) == ""
|
|
634
|
+
assert mesh._opencode_input(narrow.splitlines()) == ""
|
|
635
|
+
typed_narrow = "\n".join([
|
|
636
|
+
" ┃",
|
|
637
|
+
" ┃ hello from a human",
|
|
638
|
+
" ┃",
|
|
639
|
+
" ┃ Build · DeepSeek V4 Pro (New) OpenCode Go",
|
|
640
|
+
])
|
|
641
|
+
assert mesh._opencode_input(typed_narrow.splitlines()) == "hello from a human"
|
|
642
|
+
overlay = (
|
|
643
|
+
" ┃ hello from a human" + (" " * 150)
|
|
644
|
+
+ "meta-edge/d7cba227-9da7-4d92-92da-"
|
|
645
|
+
)
|
|
646
|
+
typed_wide = "\n".join([
|
|
647
|
+
" ┃",
|
|
648
|
+
overlay,
|
|
649
|
+
" ┃ Build · DeepSeek V4 Pro (New) OpenCode Go" + (" " * 40)
|
|
650
|
+
+ "feat/sacrificial-612",
|
|
651
|
+
])
|
|
652
|
+
assert mesh._opencode_input(typed_wide.splitlines()) == "hello from a human"
|
|
653
|
+
holding = "\n".join([
|
|
654
|
+
"┃",
|
|
655
|
+
"┃ check mesh: swarph_dm_unread",
|
|
656
|
+
"┃",
|
|
657
|
+
"┃ Build · DeepSeek V4 Pro (New) OpenCode Go",
|
|
658
|
+
])
|
|
659
|
+
assert mesh._opencode_input(holding.splitlines()) == "check mesh: swarph_dm_unread"
|
|
660
|
+
# A wide pane keeps the sidebar on the ▣ row, past the duration.
|
|
661
|
+
sidebar = "▣ Build · DeepSeek V4.1 Flash · 5.1s" + (" " * 40) + "LSPs are disabled"
|
|
662
|
+
assert mesh._OPENCODE_DONE.search(sidebar)
|
|
663
|
+
assert mesh._opencode_running([sidebar]) is False
|
|
664
|
+
assert mesh._opencode_running(["▣ Build · DeepSeek V4 Pro (New)"]) is True
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def test_wide_empty_box_records_the_wake_outstanding(tmux):
|
|
668
|
+
"""W-D1: the footer showed up after Enter and the old read called the
|
|
669
|
+
box busy, so the injected wake was reported as a human's line and
|
|
670
|
+
wake_outstanding stayed unset."""
|
|
671
|
+
from pathlib import Path
|
|
672
|
+
wide = (Path(__file__).resolve().parent / "fixtures" / "opencode-961"
|
|
673
|
+
/ "W2_turn2.txt").read_text(encoding="utf-8")
|
|
674
|
+
calls, state = tmux
|
|
675
|
+
state["captures"] = [wide]
|
|
676
|
+
st = _State()
|
|
677
|
+
assert mesh.TmuxSink("sacW").deliver(st, [], 1) is True
|
|
678
|
+
assert st.ledger("x")["wake_outstanding"] is True
|
|
679
|
+
literals = [c for c in calls if "-l" in c]
|
|
680
|
+
assert literals == [[
|
|
681
|
+
"tmux", "send-keys", "-t", "sacW", "-l", mesh._OPENCODE_WAKE_PROMPT]]
|
|
682
|
+
assert enters(calls) == 1
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def test_opencode_idle_box_is_a_clear_composer(tmux):
|
|
686
|
+
calls, state = tmux
|
|
687
|
+
state["captures"] = [_OPENCODE_IDLE]
|
|
688
|
+
assert mesh._composer_state("sac") == "clear"
|
|
689
|
+
assert mesh._agent_running("sac") is False
|
|
690
|
+
# `--auto` rewrites the mode row to "Build auto ·" (1.18.33).
|
|
691
|
+
state["captures"] = [_OPENCODE_IDLE.replace("Build ·", "Build auto ·")]
|
|
692
|
+
state["i"] = 0
|
|
693
|
+
assert mesh._composer_state("sac") == "clear"
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def test_opencode_unsubmitted_pointer_is_wake_not_unknown(tmux):
|
|
697
|
+
"""Main reads this pane as no composer at all, so the sink fails closed
|
|
698
|
+
and the text stays typed. The box is a composer, and this line is wake."""
|
|
699
|
+
calls, state = tmux
|
|
700
|
+
state["captures"] = [_OPENCODE_HOLDING]
|
|
701
|
+
assert mesh._composer_state("sac") == "wake"
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def test_opencode_human_text_in_the_box_is_busy(tmux):
|
|
705
|
+
calls, state = tmux
|
|
706
|
+
state["captures"] = [_OPENCODE_HOLDING.replace(
|
|
707
|
+
"check mesh: swarph_dm_unread", "look at the diff")]
|
|
708
|
+
assert mesh._composer_state("sac") == "busy"
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def test_opencode_transcript_above_the_spinner_is_not_the_composer(tmux):
|
|
712
|
+
"""The submitted prompt stays on screen above ▣. Reading it as the
|
|
713
|
+
composer would send another Enter into the running turn."""
|
|
714
|
+
calls, state = tmux
|
|
715
|
+
state["captures"] = [_OPENCODE_RUNNING]
|
|
716
|
+
assert mesh._composer_state("sac") == "clear"
|
|
717
|
+
assert mesh._agent_running("sac") is True
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def test_opencode_permission_modal_is_not_typed_into(tmux):
|
|
721
|
+
"""The dialog is a ┃ row. deliver returns None from the busy branch.
|
|
722
|
+
False would be the unrecognised-pane fallthrough, which also sends no
|
|
723
|
+
keys and is the wrong outcome."""
|
|
724
|
+
calls, state = tmux
|
|
725
|
+
assert "┃ △ Permission required" in _OPENCODE_PERMISSION
|
|
726
|
+
assert "╹▀" not in _OPENCODE_PERMISSION
|
|
727
|
+
assert "Build ·" not in _OPENCODE_PERMISSION
|
|
728
|
+
state["captures"] = [_OPENCODE_PERMISSION]
|
|
729
|
+
assert mesh._composer_state("sac") == "busy"
|
|
730
|
+
sink = mesh.TmuxSink("sac")
|
|
731
|
+
assert sink.deliver(_State(), [], 1) is None
|
|
732
|
+
assert enters(calls) == 0
|
|
733
|
+
assert not any("-l" in c for c in calls)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def test_opencode_finished_header_is_not_a_running_turn(tmux):
|
|
737
|
+
calls, state = tmux
|
|
738
|
+
state["captures"] = [_OPENCODE_FINISHED]
|
|
739
|
+
assert mesh._agent_running("sac") is False
|
|
740
|
+
assert mesh._opencode_turn_finished_target("sac") is True
|
|
741
|
+
assert mesh._composer_state("sac") == "clear"
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def test_opencode_deferral_keeps_wake_outstanding(tmux):
|
|
745
|
+
calls, state = tmux
|
|
746
|
+
state["captures"] = [_OPENCODE_RUNNING]
|
|
747
|
+
st = _State()
|
|
748
|
+
st.ledger("x")["wake_outstanding"] = True
|
|
749
|
+
assert mesh.TmuxSink("sac").deliver(st, [], 2) is None
|
|
750
|
+
assert st.ledger("x")["wake_outstanding"] is True
|
|
751
|
+
assert enters(calls) == 0
|
|
752
|
+
assert not any("-l" in c for c in calls)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def test_opencode_finished_turn_earns_another_wake(tmux, monkeypatch):
|
|
756
|
+
"""Inside the trust window a standing wake would report delivery with
|
|
757
|
+
no keystroke. A finished header means the cell is idle again."""
|
|
758
|
+
import time
|
|
759
|
+
import swarph_cli.commands.watchdog as wd
|
|
760
|
+
monkeypatch.setattr(wd, "_gateway_unread_count", lambda *a, **k: 1)
|
|
761
|
+
calls, state = tmux
|
|
762
|
+
state["captures"] = [_OPENCODE_FINISHED]
|
|
763
|
+
st = _State()
|
|
764
|
+
st.ledger("x")["wake_outstanding"] = True
|
|
765
|
+
st.ledger("x")["last_wake_injected_at"] = time.time()
|
|
766
|
+
assert mesh.TmuxSink("sac").deliver(st, [], 3) is True
|
|
767
|
+
literals = [c for c in calls if "-l" in c]
|
|
768
|
+
assert literals == [[
|
|
769
|
+
"tmux", "send-keys", "-t", "sac", "-l", mesh._OPENCODE_WAKE_PROMPT]]
|
|
770
|
+
assert enters(calls) == 1
|
|
771
|
+
assert st.ledger("x")["wake_outstanding"] is True
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def test_opencode_running_turn_gets_no_second_prompt(tmux):
|
|
775
|
+
calls, state = tmux
|
|
776
|
+
state["captures"] = [_OPENCODE_RUNNING]
|
|
777
|
+
sink = mesh.TmuxSink("sac")
|
|
778
|
+
assert sink.deliver(_State(), [], 1) is None
|
|
779
|
+
assert enters(calls) == 0
|
|
780
|
+
assert not any("-l" in c for c in calls)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def test_opencode_idle_injects_the_pointer_then_one_enter(tmux):
|
|
784
|
+
calls, state = tmux
|
|
785
|
+
state["captures"] = [_OPENCODE_IDLE, _OPENCODE_RUNNING]
|
|
786
|
+
assert mesh._tmux_wake("sac") is True
|
|
787
|
+
literals = [c for c in calls if "-l" in c]
|
|
788
|
+
assert literals == [[
|
|
789
|
+
"tmux", "send-keys", "-t", "sac", "-l", mesh._OPENCODE_WAKE_PROMPT]]
|
|
790
|
+
assert enters(calls) == 1
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def test_opencode_holding_wake_is_nudged_not_retyped(tmux):
|
|
794
|
+
calls, state = tmux
|
|
795
|
+
state["captures"] = [_OPENCODE_HOLDING]
|
|
796
|
+
sink = mesh.TmuxSink("sac")
|
|
797
|
+
assert sink.deliver(_State(), [], 1) is True
|
|
798
|
+
assert enters(calls) == 1
|
|
799
|
+
assert not any("-l" in c for c in calls)
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def test_opencode_dropin_names_the_tmux_sink_and_is_not_the_template():
|
|
803
|
+
"""install-unit renders swarph-monitor@.service only. This file is the
|
|
804
|
+
review copy of the opencode drop-in; nothing in this test installs it."""
|
|
805
|
+
from pathlib import Path
|
|
806
|
+
root = Path(__file__).resolve().parents[1]
|
|
807
|
+
dropin = root / "deploy" / "monitor" / "swarph-monitor@opencode.conf"
|
|
808
|
+
text = dropin.read_text(encoding="utf-8")
|
|
809
|
+
assert "ExecStart=" in text
|
|
810
|
+
assert "--deliver tmux:opencode" in text
|
|
811
|
+
assert "--deliver pull" in text
|
|
812
|
+
assert "--as opencode" in text
|
|
813
|
+
template = (root / "src" / "swarph_cli" / "systemd"
|
|
814
|
+
/ "swarph-monitor@.service").read_text(encoding="utf-8")
|
|
815
|
+
assert "tmux:opencode" not in template
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|