swarph-cli 0.67.2__tar.gz → 0.68.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swarph_cli-0.67.2/src/swarph_cli.egg-info → swarph_cli-0.68.1}/PKG-INFO +2 -2
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/README.md +1 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/pyproject.toml +1 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/channel.py +11 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/codegraph.py +14 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/hooks.py +61 -17
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/mcp_server.py +1 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/mesh.py +252 -8
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/schedule.py +1 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/spawn.py +26 -6
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/watchdog.py +106 -31
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/server.py +1 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/guide/GUIDE.md +1 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/main.py +1 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1/src/swarph_cli.egg-info}/PKG-INFO +2 -2
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli.egg-info/SOURCES.txt +5 -0
- swarph_cli-0.68.1/tests/test_184_notify_honesty.py +114 -0
- swarph_cli-0.68.1/tests/test_559_edges_truncated.py +75 -0
- swarph_cli-0.68.1/tests/test_825_install_posttooluse_only.py +105 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_825_remaining_cap_shred_bind.py +4 -4
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_830_hook_binding_version.py +2 -2
- swarph_cli-0.68.1/tests/test_975_channel_hop.py +56 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_codegraph_hook.py +1 -3
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_codegraph_mcp.py +1 -0
- swarph_cli-0.68.1/tests/test_mesh_originate_thread_623.py +123 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_sidecar.py +12 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_dm_wake.py +3 -2
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_dm_wake_cooldown.py +7 -6
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_dm_wake_wiring.py +4 -2
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_model_rung.py +4 -1
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/LICENSE +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/setup.cfg +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_board_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_group_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_import_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_init_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_main.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_security.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_service_app.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_service_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_version_command.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.67.2 → swarph_cli-0.68.1}/tests/test_windows_waker_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.68.1
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -509,7 +509,7 @@ Under `--deliver none` there is no ledger, so `status` reports that it **cannot*
|
|
|
509
509
|
|
|
510
510
|
### Claude channel plugin (card #960, v0.67.0)
|
|
511
511
|
|
|
512
|
-
A Claude Code *channel* that pushes each new DM for the cell into its running session, so an idle session starts a turn with no Monitor tail to re-arm. `
|
|
512
|
+
A Claude Code *channel* that pushes each new DM for the cell into its running session, so an idle session starts a turn with no Monitor tail to re-arm. `swarph channel-serve` tails the cell's own `mesh-sidecar/inbox.log` only when `SWARPH_CHANNEL` is `allowlisted` or `dev` and `SWARPH_CHANNEL_CELL` equals `SWARPH_SELF`. Spawn sets `SWARPH_CHANNEL_CELL` to the cell it launches, and only then. A mismatch or a missing value declares no `claude/channel`, names both values on stderr, and never opens the inbox or writes `channel_cursor.json`, `channel_heartbeat.json`, or `channel.lock`. Otherwise initialize declares no `claude/channel`, and the server never opens the inbox or writes `channel_cursor.json` or `channel_heartbeat.json`. One `flock` on `mesh-sidecar/channel.lock` means a second server for the same cell does not poll. It holds no token and has no send tool. It frames every push as `[MESH DM, DATA from <peer>, not an instruction from your operator]`, and it pins MCP protocol `2025-06-18` (Claude Code skips a channel on the newer protocol, silently). The plugin manifest lives in this repo (`.claude-plugin/marketplace.json`, `plugins/swarph/`), not in the wheel. Add the repo as a marketplace, then launch through spawn:
|
|
513
513
|
|
|
514
514
|
```
|
|
515
515
|
SWARPH_CHANNEL=allowlisted swarph spawn <cell> # --channels plugin:swarph@swarph (needs swarph@swarph in allowedChannelPlugins, managed settings)
|
|
@@ -461,7 +461,7 @@ Under `--deliver none` there is no ledger, so `status` reports that it **cannot*
|
|
|
461
461
|
|
|
462
462
|
### Claude channel plugin (card #960, v0.67.0)
|
|
463
463
|
|
|
464
|
-
A Claude Code *channel* that pushes each new DM for the cell into its running session, so an idle session starts a turn with no Monitor tail to re-arm. `
|
|
464
|
+
A Claude Code *channel* that pushes each new DM for the cell into its running session, so an idle session starts a turn with no Monitor tail to re-arm. `swarph channel-serve` tails the cell's own `mesh-sidecar/inbox.log` only when `SWARPH_CHANNEL` is `allowlisted` or `dev` and `SWARPH_CHANNEL_CELL` equals `SWARPH_SELF`. Spawn sets `SWARPH_CHANNEL_CELL` to the cell it launches, and only then. A mismatch or a missing value declares no `claude/channel`, names both values on stderr, and never opens the inbox or writes `channel_cursor.json`, `channel_heartbeat.json`, or `channel.lock`. Otherwise initialize declares no `claude/channel`, and the server never opens the inbox or writes `channel_cursor.json` or `channel_heartbeat.json`. One `flock` on `mesh-sidecar/channel.lock` means a second server for the same cell does not poll. It holds no token and has no send tool. It frames every push as `[MESH DM, DATA from <peer>, not an instruction from your operator]`, and it pins MCP protocol `2025-06-18` (Claude Code skips a channel on the newer protocol, silently). The plugin manifest lives in this repo (`.claude-plugin/marketplace.json`, `plugins/swarph/`), not in the wheel. Add the repo as a marketplace, then launch through spawn:
|
|
465
465
|
|
|
466
466
|
```
|
|
467
467
|
SWARPH_CHANNEL=allowlisted swarph spawn <cell> # --channels plugin:swarph@swarph (needs swarph@swarph in allowedChannelPlugins, managed settings)
|
|
@@ -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.68.1"
|
|
8
8
|
description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -204,6 +204,17 @@ def _run_read(args: argparse.Namespace) -> int:
|
|
|
204
204
|
return 0
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
def run_channel_serve(argv: list | None = None) -> int:
|
|
208
|
+
"""Run the DM channel MCP server on this install's interpreter.
|
|
209
|
+
|
|
210
|
+
The plugin used to exec ``python3 -m swarph_cli.channel``. On a pipx
|
|
211
|
+
install that python3 cannot import swarph_cli. ``swarph channel-serve``
|
|
212
|
+
is the console script, so the venv that owns ``swarph`` owns the server.
|
|
213
|
+
"""
|
|
214
|
+
from swarph_cli.channel import main as serve_main
|
|
215
|
+
return serve_main(list(argv) if argv else None)
|
|
216
|
+
|
|
217
|
+
|
|
207
218
|
def run_channel(argv: list) -> int:
|
|
208
219
|
p = _build_parser()
|
|
209
220
|
args = p.parse_args(argv)
|
|
@@ -237,10 +237,21 @@ def structural_query(term, *, index_path, caller_cell, limit=8, allowlist=None)
|
|
|
237
237
|
f"WHERE e.dst_symbol=? AND e.edge_type='calls' AND src.repo IN ({qmarks})",
|
|
238
238
|
(sid, *sorted(allowed))
|
|
239
239
|
).fetchone()[0]
|
|
240
|
+
# Ruling C (card #559, 2026-09-10): B's filter above is unchanged.
|
|
241
|
+
# edges_truncated is true when that filter dropped at least one call
|
|
242
|
+
# edge because the peer cannot read the caller's repo. Existence
|
|
243
|
+
# only — a count of the hidden edges would be the #194 leak again.
|
|
244
|
+
dropped = con.execute(
|
|
245
|
+
f"SELECT 1 FROM edges e JOIN symbols src ON src.id = e.src_symbol "
|
|
246
|
+
f"WHERE e.dst_symbol=? AND e.edge_type='calls' AND src.repo NOT IN ({qmarks}) "
|
|
247
|
+
f"LIMIT 1",
|
|
248
|
+
(sid, *sorted(allowed)),
|
|
249
|
+
).fetchone()
|
|
240
250
|
out.append({"repo": repo, "name": name, "kind": kind,
|
|
241
251
|
"file_path": fp, "start_line": line, "callers": callers, "score": score,
|
|
242
252
|
"qualified_name": qualified_name or "", "docstring": docstring or "",
|
|
243
|
-
"signature": signature or ""
|
|
253
|
+
"signature": signature or "",
|
|
254
|
+
"edges_truncated": dropped is not None})
|
|
244
255
|
return out
|
|
245
256
|
# OperationalError (lock/busy, or a malformed FTS5 MATCH query string) OR DatabaseError
|
|
246
257
|
# (corrupt/non-sqlite file) — both are sqlite3.Error siblings — fail safe (A2), never raise/hang.
|
|
@@ -279,6 +290,8 @@ def format_human(rows, term) -> str:
|
|
|
279
290
|
loc = f"{r['file_path']}:{r['start_line']}"
|
|
280
291
|
n = r["callers"]
|
|
281
292
|
callers = f" ({n} caller{'s' if n != 1 else ''})" if n else ""
|
|
293
|
+
if r.get("edges_truncated"):
|
|
294
|
+
callers += " [edges_truncated]"
|
|
282
295
|
lines.append(f"{i}. {r['name']} [{r['kind']}] {r['repo']} {loc}{callers}")
|
|
283
296
|
if r["signature"]:
|
|
284
297
|
lines.append(f" {r['signature']}")
|
|
@@ -190,17 +190,17 @@ exit 0
|
|
|
190
190
|
# The bundled codegraph hook (cards #194 + #825). Thin wrapper around
|
|
191
191
|
# `swarph codegraph-hook` so heuristics stay unit-testable.
|
|
192
192
|
#
|
|
193
|
-
# #825: UserPromptSubmit
|
|
194
|
-
#
|
|
195
|
-
# (
|
|
196
|
-
#
|
|
193
|
+
# #825, retired 2026-09-26: UserPromptSubmit / Stop / StopFailure are not
|
|
194
|
+
# installed. The audit showed the prompt side did not steer tool choice
|
|
195
|
+
# (3 codegraph calls in 1,206 displays). PostToolUse/Bash is the binding
|
|
196
|
+
# that stays. install_hook drops the retired events for this command so a
|
|
197
|
+
# later `swarph hooks add` cannot put them back.
|
|
197
198
|
_CODEGRAPH_ON_GREP_SH = r"""#!/bin/sh
|
|
198
199
|
# codegraph-on-grep.sh — swarph bundled Claude Code hook (#194 + #825).
|
|
199
200
|
# swarph-cli-bundle-version: __SWARPH_CLI_VERSION__
|
|
200
201
|
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
# Stop/StopFailure: record whether the session grepped, called codegraph, or neither.
|
|
202
|
+
# PostToolUse/Bash: annotate code greps. UserPromptSubmit, Stop, and
|
|
203
|
+
# StopFailure are not bound (card #825, retired 2026-09-26).
|
|
204
204
|
# #830: version stamp is substituted at install time — hooks list compares it to
|
|
205
205
|
# the running swarph --version so a binding/code skew is reportable.
|
|
206
206
|
exec swarph codegraph-hook "$@"
|
|
@@ -210,10 +210,8 @@ BUILTIN_HOOKS: dict = {
|
|
|
210
210
|
"codegraph-on-grep": HookBundle(
|
|
211
211
|
name="codegraph-on-grep",
|
|
212
212
|
description=(
|
|
213
|
-
"On
|
|
214
|
-
"
|
|
215
|
-
"symbol-level answers. Prompt path steers tool choice; Bash path "
|
|
216
|
-
"annotates. Audits counterfactual outcomes. Never blocks. Reports "
|
|
213
|
+
"On PostToolUse/Bash (code grep), queries the gateway's structural "
|
|
214
|
+
"codegraph and injects symbol-level answers. Never blocks. Reports "
|
|
217
215
|
"an unavailable index LOUDLY rather than as 'no matches'."
|
|
218
216
|
),
|
|
219
217
|
publisher="swarph-builtin",
|
|
@@ -221,13 +219,10 @@ BUILTIN_HOOKS: dict = {
|
|
|
221
219
|
script_name="codegraph-on-grep.sh",
|
|
222
220
|
script_body=_CODEGRAPH_ON_GREP_SH,
|
|
223
221
|
bindings=(
|
|
224
|
-
HookBinding("UserPromptSubmit", ""),
|
|
225
222
|
HookBinding("PostToolUse", "Bash"),
|
|
226
|
-
HookBinding("Stop", ""),
|
|
227
|
-
HookBinding("StopFailure", ""),
|
|
228
223
|
),
|
|
229
224
|
handler_events=(
|
|
230
|
-
"
|
|
225
|
+
"PostToolUse",
|
|
231
226
|
),
|
|
232
227
|
),
|
|
233
228
|
"cell-resilience": HookBundle(
|
|
@@ -502,6 +497,49 @@ def _unmerge_hook(settings: dict, event: str, matcher: str, command: str) -> dic
|
|
|
502
497
|
return settings
|
|
503
498
|
|
|
504
499
|
|
|
500
|
+
def _drop_command_outside_bindings(settings: dict, commands, bundle: HookBundle) -> dict:
|
|
501
|
+
"""Remove this bundle's command strings from events it does not declare.
|
|
502
|
+
|
|
503
|
+
``_merge_hook`` only adds. A binding deleted from the bundle would
|
|
504
|
+
otherwise survive the next install, which is how the retired codegraph
|
|
505
|
+
prompt-side events came back (card #825). ``commands`` is the install
|
|
506
|
+
ladder (canonical plus older Windows forms). Other commands on those
|
|
507
|
+
events are left alone.
|
|
508
|
+
"""
|
|
509
|
+
keep = {(b.event, b.matcher) for b in bundle.bindings}
|
|
510
|
+
hooks = settings.get("hooks")
|
|
511
|
+
if not isinstance(hooks, dict):
|
|
512
|
+
return settings
|
|
513
|
+
drop = {v for c in commands for v in _command_variants_for(c)}
|
|
514
|
+
for event, entries in list(hooks.items()):
|
|
515
|
+
if not isinstance(entries, list):
|
|
516
|
+
continue
|
|
517
|
+
removed = False
|
|
518
|
+
for entry in list(entries):
|
|
519
|
+
# A string (or anything else) in an unrelated event is not a
|
|
520
|
+
# group. Skip it. _unmerge_hook would call .get on it (#825).
|
|
521
|
+
if not isinstance(entry, dict):
|
|
522
|
+
continue
|
|
523
|
+
acts = entry.get("hooks")
|
|
524
|
+
if not isinstance(acts, list) or (event, entry.get("matcher", "")) in keep:
|
|
525
|
+
continue
|
|
526
|
+
kept = [
|
|
527
|
+
a for a in acts
|
|
528
|
+
if not (isinstance(a, dict) and a.get("command") in drop)
|
|
529
|
+
]
|
|
530
|
+
# Never rewrite a group that did not hold this command. An empty
|
|
531
|
+
# hooks list and a matcher-only group stay as they were.
|
|
532
|
+
if len(kept) == len(acts):
|
|
533
|
+
continue
|
|
534
|
+
entry["hooks"] = kept
|
|
535
|
+
if not kept:
|
|
536
|
+
entries.remove(entry)
|
|
537
|
+
removed = True
|
|
538
|
+
if removed and not entries:
|
|
539
|
+
del hooks[event]
|
|
540
|
+
return settings
|
|
541
|
+
|
|
542
|
+
|
|
505
543
|
# --------------------------------------------------------------------------- #
|
|
506
544
|
# Local-bundle resolver (T3)
|
|
507
545
|
# --------------------------------------------------------------------------- #
|
|
@@ -699,6 +737,11 @@ def install_hook(
|
|
|
699
737
|
settings = _load_settings(settings_path)
|
|
700
738
|
for b in bundle.bindings:
|
|
701
739
|
settings = _merge_hook(settings, b.event, b.matcher, command)
|
|
740
|
+
# Drop this command from every event the bundle no longer declares.
|
|
741
|
+
# Otherwise a retired binding (codegraph UserPromptSubmit/Stop, #825)
|
|
742
|
+
# stays forever, and the next add puts it back if it was removed by hand.
|
|
743
|
+
settings = _drop_command_outside_bindings(
|
|
744
|
+
settings, _installed_command_variants(bundle, hooks_home), bundle)
|
|
702
745
|
|
|
703
746
|
# ---- write the script (version-stamped), then atomic-save settings ----
|
|
704
747
|
hooks_home_p.mkdir(parents=True, exist_ok=True)
|
|
@@ -1058,8 +1101,9 @@ def list_hooks(
|
|
|
1058
1101
|
|
|
1059
1102
|
name [installed|partial|available] trust=builtin — description
|
|
1060
1103
|
|
|
1061
|
-
``partial`` (#825): at least one binding present but not the full set
|
|
1062
|
-
|
|
1104
|
+
``partial`` (#825): at least one binding present but not the full set.
|
|
1105
|
+
codegraph-on-grep has a single PostToolUse/Bash binding, so that shape
|
|
1106
|
+
is installed, not partial.
|
|
1063
1107
|
When installed and the script carries a ``swarph-cli-bundle-version`` stamp
|
|
1064
1108
|
(#830), a second line reports match / mismatch vs the running package.
|
|
1065
1109
|
"""
|
|
@@ -29,6 +29,7 @@ import sys
|
|
|
29
29
|
import time
|
|
30
30
|
import urllib.error
|
|
31
31
|
import urllib.parse
|
|
32
|
+
import uuid
|
|
32
33
|
|
|
33
34
|
import swarph_cli
|
|
34
35
|
import urllib.request
|
|
@@ -78,6 +79,21 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
78
79
|
send = sub.add_parser("send", help="send a mesh DM")
|
|
79
80
|
send.add_argument("to", help="recipient peer name")
|
|
80
81
|
send.add_argument("--kind", required=True, help="message kind")
|
|
82
|
+
send.add_argument(
|
|
83
|
+
"--thread",
|
|
84
|
+
default=None,
|
|
85
|
+
metavar="HANDLE",
|
|
86
|
+
help="join HANDLE, or 'new' to originate a card-less thread (#623). "
|
|
87
|
+
"The printed handle is one pasteable token (the gateway UUID). "
|
|
88
|
+
"A post whose from_node equals to_node, or whose from_node is "
|
|
89
|
+
"in --cc, is refused (#185 / #409 — no self-wake).",
|
|
90
|
+
)
|
|
91
|
+
send.add_argument(
|
|
92
|
+
"--cc",
|
|
93
|
+
action="append",
|
|
94
|
+
default=[],
|
|
95
|
+
help="extra recipient (repeatable). from_node in this list is refused.",
|
|
96
|
+
)
|
|
81
97
|
add_content_args(send)
|
|
82
98
|
_add_common(send)
|
|
83
99
|
|
|
@@ -118,8 +134,25 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
118
134
|
help="show the inbox WITHOUT marking anything read (default: reading CONSUMES -- "
|
|
119
135
|
"it marks every DM shown as read for the resolved identity)",
|
|
120
136
|
)
|
|
137
|
+
inbox.add_argument(
|
|
138
|
+
"--thread",
|
|
139
|
+
default=None,
|
|
140
|
+
metavar="HANDLE",
|
|
141
|
+
help="read this thread's ordered set (GET /threads/{handle}) instead of "
|
|
142
|
+
"the inbox. Non-destructive. Handle is the one-token UUID.",
|
|
143
|
+
)
|
|
121
144
|
_add_common(inbox)
|
|
122
145
|
|
|
146
|
+
threads = sub.add_parser(
|
|
147
|
+
"threads",
|
|
148
|
+
help="list threads this caller participates in (#623). "
|
|
149
|
+
"Peer identity tokens are scoped by the gateway (FM3); this "
|
|
150
|
+
"command does not widen that.",
|
|
151
|
+
)
|
|
152
|
+
threads.add_argument("--limit", type=int, default=50, help="max threads")
|
|
153
|
+
threads.add_argument("--json", action="store_true", help="print raw JSON")
|
|
154
|
+
_add_common(threads)
|
|
155
|
+
|
|
123
156
|
register = sub.add_parser("register", help="self-register this peer")
|
|
124
157
|
register.add_argument(
|
|
125
158
|
"--url",
|
|
@@ -577,6 +610,56 @@ def _check_recipient(to: str, gateway: str, token: str) -> str | None:
|
|
|
577
610
|
return msg
|
|
578
611
|
|
|
579
612
|
|
|
613
|
+
def _self_wake_refusal(from_node: str, to_node: str, cc: list) -> Optional[str]:
|
|
614
|
+
"""#185 / #409: a participant must not be woken by their own post.
|
|
615
|
+
|
|
616
|
+
One post, one to_node — the 226 self-wakes were the addressed peer BEING
|
|
617
|
+
the poster, not a fan-out. Refuse from_node == to_node and from_node in cc.
|
|
618
|
+
Returns a message or None.
|
|
619
|
+
"""
|
|
620
|
+
if from_node and to_node and from_node == to_node:
|
|
621
|
+
return (
|
|
622
|
+
f"swarph mesh send: REFUSED — from_node {from_node!r} equals to_node. "
|
|
623
|
+
f"A post must not wake its own sender (#185 / #409)."
|
|
624
|
+
)
|
|
625
|
+
cc_hits = [c for c in (cc or []) if c == from_node]
|
|
626
|
+
if cc_hits:
|
|
627
|
+
return (
|
|
628
|
+
f"swarph mesh send: REFUSED — from_node {from_node!r} is in --cc. "
|
|
629
|
+
f"A post must not wake its own sender (#185 / #409)."
|
|
630
|
+
)
|
|
631
|
+
return None
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _originate_thread(gateway: str, token: str, from_node: str, to_node: str) -> tuple:
|
|
635
|
+
"""Mint a card-less thread via POST /threads. Returns (uuid, None) or (None, err).
|
|
636
|
+
|
|
637
|
+
A bare client UUID on POST /messages 500s — the row must exist in
|
|
638
|
+
claude_threads first. thread_name is an internal key (idempotent mint);
|
|
639
|
+
the HANDLE the caller pastes is the UUID, one token, no quoting.
|
|
640
|
+
"""
|
|
641
|
+
stamp = uuid.uuid4().hex[:12]
|
|
642
|
+
# Gateway thread_name max 128. Peers are max 64 each; keep the name short.
|
|
643
|
+
name = f"{from_node}↔{to_node}:t{stamp}"
|
|
644
|
+
if len(name) > 128:
|
|
645
|
+
name = f"t{stamp}"
|
|
646
|
+
status, payload = _post_json(
|
|
647
|
+
f"{gateway.rstrip('/')}/threads",
|
|
648
|
+
{"thread_name": name},
|
|
649
|
+
token,
|
|
650
|
+
)
|
|
651
|
+
if status < 200 or status >= 300 or not isinstance(payload, dict):
|
|
652
|
+
detail = payload.get("detail", payload) if isinstance(payload, dict) else payload
|
|
653
|
+
return None, f"gateway {status}: {detail}"
|
|
654
|
+
handle = payload.get("thread_uuid")
|
|
655
|
+
if not handle or not isinstance(handle, str):
|
|
656
|
+
return None, f"gateway minted no thread_uuid: {payload}"
|
|
657
|
+
# One token: no whitespace, no shell metacharacters.
|
|
658
|
+
if not re.fullmatch(r"[0-9a-fA-F-]{36}", handle):
|
|
659
|
+
return None, f"handle is not a pasteable UUID token: {handle!r}"
|
|
660
|
+
return handle, None
|
|
661
|
+
|
|
662
|
+
|
|
580
663
|
def _run_send(args: argparse.Namespace) -> int:
|
|
581
664
|
try:
|
|
582
665
|
content = resolve_content(args.content, getattr(args, "content_file", None))
|
|
@@ -585,12 +668,36 @@ def _run_send(args: argparse.Namespace) -> int:
|
|
|
585
668
|
return 1
|
|
586
669
|
self_name = _resolve_self_name(args.self_name)
|
|
587
670
|
token = _resolve_token(self_name, args.token_file)
|
|
671
|
+
cc = list(getattr(args, "cc", None) or [])
|
|
672
|
+
refusal = _self_wake_refusal(self_name, args.to, cc)
|
|
673
|
+
if refusal:
|
|
674
|
+
print(refusal, file=sys.stderr)
|
|
675
|
+
return 1
|
|
588
676
|
body = {
|
|
589
677
|
"from_node": self_name,
|
|
590
678
|
"to_node": args.to,
|
|
591
679
|
"kind": args.kind,
|
|
592
680
|
"content": content,
|
|
593
681
|
}
|
|
682
|
+
if cc:
|
|
683
|
+
body["cc"] = cc
|
|
684
|
+
thread_arg = getattr(args, "thread", None)
|
|
685
|
+
if thread_arg:
|
|
686
|
+
if thread_arg == "new":
|
|
687
|
+
handle, err = _originate_thread(args.gateway, token, self_name, args.to)
|
|
688
|
+
if err:
|
|
689
|
+
print(f"swarph mesh send: cannot originate thread — {err}", file=sys.stderr)
|
|
690
|
+
return 1
|
|
691
|
+
else:
|
|
692
|
+
handle = thread_arg.strip()
|
|
693
|
+
if not re.fullmatch(r"[0-9a-fA-F-]{36}", handle):
|
|
694
|
+
print(
|
|
695
|
+
f"swarph mesh send: REFUSED — thread handle {handle!r} is not "
|
|
696
|
+
f"one pasteable UUID token (or 'new').",
|
|
697
|
+
file=sys.stderr,
|
|
698
|
+
)
|
|
699
|
+
return 1
|
|
700
|
+
body["thread_id"] = handle
|
|
594
701
|
refusal = _check_recipient(args.to, args.gateway, token)
|
|
595
702
|
if refusal:
|
|
596
703
|
print(refusal, file=sys.stderr)
|
|
@@ -605,10 +712,72 @@ def _run_send(args: argparse.Namespace) -> int:
|
|
|
605
712
|
detail = payload.get("detail", "<gateway error>")
|
|
606
713
|
print(f"swarph mesh send: gateway {status}: {detail}", file=sys.stderr)
|
|
607
714
|
return 1
|
|
608
|
-
|
|
715
|
+
line = (
|
|
609
716
|
f"sent id={payload.get('id')} from={payload.get('from_node')} "
|
|
610
717
|
f"to={payload.get('to_node')} kind={payload.get('kind')}"
|
|
611
718
|
)
|
|
719
|
+
handle_out = payload.get("thread_id") or body.get("thread_id")
|
|
720
|
+
if handle_out:
|
|
721
|
+
line += f" thread={handle_out}"
|
|
722
|
+
print(line)
|
|
723
|
+
return 0
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _run_inbox_thread(args: argparse.Namespace, self_name: str, handle: str) -> int:
|
|
727
|
+
"""Ordered set for one thread. Non-destructive. FM3 scoping is the gateway's."""
|
|
728
|
+
handle = (handle or "").strip()
|
|
729
|
+
if not re.fullmatch(r"[0-9a-fA-F-]{36}", handle):
|
|
730
|
+
print(
|
|
731
|
+
f"swarph mesh inbox: REFUSED — thread handle {handle!r} is not "
|
|
732
|
+
f"one pasteable UUID token.",
|
|
733
|
+
file=sys.stderr,
|
|
734
|
+
)
|
|
735
|
+
return 1
|
|
736
|
+
token = _resolve_token(self_name, args.token_file)
|
|
737
|
+
url = f"{args.gateway.rstrip('/')}/threads/{handle}?limit={int(args.limit)}"
|
|
738
|
+
status, payload = _http_get_json(url, token)
|
|
739
|
+
if status < 200 or status >= 300:
|
|
740
|
+
detail = payload.get("detail", "<gateway error>") if isinstance(payload, dict) else payload
|
|
741
|
+
print(f"swarph mesh inbox: gateway {status}: {detail}", file=sys.stderr)
|
|
742
|
+
return 1
|
|
743
|
+
msgs = payload.get("messages") if isinstance(payload, dict) else None
|
|
744
|
+
if msgs is None:
|
|
745
|
+
print(f"swarph mesh inbox: unexpected /threads shape: {payload}", file=sys.stderr)
|
|
746
|
+
return 1
|
|
747
|
+
if args.json:
|
|
748
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
749
|
+
return 0
|
|
750
|
+
print(f"thread {handle} ({len(msgs)} message(s), identity {self_name})")
|
|
751
|
+
for dm in msgs:
|
|
752
|
+
print(_format_inbox_line(dm))
|
|
753
|
+
if not msgs:
|
|
754
|
+
print(" empty")
|
|
755
|
+
return 0
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _run_threads(args: argparse.Namespace) -> int:
|
|
759
|
+
"""List threads. Gateway scopes a peer-identity token to participants (FM3)."""
|
|
760
|
+
self_name = _resolve_self_name(args.self_name)
|
|
761
|
+
token = _resolve_token(self_name, args.token_file)
|
|
762
|
+
url = f"{args.gateway.rstrip('/')}/threads?limit={int(args.limit)}"
|
|
763
|
+
status, payload = _http_get_json(url, token)
|
|
764
|
+
if status < 200 or status >= 300:
|
|
765
|
+
detail = payload.get("detail", "<gateway error>") if isinstance(payload, dict) else payload
|
|
766
|
+
print(f"swarph mesh threads: gateway {status}: {detail}", file=sys.stderr)
|
|
767
|
+
return 1
|
|
768
|
+
rows = payload.get("threads") if isinstance(payload, dict) else None
|
|
769
|
+
if rows is None:
|
|
770
|
+
print(f"swarph mesh threads: unexpected shape: {payload}", file=sys.stderr)
|
|
771
|
+
return 1
|
|
772
|
+
if args.json:
|
|
773
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
774
|
+
return 0
|
|
775
|
+
print(f"threads {self_name} ({len(rows)})")
|
|
776
|
+
for r in rows:
|
|
777
|
+
uid = r.get("thread_uuid") or ""
|
|
778
|
+
topic = sanitize_terminal(r.get("topic") or "")
|
|
779
|
+
pair = sanitize_terminal(r.get("peer_pair") or "")
|
|
780
|
+
print(f"{uid} {pair} {topic}")
|
|
612
781
|
return 0
|
|
613
782
|
|
|
614
783
|
|
|
@@ -814,6 +983,9 @@ def _mark_read(gateway: str, token: str, messages: list) -> None:
|
|
|
814
983
|
|
|
815
984
|
def _run_inbox(args: argparse.Namespace) -> int:
|
|
816
985
|
self_name, id_source = _resolve_self_with_source(args.self_name)
|
|
986
|
+
thread_handle = getattr(args, "thread", None)
|
|
987
|
+
if thread_handle:
|
|
988
|
+
return _run_inbox_thread(args, self_name, thread_handle)
|
|
817
989
|
|
|
818
990
|
# >>> A DESTRUCTIVE READ UNDER AN UNNAMED IDENTITY IS REFUSED, NOT WARNED. <<<
|
|
819
991
|
# Printing the identity was the first fix and it is not sufficient, for two
|
|
@@ -1081,6 +1253,38 @@ def _write_cursor_atomic(path: Path, cursor: dict) -> None:
|
|
|
1081
1253
|
os.replace(tmp, path)
|
|
1082
1254
|
|
|
1083
1255
|
|
|
1256
|
+
def _persist_wake_stamp(state: "MonitorState") -> None:
|
|
1257
|
+
"""Rewrite cursor.json once a wake stamp exists.
|
|
1258
|
+
|
|
1259
|
+
The observation save runs before any sink, so it records last_wake_at
|
|
1260
|
+
while the name is still unknown. Without this second write the file
|
|
1261
|
+
keeps 0.0 after a keystroke (#405).
|
|
1262
|
+
"""
|
|
1263
|
+
stamps: list[float] = []
|
|
1264
|
+
for led in state.ledgers.values():
|
|
1265
|
+
raw = led.get("last_wake_at")
|
|
1266
|
+
if raw is None:
|
|
1267
|
+
continue
|
|
1268
|
+
try:
|
|
1269
|
+
stamp = float(raw)
|
|
1270
|
+
except (TypeError, ValueError):
|
|
1271
|
+
continue
|
|
1272
|
+
if stamp > 0:
|
|
1273
|
+
stamps.append(stamp)
|
|
1274
|
+
if not stamps:
|
|
1275
|
+
return
|
|
1276
|
+
newest = max(stamps)
|
|
1277
|
+
previous = state.observed.get("last_wake_at")
|
|
1278
|
+
try:
|
|
1279
|
+
previous_f = float(previous) if previous is not None else 0.0
|
|
1280
|
+
except (TypeError, ValueError):
|
|
1281
|
+
previous_f = 0.0
|
|
1282
|
+
if previous_f == newest:
|
|
1283
|
+
return
|
|
1284
|
+
state.observed["last_wake_at"] = newest
|
|
1285
|
+
_write_cursor_atomic(state.cursor_path, dict(state.cursor))
|
|
1286
|
+
|
|
1287
|
+
|
|
1084
1288
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
1085
1289
|
# card #122 — pluggable delivery
|
|
1086
1290
|
#
|
|
@@ -1218,6 +1422,18 @@ class NoneSink(Sink):
|
|
|
1218
1422
|
super().__init__("none")
|
|
1219
1423
|
|
|
1220
1424
|
|
|
1425
|
+
def _stamp_wake(led: dict) -> None:
|
|
1426
|
+
"""The keystroke, not the delivery report.
|
|
1427
|
+
|
|
1428
|
+
`last_delivery_at` also moves when a standing wake returns True with no
|
|
1429
|
+
keystroke. `last_wake_at` moves only here, beside `last_wake_injected_at`.
|
|
1430
|
+
The name is absent until this runs; 0.0 is not a time.
|
|
1431
|
+
"""
|
|
1432
|
+
now = time.time()
|
|
1433
|
+
led["last_wake_at"] = now
|
|
1434
|
+
led["last_wake_injected_at"] = now
|
|
1435
|
+
|
|
1436
|
+
|
|
1221
1437
|
class TmuxSink(Sink):
|
|
1222
1438
|
"""Poke a tmux pane — the behaviour `mesh sidecar` has always had.
|
|
1223
1439
|
|
|
@@ -1303,7 +1519,7 @@ class TmuxSink(Sink):
|
|
|
1303
1519
|
return None
|
|
1304
1520
|
ok = _tmux_wake(self.target)
|
|
1305
1521
|
if ok:
|
|
1306
|
-
led
|
|
1522
|
+
_stamp_wake(led)
|
|
1307
1523
|
return True
|
|
1308
1524
|
if ok is None:
|
|
1309
1525
|
return None # human adopted mid-settle: defer
|
|
@@ -1332,7 +1548,7 @@ class TmuxSink(Sink):
|
|
|
1332
1548
|
return None
|
|
1333
1549
|
ok = _tmux_wake(self.target)
|
|
1334
1550
|
if ok:
|
|
1335
|
-
led
|
|
1551
|
+
_stamp_wake(led)
|
|
1336
1552
|
return True
|
|
1337
1553
|
if ok is None:
|
|
1338
1554
|
return None # human adopted mid-settle: defer
|
|
@@ -1377,7 +1593,8 @@ class TmuxSink(Sink):
|
|
|
1377
1593
|
# The wake text was OBSERVED already sitting in this pane, so
|
|
1378
1594
|
# the injection anchor stays valid for this session — nudging
|
|
1379
1595
|
# it does not re-anchor (the text predates us).
|
|
1380
|
-
|
|
1596
|
+
if "last_wake_injected_at" not in led:
|
|
1597
|
+
_stamp_wake(led)
|
|
1381
1598
|
return True
|
|
1382
1599
|
return False
|
|
1383
1600
|
if composer == "busy":
|
|
@@ -1404,7 +1621,7 @@ class TmuxSink(Sink):
|
|
|
1404
1621
|
return None
|
|
1405
1622
|
if ok:
|
|
1406
1623
|
led["wake_outstanding"] = True
|
|
1407
|
-
led
|
|
1624
|
+
_stamp_wake(led)
|
|
1408
1625
|
return True
|
|
1409
1626
|
# False splits by what the pane actually holds: text observably STUCK
|
|
1410
1627
|
# in the composer -> mark outstanding so the next poll nudges instead
|
|
@@ -1726,7 +1943,17 @@ class MonitorState:
|
|
|
1726
1943
|
continue
|
|
1727
1944
|
led = _new_ledger()
|
|
1728
1945
|
led["last_delivered_id"] = seed
|
|
1729
|
-
|
|
1946
|
+
raw = self.observed.get("last_wake_at")
|
|
1947
|
+
try:
|
|
1948
|
+
stamp = float(raw) if raw is not None else 0.0
|
|
1949
|
+
except (TypeError, ValueError):
|
|
1950
|
+
stamp = 0.0
|
|
1951
|
+
led["last_delivery_at"] = stamp
|
|
1952
|
+
# A positive file value is the only wake time the old cursor had.
|
|
1953
|
+
# 0.0 was the unread default, not a measurement — leave the key
|
|
1954
|
+
# absent so a later read is unknown, not "never".
|
|
1955
|
+
if stamp > 0:
|
|
1956
|
+
led["last_wake_at"] = stamp
|
|
1730
1957
|
self.ledgers[sink.name] = led
|
|
1731
1958
|
|
|
1732
1959
|
def ledger(self, name: str) -> dict:
|
|
@@ -2350,6 +2577,10 @@ def _monitor_iteration(state: MonitorState, *, poll_channels: bool = True) -> No
|
|
|
2350
2577
|
# A delivery deferred by the idle guard (or failed against a dead sink) must
|
|
2351
2578
|
# still land even though no NEW mail arrived, so this runs on every poll.
|
|
2352
2579
|
_monitor_deliver(state)
|
|
2580
|
+
# The observation cursor is written BEFORE delivery, while last_wake_at is
|
|
2581
|
+
# still unknown. A wake that landed this poll has to be written again or
|
|
2582
|
+
# cursor.json keeps the 0.0 default after a real keystroke (#405).
|
|
2583
|
+
_persist_wake_stamp(state)
|
|
2353
2584
|
|
|
2354
2585
|
# MATERIALIZE new ledgers even if nothing moved them. Found by driving the
|
|
2355
2586
|
# real CLI: a pure `--deliver pull` monitor never writes ledgers.json until
|
|
@@ -2611,7 +2842,14 @@ class _LegacyCursorView(MutableMapping):
|
|
|
2611
2842
|
if key == "last_msg_id":
|
|
2612
2843
|
return self._observed.get("last_msg_id", 0)
|
|
2613
2844
|
if key == "last_wake_at":
|
|
2614
|
-
|
|
2845
|
+
raw = self._led().get("last_wake_at")
|
|
2846
|
+
if raw is None:
|
|
2847
|
+
return None
|
|
2848
|
+
try:
|
|
2849
|
+
stamp = float(raw)
|
|
2850
|
+
except (TypeError, ValueError):
|
|
2851
|
+
return None
|
|
2852
|
+
return stamp if stamp > 0 else None
|
|
2615
2853
|
if key == "pending_wake":
|
|
2616
2854
|
return int(self._led()["last_delivered_id"]) < self._observed_id()
|
|
2617
2855
|
raise KeyError(key)
|
|
@@ -2630,7 +2868,11 @@ class _LegacyCursorView(MutableMapping):
|
|
|
2630
2868
|
self._led()["last_delivered_id"] = int(value)
|
|
2631
2869
|
return
|
|
2632
2870
|
if key == "last_wake_at":
|
|
2633
|
-
|
|
2871
|
+
stamp = float(value)
|
|
2872
|
+
self._led()["last_wake_at"] = stamp
|
|
2873
|
+
# The idle guard reads last_delivery_at. Legacy callers suppress
|
|
2874
|
+
# it by assigning this key; keep that clock in step.
|
|
2875
|
+
self._led()["last_delivery_at"] = stamp
|
|
2634
2876
|
return
|
|
2635
2877
|
if key == "pending_wake":
|
|
2636
2878
|
self._led()["last_delivered_id"] = 0 if value else self._observed_id()
|
|
@@ -2869,6 +3111,8 @@ def run_mesh(argv: list[str]) -> int:
|
|
|
2869
3111
|
return _run_reply(args)
|
|
2870
3112
|
if args.command == "inbox":
|
|
2871
3113
|
return _run_inbox(args)
|
|
3114
|
+
if args.command == "threads":
|
|
3115
|
+
return _run_threads(args)
|
|
2872
3116
|
if args.command == "register":
|
|
2873
3117
|
return _run_register(args)
|
|
2874
3118
|
if args.command == "peers":
|
|
@@ -256,7 +256,7 @@ def run_schedule(argv: list) -> int:
|
|
|
256
256
|
if args.command == "delete":
|
|
257
257
|
return _run_delete(args)
|
|
258
258
|
if args.command == "fire-now":
|
|
259
|
-
return _run_action(args, "fire-now", "fire-now", "
|
|
259
|
+
return _run_action(args, "fire-now", "fire-now", "marked")
|
|
260
260
|
parser.error(f"unknown command: {args.command}")
|
|
261
261
|
except RuntimeError as exc:
|
|
262
262
|
print(f"swarph schedule: {exc}", file=sys.stderr)
|