swarph-cli 0.72.1__tar.gz → 0.72.3__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.72.1/src/swarph_cli.egg-info → swarph_cli-0.72.3}/PKG-INFO +3 -1
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/README.md +2 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/pyproject.toml +1 -1
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/__init__.py +1 -1
- swarph_cli-0.72.3/src/swarph_cli/bench/codex_cli.py +127 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/bench.py +7 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/spawn.py +7 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/wake_hook_output.py +39 -5
- {swarph_cli-0.72.1 → swarph_cli-0.72.3/src/swarph_cli.egg-info}/PKG-INFO +3 -1
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1013_spawn_channel.py +25 -0
- swarph_cli-0.72.3/tests/test_961_loud_stop.py +106 -0
- swarph_cli-0.72.3/tests/test_bench_codex_cli.py +138 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/LICENSE +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/setup.cfg +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1006_board_publisher.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1017_muse_tui.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_484_codex_queue_sink.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_group_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_import_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_init_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_main.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_security.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_app.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_version_command.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.72.1 → swarph_cli-0.72.3}/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.72.
|
|
3
|
+
Version: 0.72.3
|
|
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
|
|
@@ -248,6 +248,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
|
|
|
248
248
|
|
|
249
249
|
`claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
|
|
250
250
|
|
|
251
|
+
`gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
|
|
252
|
+
|
|
251
253
|
### `swarph brain-ask` (v0.14.0)
|
|
252
254
|
|
|
253
255
|
Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
|
|
@@ -199,6 +199,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
|
|
|
199
199
|
|
|
200
200
|
`claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
|
|
201
201
|
|
|
202
|
+
`gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
|
|
203
|
+
|
|
202
204
|
### `swarph brain-ask` (v0.14.0)
|
|
203
205
|
|
|
204
206
|
Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.72.
|
|
7
|
+
version = "0.72.3"
|
|
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" }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""codex exec transport for the bench `codex` backend. Subscription-only, isolated,
|
|
2
|
+
billing-guarded. Sibling of :mod:`swarph_cli.bench.claude_cli`, and it differs from it
|
|
3
|
+
in three places that matter.
|
|
4
|
+
|
|
5
|
+
Measured 2026-10-02, codex-cli 0.157.0, macOS arm64, a one-line prompt:
|
|
6
|
+
default config 15,472 input tokens
|
|
7
|
+
--ignore-user-config --ignore-rules 14,410 input tokens
|
|
8
|
+
The isolation flags remove the operator's MCP servers and plugins, and that is all
|
|
9
|
+
they remove. The floor is the CLI's own agent instructions: there is no flag here that
|
|
10
|
+
turns `codex exec` into a bare completion, so this arm does not reach the ~421 tokens
|
|
11
|
+
`claude -p --tools ""` does. Compare arms on quality and on OUTPUT tokens; the input
|
|
12
|
+
count carries this fixed load.
|
|
13
|
+
|
|
14
|
+
THE BILLING GUARD IS CHECKED BEFORE THE CALL, NOT READ FROM THE RESULT. The claude
|
|
15
|
+
stream names its key source (`apiKeySource`). The codex stream names nothing about
|
|
16
|
+
billing, so the guard reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses to
|
|
17
|
+
call unless it is `chatgpt`. A file that cannot be read is `billing:unknown`: not
|
|
18
|
+
knowing is a refusal, never a pass.
|
|
19
|
+
|
|
20
|
+
NO SYSTEM-PROMPT FLAG. `codex exec` has none, so the pack's `system` is sent on stdin
|
|
21
|
+
ahead of the prompt, separated by a blank line.
|
|
22
|
+
|
|
23
|
+
The child runs in an empty temporary directory under a read-only sandbox, so there is
|
|
24
|
+
no repository for the agent to read and nothing it can write.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import subprocess
|
|
31
|
+
import tempfile
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
from swarph_cli.bench.claude_cli import CliResult
|
|
36
|
+
|
|
37
|
+
_STRIP_EXACT = ("OPENAI_API_KEY", "CODEX_API_KEY")
|
|
38
|
+
_RATE = ("usage limit", "rate limit", "429", "too many requests")
|
|
39
|
+
_SUBSCRIPTION_MODE = "chatgpt"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_argv(model_id: str) -> list:
|
|
43
|
+
"""Fixed argv. The trailing `-` reads the prompt from stdin, never from argv."""
|
|
44
|
+
return ["codex", "exec", "--model", model_id, "--json", "--ephemeral",
|
|
45
|
+
"--skip-git-repo-check", "--ignore-user-config", "--ignore-rules",
|
|
46
|
+
"--sandbox", "read-only", "-"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def clean_env(env: dict) -> dict:
|
|
50
|
+
"""Drop billing keys and every SWARPH_CHANNEL* name, including ones not listed yet."""
|
|
51
|
+
return {
|
|
52
|
+
k: v for k, v in env.items()
|
|
53
|
+
if k not in _STRIP_EXACT and not k.startswith("SWARPH_CHANNEL")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def codex_home(env: dict) -> Path:
|
|
58
|
+
return Path(env.get("CODEX_HOME") or (Path.home() / ".codex"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def auth_mode(home: Path) -> Optional[str]:
|
|
62
|
+
"""`auth_mode` from auth.json, or None when it cannot be read. None is not a pass."""
|
|
63
|
+
try:
|
|
64
|
+
data = json.loads((home / "auth.json").read_text(encoding="utf-8"))
|
|
65
|
+
except (OSError, ValueError):
|
|
66
|
+
return None
|
|
67
|
+
mode = data.get("auth_mode") if isinstance(data, dict) else None
|
|
68
|
+
return mode if isinstance(mode, str) and mode else None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def stdin_text(prompt: str, system: str) -> str:
|
|
72
|
+
return f"{system}\n\n{prompt}" if system else prompt
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_rate(text: str) -> bool:
|
|
76
|
+
low = (text or "").lower()
|
|
77
|
+
return any(t in low for t in _RATE)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_stream(lines, stderr: str = "", key: Optional[str] = _SUBSCRIPTION_MODE) -> CliResult:
|
|
81
|
+
"""JSONL from `codex exec --json` -> CliResult.
|
|
82
|
+
|
|
83
|
+
The answer is the LAST agent_message. Usage is the LAST turn.completed: that event
|
|
84
|
+
carries the thread's running total, so adding two of them would double-count.
|
|
85
|
+
A usage limit is `rate_limit` whether it arrives as an event or on stderr — the
|
|
86
|
+
runner retries that string and never scores it as an answer.
|
|
87
|
+
"""
|
|
88
|
+
text, usage, failure = None, None, None
|
|
89
|
+
for ln in lines:
|
|
90
|
+
try:
|
|
91
|
+
e = json.loads(ln)
|
|
92
|
+
except Exception:
|
|
93
|
+
continue
|
|
94
|
+
if not isinstance(e, dict):
|
|
95
|
+
continue
|
|
96
|
+
kind = e.get("type")
|
|
97
|
+
if kind == "item.completed":
|
|
98
|
+
item = e.get("item") or {}
|
|
99
|
+
if item.get("type") == "agent_message" and isinstance(item.get("text"), str):
|
|
100
|
+
text = item["text"]
|
|
101
|
+
elif kind == "turn.completed":
|
|
102
|
+
usage = e.get("usage") or {}
|
|
103
|
+
elif kind in ("error", "turn.failed"):
|
|
104
|
+
detail = e.get("message") or e.get("error") or ""
|
|
105
|
+
failure = (kind, detail if isinstance(detail, str) else json.dumps(detail))
|
|
106
|
+
u = usage or {}
|
|
107
|
+
tin, tout = int(u.get("input_tokens") or 0), int(u.get("output_tokens") or 0)
|
|
108
|
+
if failure is not None:
|
|
109
|
+
err = "rate_limit" if _is_rate(failure[1]) or _is_rate(stderr) else f"cli_error:{failure[0]}"
|
|
110
|
+
return CliResult("", tin, tout, key, None, error=err)
|
|
111
|
+
if text is None or usage is None:
|
|
112
|
+
err = "rate_limit" if _is_rate(stderr) else "no_result"
|
|
113
|
+
return CliResult("", tin, tout, key, None, error=err)
|
|
114
|
+
return CliResult(text, tin, tout, key, None)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def call(model_id: str, prompt: str, system: str, *, runner=subprocess.run,
|
|
118
|
+
timeout: int = 300, env: Optional[dict] = None) -> CliResult:
|
|
119
|
+
source = dict(os.environ) if env is None else dict(env)
|
|
120
|
+
mode = auth_mode(codex_home(source))
|
|
121
|
+
if mode != _SUBSCRIPTION_MODE:
|
|
122
|
+
# Refused before any call: nothing was spent, and nothing is scored.
|
|
123
|
+
return CliResult("", 0, 0, mode, None, error=f"billing:{mode or 'unknown'}")
|
|
124
|
+
with tempfile.TemporaryDirectory(prefix="swarph-bench-codex-") as cwd:
|
|
125
|
+
p = runner(build_argv(model_id), input=stdin_text(prompt, system), capture_output=True,
|
|
126
|
+
text=True, env=clean_env(source), timeout=timeout, cwd=cwd)
|
|
127
|
+
return parse_stream((p.stdout or "").splitlines(), p.stderr or "", key=mode)
|
|
@@ -61,10 +61,17 @@ def _default_backends() -> dict[str, Backend]:
|
|
|
61
61
|
if shutil.which("claude"):
|
|
62
62
|
from swarph_cli.bench.claude_cli import call as claude_call
|
|
63
63
|
subscription = SubscriptionBackend(call_fn=claude_call)
|
|
64
|
+
# codex on PATH wires its own lane to an isolated `codex exec`. Its own key,
|
|
65
|
+
# like mistral: `id:subscription` keeps meaning claude.
|
|
66
|
+
codex = SubscriptionBackend()
|
|
67
|
+
if shutil.which("codex"):
|
|
68
|
+
from swarph_cli.bench.codex_cli import call as codex_call
|
|
69
|
+
codex = SubscriptionBackend(call_fn=codex_call)
|
|
64
70
|
return {
|
|
65
71
|
"metered": MeteredGeminiBackend(),
|
|
66
72
|
"mistral": MeteredMistralBackend(),
|
|
67
73
|
"subscription": subscription,
|
|
74
|
+
"codex": codex,
|
|
68
75
|
"rule": RuleBackend(),
|
|
69
76
|
"signal": SignalBackend(),
|
|
70
77
|
"typed-http": TypedHttpBackend(),
|
|
@@ -581,9 +581,16 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
|
|
|
581
581
|
# NOTE: the marketplace/validity gate lives in _validate_channel, which
|
|
582
582
|
# _build_claude_argv (and the muse env builder) run. This base stays a
|
|
583
583
|
# pure reflection of the resolved mode so every provider's env agrees.
|
|
584
|
+
# #1022: SWARPH_CHANNEL must be exported too, whatever the mode's source.
|
|
585
|
+
# channel_opted_in() (channel.py) gates on SWARPH_CHANNEL alone, so a
|
|
586
|
+
# mode that came from cell.yaml — not from the parent env — left the
|
|
587
|
+
# session with SWARPH_CHANNEL_CELL set and the channel deaf: the server
|
|
588
|
+
# never declared claude/channel (droplet 4 days, gridiron on lab).
|
|
584
589
|
if mode in {"allowlisted", "dev"}:
|
|
590
|
+
env["SWARPH_CHANNEL"] = mode
|
|
585
591
|
env["SWARPH_CHANNEL_CELL"] = cell.name
|
|
586
592
|
else:
|
|
593
|
+
env.pop("SWARPH_CHANNEL", None)
|
|
587
594
|
env.pop("SWARPH_CHANNEL_CELL", None)
|
|
588
595
|
return env
|
|
589
596
|
|
|
@@ -10,6 +10,11 @@ distinction is not WHICH harness, it is WHERE THE WAKE LIVES):
|
|
|
10
10
|
* ``claude`` / ``codex`` → ARM-INSTRUCTION. The wake lives in the harness:
|
|
11
11
|
emit the watch pipeline (tail -F inbox.log | dm_notify_filter) as
|
|
12
12
|
additionalContext so the agent arms it as a background watch.
|
|
13
|
+
* ``grok`` → the same arm instruction, with the pipeline wrapped in
|
|
14
|
+
``timeout -k 5s 590m`` and one ``[MESH WAKE EXPIRED]`` line. Grok caps a
|
|
15
|
+
background task at 10h (measured 2026-10-01); the wrapper exits 10
|
|
16
|
+
minutes early so the stop is a wake, not a SIGKILL the session never
|
|
17
|
+
sees. The duration is one unit: coreutils timeout rejects ``9h50m``.
|
|
13
18
|
* ``cursor`` → ARM-INSTRUCTION, same pipeline as claude/codex, emitted in
|
|
14
19
|
Cursor's ``additional_context`` envelope (the ``~/.cursor/hooks.json``
|
|
15
20
|
sessionStart shape). The wake is the session-armed tail. A missing or
|
|
@@ -43,7 +48,7 @@ from swarph_cli.cell import (
|
|
|
43
48
|
load_cell,
|
|
44
49
|
)
|
|
45
50
|
|
|
46
|
-
_ARM_HARNESSES = ("claude", "codex", "muse", "opencode")
|
|
51
|
+
_ARM_HARNESSES = ("claude", "codex", "muse", "opencode", "grok")
|
|
47
52
|
_INJECT_HARNESSES = ("antigravity",)
|
|
48
53
|
_VERIFY_HARNESSES = ("cursor",)
|
|
49
54
|
_KNOWN_HARNESSES = _ARM_HARNESSES + _INJECT_HARNESSES + _VERIFY_HARNESSES
|
|
@@ -52,6 +57,12 @@ _KNOWN_HARNESSES = _ARM_HARNESSES + _INJECT_HARNESSES + _VERIFY_HARNESSES
|
|
|
52
57
|
_FILTER_MODULE = "swarph_cli.scripts.dm_notify_filter"
|
|
53
58
|
# Same default as `swarph monitor arm-check --max-age-s` (15 minutes).
|
|
54
59
|
_INBOX_MAX_AGE_S = 15 * 60
|
|
60
|
+
_EXPIRED_LINE = "[MESH WAKE EXPIRED] re-arm the inbox tail now"
|
|
61
|
+
# Harnesses whose background tasks die at a cap. The wrapper is that cap
|
|
62
|
+
# minus 10 minutes, as one unit coreutils timeout accepts. Grok's cap is
|
|
63
|
+
# 10h (grok-researcher, 2026-10-01); "9h50m" is rejected as an invalid
|
|
64
|
+
# time interval on this box.
|
|
65
|
+
_HARNESS_TIMEOUT = {"grok": "590m"}
|
|
55
66
|
|
|
56
67
|
|
|
57
68
|
def _read_stdin_payload() -> dict[str, Any]:
|
|
@@ -233,7 +244,27 @@ def _provenance_note(source: str) -> str:
|
|
|
233
244
|
return f" (identity from {source})"
|
|
234
245
|
|
|
235
246
|
|
|
236
|
-
def
|
|
247
|
+
def _watch_pipeline(inbox: Path, interpreter: str) -> str:
|
|
248
|
+
return f'tail -n 0 -F "{inbox}" | "{interpreter}" -u -m {_FILTER_MODULE}'
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _loud_stop(pipeline: str, duration: str) -> str:
|
|
252
|
+
"""Self-expire before the harness kills the task, then say so.
|
|
253
|
+
|
|
254
|
+
``-k 5s`` sends KILL if the tail is still up after TERM. A single-quoted
|
|
255
|
+
``sh -c`` matches the pipeline, which quotes paths with double quotes.
|
|
256
|
+
"""
|
|
257
|
+
return (
|
|
258
|
+
f"timeout -k 5s {duration} sh -c '{pipeline}'; "
|
|
259
|
+
f"echo '{_EXPIRED_LINE}'"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _arm_instruction(
|
|
264
|
+
cell_name: Optional[str],
|
|
265
|
+
source: str = "unresolved",
|
|
266
|
+
harness: str = "",
|
|
267
|
+
) -> str:
|
|
237
268
|
interpreter = sys.executable
|
|
238
269
|
if not cell_name:
|
|
239
270
|
# CANNOT-RESOLVE is a LOUD REFUSAL, never a placeholder (card #527
|
|
@@ -259,8 +290,10 @@ def _arm_instruction(cell_name: Optional[str], source: str = "unresolved") -> st
|
|
|
259
290
|
f'"{interpreter}" -m swarph_cli monitor arm-check '
|
|
260
291
|
f'--as {cell_name} --path "{inbox}"'
|
|
261
292
|
)
|
|
262
|
-
|
|
263
|
-
|
|
293
|
+
pipeline = _watch_pipeline(inbox, interpreter)
|
|
294
|
+
cap = _HARNESS_TIMEOUT.get(harness)
|
|
295
|
+
if cap:
|
|
296
|
+
pipeline = _loud_stop(pipeline, cap)
|
|
264
297
|
return (
|
|
265
298
|
"[swarph silent-wake] This cell's DM wake lives in THIS harness. "
|
|
266
299
|
"BEFORE arming, refuse a missing/stale inbox.log (#729):\n"
|
|
@@ -490,7 +523,8 @@ def run_wake_hook_output(argv: Optional[list[str]] = None) -> int:
|
|
|
490
523
|
)
|
|
491
524
|
if harness in _ARM_HARNESSES:
|
|
492
525
|
return _emit(
|
|
493
|
-
{"context": _arm_instruction(cell_name, cell_source)},
|
|
526
|
+
{"context": _arm_instruction(cell_name, cell_source, harness)},
|
|
527
|
+
harness=harness,
|
|
494
528
|
)
|
|
495
529
|
return _emit(
|
|
496
530
|
{"context": _verify_report(cell_name, cell_source)}, harness=harness
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.72.
|
|
3
|
+
Version: 0.72.3
|
|
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
|
|
@@ -248,6 +248,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
|
|
|
248
248
|
|
|
249
249
|
`claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
|
|
250
250
|
|
|
251
|
+
`gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
|
|
252
|
+
|
|
251
253
|
### `swarph brain-ask` (v0.14.0)
|
|
252
254
|
|
|
253
255
|
Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
|
|
@@ -40,6 +40,7 @@ src/swarph_cli.egg-info/top_level.txt
|
|
|
40
40
|
src/swarph_cli/bench/__init__.py
|
|
41
41
|
src/swarph_cli/bench/backends.py
|
|
42
42
|
src/swarph_cli/bench/claude_cli.py
|
|
43
|
+
src/swarph_cli/bench/codex_cli.py
|
|
43
44
|
src/swarph_cli/bench/pack.py
|
|
44
45
|
src/swarph_cli/bench/prices.py
|
|
45
46
|
src/swarph_cli/bench/providers.py
|
|
@@ -270,6 +271,7 @@ tests/test_959_venv_interpreter.py
|
|
|
270
271
|
tests/test_960_channel.py
|
|
271
272
|
tests/test_960_ensure_monitor_heartbeat.py
|
|
272
273
|
tests/test_961_cursor_grok_footer.py
|
|
274
|
+
tests/test_961_loud_stop.py
|
|
273
275
|
tests/test_963_plugin_manifest.py
|
|
274
276
|
tests/test_964_channel_cell.py
|
|
275
277
|
tests/test_965_mesh_wait.py
|
|
@@ -284,6 +286,7 @@ tests/test_artifact_tool.py
|
|
|
284
286
|
tests/test_artifact_uri.py
|
|
285
287
|
tests/test_bench_backends.py
|
|
286
288
|
tests/test_bench_claude_cli.py
|
|
289
|
+
tests/test_bench_codex_cli.py
|
|
287
290
|
tests/test_bench_command.py
|
|
288
291
|
tests/test_bench_pack.py
|
|
289
292
|
tests/test_bench_prices.py
|
|
@@ -244,3 +244,28 @@ def test_parent_channel_cell_does_not_reach_a_different_cell(tmp_path, monkeypat
|
|
|
244
244
|
assert rc == 0
|
|
245
245
|
assert env_added.get("SWARPH_CHANNEL_CELL") == "science-claude"
|
|
246
246
|
|
|
247
|
+
|
|
248
|
+
def test_field_channel_exports_swarph_channel_so_serve_is_not_deaf(tmp_path, monkeypatch, capsys):
|
|
249
|
+
"""Card #1022. A mode that came from cell.yaml must reach channel-serve:
|
|
250
|
+
the spawned env carries SWARPH_CHANNEL=mode (not just SWARPH_CHANNEL_CELL),
|
|
251
|
+
and channel_opted_in() is True under that env. On 7afd16b the field path
|
|
252
|
+
set only SWARPH_CHANNEL_CELL, so the server never declared claude/channel
|
|
253
|
+
and the cell ran deaf (droplet ~4 days, gridiron on lab).
|
|
254
|
+
"""
|
|
255
|
+
from swarph_cli.channel import channel_opted_in
|
|
256
|
+
|
|
257
|
+
_registry(tmp_path, monkeypatch, with_swarph=True)
|
|
258
|
+
monkeypatch.delenv("SWARPH_CHANNEL", raising=False)
|
|
259
|
+
p = _cell_yaml(tmp_path, "claude", channel="allowlisted", name="gridiron")
|
|
260
|
+
rc, argv, env_added = _print(tmp_path, p, capsys)
|
|
261
|
+
assert rc == 0
|
|
262
|
+
assert "--channels" in argv and "plugin:swarph@swarph" in argv
|
|
263
|
+
# The env handed to claude names the mode, whatever its source was.
|
|
264
|
+
assert env_added.get("SWARPH_CHANNEL") == "allowlisted"
|
|
265
|
+
assert env_added.get("SWARPH_CHANNEL_CELL") == "gridiron"
|
|
266
|
+
# And the server's own gate agrees under exactly that env.
|
|
267
|
+
with monkeypatch.context() as ctx:
|
|
268
|
+
ctx.setenv("SWARPH_CHANNEL", env_added["SWARPH_CHANNEL"])
|
|
269
|
+
ctx.setenv("SWARPH_CHANNEL_CELL", env_added["SWARPH_CHANNEL_CELL"])
|
|
270
|
+
assert channel_opted_in() is True
|
|
271
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Card #961. A capped harness self-expires its inbox tail and says so.
|
|
2
|
+
|
|
3
|
+
Grok kills a background task at 10h. The hook wraps that tail in
|
|
4
|
+
``timeout -k 5s 590m`` (one unit; ``9h50m`` is rejected) and echoes one
|
|
5
|
+
EXPIRED line. Claude, cursor, and codex have no known cap and stay the
|
|
6
|
+
bare pipeline. On main, ``--harness grok`` is an unsupported-harness
|
|
7
|
+
refusal and this file fails.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from swarph_cli.commands import wake_hook_output as who
|
|
19
|
+
|
|
20
|
+
_EXPIRED = "[MESH WAKE EXPIRED] re-arm the inbox tail now"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _context(monkeypatch, capsys, harness: str, tmp_path, cell: str = "probe-cell"):
|
|
24
|
+
def _dir(name):
|
|
25
|
+
return tmp_path / "swarph_state" / name / "mesh-sidecar"
|
|
26
|
+
|
|
27
|
+
monkeypatch.setattr(who, "_sidecar_dir", _dir)
|
|
28
|
+
monkeypatch.setattr(who, "_resolve_cell", lambda explicit=None: (cell, "test"))
|
|
29
|
+
side = _dir(cell)
|
|
30
|
+
side.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
inbox = side / "inbox.log"
|
|
32
|
+
inbox.write_text("", encoding="utf-8")
|
|
33
|
+
rc = who.run_wake_hook_output(["--harness", harness])
|
|
34
|
+
assert rc == 0
|
|
35
|
+
raw = json.loads(capsys.readouterr().out)
|
|
36
|
+
if harness == "cursor":
|
|
37
|
+
ctx = raw["additional_context"]
|
|
38
|
+
else:
|
|
39
|
+
ctx = raw["hookSpecificOutput"]["additionalContext"]
|
|
40
|
+
return ctx, inbox
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _timeout_line(ctx: str) -> str:
|
|
44
|
+
lines = [line.strip() for line in ctx.splitlines() if "timeout -k 5s" in line]
|
|
45
|
+
assert len(lines) == 1
|
|
46
|
+
return lines[0]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_grok_emits_a_single_unit_timeout_and_the_expired_echo(monkeypatch, capsys, tmp_path):
|
|
50
|
+
ctx, _inbox = _context(monkeypatch, capsys, "grok", tmp_path)
|
|
51
|
+
line = _timeout_line(ctx)
|
|
52
|
+
assert line.startswith("timeout -k 5s 590m sh -c '")
|
|
53
|
+
assert line.endswith(f"'; echo '{_EXPIRED}'")
|
|
54
|
+
assert "9h50m" not in ctx
|
|
55
|
+
assert "9h" not in line
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_claude_cursor_and_codex_stay_the_bare_pipeline(monkeypatch, capsys, tmp_path):
|
|
59
|
+
for harness in ("claude", "cursor", "codex"):
|
|
60
|
+
ctx, _inbox = _context(monkeypatch, capsys, harness, tmp_path)
|
|
61
|
+
assert "timeout" not in ctx
|
|
62
|
+
assert "MESH WAKE EXPIRED" not in ctx
|
|
63
|
+
assert "tail -n 0 -F" in ctx
|
|
64
|
+
assert "dm_notify_filter" in ctx
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _coreutils_timeout() -> bool:
|
|
68
|
+
"""A coreutils timeout that honors ``-k`` and a duration.
|
|
69
|
+
|
|
70
|
+
GNU and uutils both do. A macOS stand-in returns at once, so the
|
|
71
|
+
elapsed check would fail for the wrong reason.
|
|
72
|
+
"""
|
|
73
|
+
if shutil.which("timeout") is None:
|
|
74
|
+
return False
|
|
75
|
+
proc = subprocess.run(
|
|
76
|
+
["timeout", "--version"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
)
|
|
80
|
+
text = f"{proc.stdout or ''}{proc.stderr or ''}".lower()
|
|
81
|
+
return proc.returncode == 0 and "coreutils" in text
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_a_two_second_cap_prints_exactly_one_expired_line(monkeypatch, capsys, tmp_path):
|
|
85
|
+
"""Run the emitted command with the duration swapped to 2s.
|
|
86
|
+
|
|
87
|
+
The scratch inbox is empty, so the filter prints nothing. After the
|
|
88
|
+
timeout, the shell echoes the re-arm line once.
|
|
89
|
+
"""
|
|
90
|
+
ctx, inbox = _context(monkeypatch, capsys, "grok", tmp_path)
|
|
91
|
+
script = _timeout_line(ctx).replace("590m", "2s", 1)
|
|
92
|
+
assert "590m" not in script
|
|
93
|
+
assert inbox.is_file()
|
|
94
|
+
if not _coreutils_timeout():
|
|
95
|
+
pytest.skip("the 2s run needs a coreutils timeout")
|
|
96
|
+
started = time.monotonic()
|
|
97
|
+
proc = subprocess.run(
|
|
98
|
+
["sh", "-c", script],
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
timeout=20,
|
|
102
|
+
)
|
|
103
|
+
elapsed = time.monotonic() - started
|
|
104
|
+
expired = [line for line in proc.stdout.splitlines() if "MESH WAKE EXPIRED" in line]
|
|
105
|
+
assert expired == [_EXPIRED]
|
|
106
|
+
assert elapsed >= 1.5
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Codex bench transport: isolated codex exec, billing guard before the call, no live call."""
|
|
2
|
+
import json
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
from swarph_cli.bench import backends as B
|
|
6
|
+
from swarph_cli.bench import codex_cli as C
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def lines(text='{"answer": 68}', usage=None, extra=()):
|
|
10
|
+
u = usage or {"input_tokens": 14410, "cached_input_tokens": 12160,
|
|
11
|
+
"cache_write_input_tokens": 0, "output_tokens": 10, "reasoning_output_tokens": 0}
|
|
12
|
+
out = [json.dumps({"type": "thread.started", "thread_id": "t"}),
|
|
13
|
+
json.dumps({"type": "turn.started"})]
|
|
14
|
+
out += [json.dumps(e) for e in extra]
|
|
15
|
+
if text is not None:
|
|
16
|
+
out.append(json.dumps({"type": "item.completed",
|
|
17
|
+
"item": {"id": "item_0", "type": "agent_message", "text": text}}))
|
|
18
|
+
out.append(json.dumps({"type": "turn.completed", "usage": u}))
|
|
19
|
+
return out
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def home(tmp_path, payload='{"auth_mode": "chatgpt", "OPENAI_API_KEY": null}'):
|
|
23
|
+
if payload is not None:
|
|
24
|
+
(tmp_path / "auth.json").write_text(payload, encoding="utf-8")
|
|
25
|
+
return {"CODEX_HOME": str(tmp_path), "PATH": "/usr/bin"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def recording_runner(stdout="", stderr=""):
|
|
29
|
+
seen = {"calls": 0}
|
|
30
|
+
|
|
31
|
+
def runner(argv, input, capture_output, text, env, timeout, cwd):
|
|
32
|
+
seen.update(argv=argv, input=input, env=env, cwd=cwd, calls=seen["calls"] + 1)
|
|
33
|
+
return types.SimpleNamespace(returncode=0, stdout=stdout, stderr=stderr)
|
|
34
|
+
|
|
35
|
+
return runner, seen
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_argv_isolation_flags():
|
|
39
|
+
a = C.build_argv("gpt-5.6-sol")
|
|
40
|
+
for flag in ("--json", "--ephemeral", "--skip-git-repo-check", "--ignore-user-config", "--ignore-rules"):
|
|
41
|
+
assert flag in a
|
|
42
|
+
assert a[a.index("--sandbox") + 1] == "read-only"
|
|
43
|
+
assert a[a.index("--model") + 1] == "gpt-5.6-sol"
|
|
44
|
+
assert a[-1] == "-" # prompt is read from stdin, never argv
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_clean_env_strips_keys():
|
|
48
|
+
e = C.clean_env({"OPENAI_API_KEY": "k", "CODEX_API_KEY": "c", "SWARPH_CHANNEL": "dev",
|
|
49
|
+
"SWARPH_CHANNEL_CELL": "droplet", "PATH": "/usr/bin", "CODEX_HOME": "/h"})
|
|
50
|
+
assert set(e) == {"PATH", "CODEX_HOME"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_parse_ok():
|
|
54
|
+
r = C.parse_stream(lines())
|
|
55
|
+
assert r.text == '{"answer": 68}' and r.tokens_in == 14410 and r.tokens_out == 10
|
|
56
|
+
assert r.error is None and r.cost_equiv is None and r.api_key_source == "chatgpt"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_parse_takes_the_last_message_and_the_last_usage():
|
|
60
|
+
first = {"type": "item.completed", "item": {"type": "agent_message", "text": "draft"}}
|
|
61
|
+
early = {"type": "turn.completed", "usage": {"input_tokens": 5, "output_tokens": 1}}
|
|
62
|
+
r = C.parse_stream(lines(extra=(first, early)))
|
|
63
|
+
assert r.text == '{"answer": 68}' and r.tokens_in == 14410 # running total, never summed
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_tool_items_are_not_the_answer():
|
|
67
|
+
tool = {"type": "item.completed", "item": {"type": "command_execution", "text": "ls"}}
|
|
68
|
+
r = C.parse_stream(lines(extra=(tool,)))
|
|
69
|
+
assert r.text == '{"answer": 68}'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_no_answer_is_no_result_not_an_empty_answer():
|
|
73
|
+
r = C.parse_stream(lines(text=None))
|
|
74
|
+
assert r.error == "no_result" and r.text == ""
|
|
75
|
+
assert C.parse_stream([]).error == "no_result"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_rate_limit_is_retryable_from_event_or_stderr():
|
|
79
|
+
ev = {"type": "error", "message": "You've hit your usage limit. Try again later."}
|
|
80
|
+
assert C.parse_stream([json.dumps(ev)]).error == "rate_limit"
|
|
81
|
+
assert C.parse_stream([], stderr="ERROR: You've hit your usage limit").error == "rate_limit"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_other_failure_is_a_cli_error_and_discards_the_text():
|
|
85
|
+
ev = {"type": "turn.failed", "error": {"message": "model not found"}}
|
|
86
|
+
r = C.parse_stream(lines(extra=(ev,)))
|
|
87
|
+
assert r.error == "cli_error:turn.failed" and r.text == ""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_call_passes_system_and_prompt_on_stdin_in_an_empty_cwd(tmp_path):
|
|
91
|
+
runner, seen = recording_runner(stdout="\n".join(lines()))
|
|
92
|
+
env = dict(home(tmp_path), OPENAI_API_KEY="k")
|
|
93
|
+
r = C.call("gpt-5.6-sol", "PROMPT TEXT", "SYS", runner=runner, env=env)
|
|
94
|
+
assert r.error is None and r.text == '{"answer": 68}'
|
|
95
|
+
assert seen["input"] == "SYS\n\nPROMPT TEXT" and "PROMPT TEXT" not in seen["argv"]
|
|
96
|
+
assert "OPENAI_API_KEY" not in seen["env"] and seen["env"]["CODEX_HOME"] == str(tmp_path)
|
|
97
|
+
assert seen["cwd"] != str(tmp_path) and "swarph-bench-codex-" in seen["cwd"]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_billing_guard_refuses_before_any_call(tmp_path):
|
|
101
|
+
runner, seen = recording_runner(stdout="\n".join(lines()))
|
|
102
|
+
r = C.call("gpt-5.6-sol", "P", "S", runner=runner,
|
|
103
|
+
env=home(tmp_path, '{"auth_mode": "apikey", "OPENAI_API_KEY": "k"}'))
|
|
104
|
+
assert r.error == "billing:apikey" and r.text == "" and seen["calls"] == 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_unreadable_auth_is_unknown_never_a_pass(tmp_path):
|
|
108
|
+
runner, seen = recording_runner(stdout="\n".join(lines()))
|
|
109
|
+
for payload in (None, "not json", '{"OPENAI_API_KEY": null}', '["chatgpt"]'):
|
|
110
|
+
d = tmp_path / str(abs(hash(payload)))
|
|
111
|
+
d.mkdir()
|
|
112
|
+
r = C.call("gpt-5.6-sol", "P", "S", runner=runner, env=home(d, payload))
|
|
113
|
+
assert r.error == "billing:unknown", payload
|
|
114
|
+
assert seen["calls"] == 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_backend_reports_real_usage_and_carries_the_error(tmp_path):
|
|
118
|
+
runner, _ = recording_runner(stdout="\n".join(lines()))
|
|
119
|
+
ok = B.SubscriptionBackend(
|
|
120
|
+
call_fn=lambda m, p, s: C.call(m, p, s, runner=runner, env=home(tmp_path)))
|
|
121
|
+
res = ok.generate("gpt-5.6-sol", "P", "S")
|
|
122
|
+
assert res.text == '{"answer": 68}' and res.tokens_in == 14410 and res.estimated is False
|
|
123
|
+
|
|
124
|
+
bad = tmp_path / "bad"
|
|
125
|
+
bad.mkdir()
|
|
126
|
+
refused = B.SubscriptionBackend(
|
|
127
|
+
call_fn=lambda m, p, s: C.call(m, p, s, runner=runner, env=home(bad, None)))
|
|
128
|
+
res = refused.generate("gpt-5.6-sol", "P", "S")
|
|
129
|
+
assert res.error == "billing:unknown" and res.text == ""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_codex_lane_is_selectable(monkeypatch):
|
|
133
|
+
from swarph_cli.commands import bench
|
|
134
|
+
|
|
135
|
+
monkeypatch.setattr(bench.shutil, "which", lambda name: None)
|
|
136
|
+
assert bench._default_backends()["codex"].missing_creds() # stub: preflight skips it
|
|
137
|
+
monkeypatch.setattr(bench.shutil, "which", lambda name: "/usr/bin/" + name)
|
|
138
|
+
assert bench._default_backends()["codex"].missing_creds() == []
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|