cli-agent-runner 0.2.21__tar.gz → 0.2.23__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.
- cli_agent_runner-0.2.23/.github/dependabot.yml +6 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/workflows/ci.yml +4 -4
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/workflows/release.yml +11 -7
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.vulture-whitelist.py +18 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/BACKLOG.md +0 -27
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/CHANGELOG.md +28 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/PKG-INFO +2 -2
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/README.md +1 -1
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/SECURITY.md +17 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_emit/__init__.py +2 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_emit/serve.py +20 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_monitor_state.py +26 -6
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/agent_runtime.py +52 -23
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/api.py +36 -6
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/api_types.py +5 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/__init__.py +2 -0
- cli_agent_runner-0.2.23/agent_runner/cli/doctor_cmd.py +77 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/serve_cmd.py +11 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/models.py +11 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/parsers.py +3 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/defenses.py +6 -2
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/lifecycle.py +27 -12
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/phase_select.py +63 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/claude.toml +1 -1
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/codewhale.toml +3 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/pi.toml +3 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/runner.py +5 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/startup_check.py +127 -49
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/architecture.md +1 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/commands.md +29 -4
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/configuration.md +16 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/memory-efficiency.md +48 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/codewhale.md +3 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/container-pi.md +16 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/runbook.md +6 -4
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/contract/test_public_api_surface.py +1 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_serve_loop.py +63 -2
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_architecture.py +11 -2
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_import_footprint.py +5 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_container.py +118 -8
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_grace.py +102 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_service.py +154 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config.py +28 -0
- cli_agent_runner-0.2.23/tests/unit/test_doctor_cmd.py +86 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_events.py +4 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_lifecycle.py +31 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_assembly.py +29 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_phase_select.py +115 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_startup_check.py +114 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/.gitignore +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/LICENSE +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/README.zh.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_emit/memory.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/_serve_round.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/build.sh +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/README.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/events.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/pyproject.toml +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.21 → cli_agent_runner-0.2.23}/tests/unit/test_vcs_state.py +0 -0
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
timeout-minutes: 2
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@v5
|
|
21
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
22
22
|
with:
|
|
23
23
|
# PR: scan only new commits the PR introduces (base..head).
|
|
24
24
|
# Push to main: scan the just-pushed commits (before..after).
|
|
@@ -74,10 +74,10 @@ jobs:
|
|
|
74
74
|
# granularity AND because coverage requires extra pytest flags not in
|
|
75
75
|
# the local `test` task. CONTRIBUTING.md states the parity intentionally.
|
|
76
76
|
steps:
|
|
77
|
-
- uses: actions/checkout@v5
|
|
77
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
78
78
|
with:
|
|
79
79
|
fetch-depth: 0
|
|
80
|
-
- uses: actions/setup-python@v6
|
|
80
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
81
81
|
with:
|
|
82
82
|
python-version: ${{ matrix.python }}
|
|
83
83
|
cache: pip
|
|
@@ -108,7 +108,7 @@ jobs:
|
|
|
108
108
|
run: pytest tests/literate/ -q
|
|
109
109
|
- name: upload coverage
|
|
110
110
|
if: matrix.python == '3.12' && matrix.os == 'ubuntu-latest'
|
|
111
|
-
uses: codecov/codecov-action@v6
|
|
111
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
|
|
112
112
|
with:
|
|
113
113
|
files: ./coverage.xml
|
|
114
114
|
fail_ci_if_error: false
|
|
@@ -26,10 +26,10 @@ jobs:
|
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
27
|
timeout-minutes: 10
|
|
28
28
|
steps:
|
|
29
|
-
- uses: actions/checkout@v5
|
|
29
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 0
|
|
32
|
-
- uses: actions/setup-python@v6
|
|
32
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
33
33
|
with:
|
|
34
34
|
python-version: "3.12"
|
|
35
35
|
- name: install build
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
|
38
38
|
run: python -m build
|
|
39
39
|
- name: list dist artifacts
|
|
40
40
|
run: ls -la dist/
|
|
41
|
-
- uses: actions/upload-artifact@v7
|
|
41
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
42
42
|
with:
|
|
43
43
|
name: dist
|
|
44
44
|
path: dist/
|
|
@@ -53,11 +53,15 @@ jobs:
|
|
|
53
53
|
permissions:
|
|
54
54
|
id-token: write
|
|
55
55
|
steps:
|
|
56
|
-
- uses: actions/download-artifact@v8
|
|
56
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
57
57
|
with:
|
|
58
58
|
name: dist
|
|
59
59
|
path: dist/
|
|
60
|
-
-
|
|
60
|
+
# SHA-pinned to release/v1's composite. NOTE: this pins the composite source;
|
|
61
|
+
# the image it pulls is still referenced by a moving ghcr tag, so a full pin
|
|
62
|
+
# would require the ghcr digest — deferred (needs read:packages + release-time
|
|
63
|
+
# validation). dependabot (github-actions) bumps this SHA.
|
|
64
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
61
65
|
with:
|
|
62
66
|
attestations: true
|
|
63
67
|
|
|
@@ -68,8 +72,8 @@ jobs:
|
|
|
68
72
|
permissions:
|
|
69
73
|
contents: write
|
|
70
74
|
steps:
|
|
71
|
-
- uses: actions/checkout@v5
|
|
72
|
-
- uses: actions/download-artifact@v8
|
|
75
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
76
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
73
77
|
with:
|
|
74
78
|
name: dist
|
|
75
79
|
path: dist/
|
|
@@ -110,6 +110,7 @@ pid
|
|
|
110
110
|
uptime_s
|
|
111
111
|
unit_file
|
|
112
112
|
rate_limit
|
|
113
|
+
system_managed
|
|
113
114
|
|
|
114
115
|
# agent_runner.api_types.SystemMetrics
|
|
115
116
|
mem_total_mb
|
|
@@ -127,6 +128,11 @@ agent
|
|
|
127
128
|
since_round
|
|
128
129
|
phase
|
|
129
130
|
|
|
131
|
+
# agent_runner.cli.doctor_cmd.DoctorReport
|
|
132
|
+
checks
|
|
133
|
+
overlaps
|
|
134
|
+
plan
|
|
135
|
+
|
|
130
136
|
# agent_runner.config.models.AgentConfig
|
|
131
137
|
command
|
|
132
138
|
prompt_arg_template
|
|
@@ -134,6 +140,7 @@ name
|
|
|
134
140
|
env
|
|
135
141
|
prompt_delivery
|
|
136
142
|
exec_prefix
|
|
143
|
+
terminal_marker
|
|
137
144
|
|
|
138
145
|
# agent_runner.config.models.Config
|
|
139
146
|
agent
|
|
@@ -295,6 +302,12 @@ resume_timezone
|
|
|
295
302
|
skipped
|
|
296
303
|
active_window
|
|
297
304
|
|
|
305
|
+
# agent_runner.phase_select.WindowOverlap
|
|
306
|
+
phase_a
|
|
307
|
+
phase_b
|
|
308
|
+
window_a
|
|
309
|
+
window_b
|
|
310
|
+
|
|
298
311
|
# agent_runner.round_log.PruneOutcome
|
|
299
312
|
deleted
|
|
300
313
|
deferred
|
|
@@ -318,6 +331,11 @@ reason
|
|
|
318
331
|
how_to_fix
|
|
319
332
|
permanent
|
|
320
333
|
|
|
334
|
+
# agent_runner.startup_check.CheckSpec
|
|
335
|
+
kind
|
|
336
|
+
scope
|
|
337
|
+
run
|
|
338
|
+
|
|
321
339
|
# agent_runner.vcs_state.StashRef
|
|
322
340
|
sha
|
|
323
341
|
message
|
|
@@ -53,18 +53,6 @@ kind name is the version discriminator; a breaking change ships as
|
|
|
53
53
|
`round_start_v2`). Reaching this failure requires a contract violation — but the
|
|
54
54
|
suite would not report it.
|
|
55
55
|
|
|
56
|
-
## `remote_failure_tolerance_s` does not cover `subprocess.TimeoutExpired` — NEEDS_DESIGN
|
|
57
|
-
|
|
58
|
-
The tolerance window covers ssh exit code 255 only. `monitor.run_remote_command`
|
|
59
|
-
raises `MonitorRemoteError` on rc=255, and `api.monitor_loop`'s retry loop
|
|
60
|
-
catches exactly that. A hung (not failed) ssh connection instead trips the
|
|
61
|
-
`subprocess.run(timeout=...)` guard and raises `subprocess.TimeoutExpired`,
|
|
62
|
-
which bypasses the window and kills the monitor.
|
|
63
|
-
|
|
64
|
-
The rc=255-only scope is intentional and pinned by the monitor-signals design,
|
|
65
|
-
and the documentation matches the code exactly — there is no drift here. Widening
|
|
66
|
-
it changes a documented recovery path, so it is a design decision, not debt.
|
|
67
|
-
|
|
68
56
|
## `anomaly_repetitive_*` default — NEEDS_DESIGN
|
|
69
57
|
|
|
70
58
|
`monitor.anomaly_repetitive_window` and `anomaly_repetitive_threshold` both
|
|
@@ -89,21 +77,6 @@ Deferred because closing it is a naming decision, not cleanup: no event kind
|
|
|
89
77
|
carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
|
|
90
78
|
— the stash exists). Left as-is until that kind is designed.
|
|
91
79
|
|
|
92
|
-
## `_monitor_loop_iter`'s three fail-open guards are hand-duplicated
|
|
93
|
-
|
|
94
|
-
`api._monitor_loop_iter` wraps the startup `monitor_started` emit, the
|
|
95
|
-
per-tick poll, and `on_alert`'s per-alert dispatch each in its own
|
|
96
|
-
`try/except Exception: warnings.warn(...)` block so a crash in any one of the
|
|
97
|
-
three never ends monitor supervision. The three guards are structurally
|
|
98
|
-
identical (catch, format a `type(e).__name__: {e}` warning, keep the loop
|
|
99
|
-
alive) but hand-copied rather than sharing one implementation; `on_alert`'s
|
|
100
|
-
own `emit()` closure already shows the pattern for factoring a shared guard.
|
|
101
|
-
|
|
102
|
-
Collapsing them to one shared guard is behavior-preserving — it must not
|
|
103
|
-
change which failures warn vs which propagate, nor the specific fallback
|
|
104
|
-
each site takes on failure (sleep-and-retry after a poll failure,
|
|
105
|
-
`verdict = "failed"` after an `on_alert` failure). Slated for 0.2.19.
|
|
106
|
-
|
|
107
80
|
## `oom_kill_delta` is named differently on its two events — NEEDS_DESIGN, DEFERRED to 0.3
|
|
108
81
|
|
|
109
82
|
The same cgroup `memory.events.oom_kill` delta is emitted under two
|
|
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.23] - 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[agent] terminal_marker` — per-preset grace-kill result marker (default = Claude's token; pi/codewhale set their own; kimi/aider have no marker and run to the wall-clock ceiling).
|
|
12
|
+
- `doctor` — read-only pre-flight (never launches the agent): startup checks, phase-window overlap warnings, and an N-round phase-plan preview.
|
|
13
|
+
- `phase_window_overlap` event — emitted at serve boot when two agent-overriding phases' windows collide in the same timezone.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Startup-check dispatch and the catalog's kind list now derive from a single descriptor table (no behavior change).
|
|
17
|
+
- The lock-holder pid create_time verify now shares one `lifecycle` helper with `serve.pid` (no behavior change).
|
|
18
|
+
- Default `claude` preset model updated `claude-opus-4-7` → `claude-opus-5`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `peek` no longer prints spurious "cannot coerce None" warnings on a freshly-initialized project; absent metric samples fall back to defaults silently (a genuinely malformed value still warns).
|
|
22
|
+
|
|
23
|
+
## [0.2.22] - 2026-09-11
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Container safety: agent-runner now refuses to boot when `runtime.log_dir` resolves inside a containerized `work_dir` — a contained agent could otherwise rewrite the pid/lock/cidfile control files the supervisor trusts. The best-effort container stop also validates the recovered container id (hex, length-bounded) and passes an end-of-options separator, and the injected `--cidfile` moves to a host-private directory, so a poisoned cidfile can no longer stop an unrelated container.
|
|
27
|
+
- `kill` / `restart --force` verify the lock-holder's start time before signaling, so a recycled pid is never sent a signal.
|
|
28
|
+
- System mode (`install --system`): `restart` / `uninstall` recognize a system unit and refuse with the exact `sudo systemctl …` command instead of a misleading one, and `uninstall` no longer aborts with an uncaught error on a host without a systemd user bus.
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
- Release and CI GitHub Actions are pinned to commit SHAs, with Dependabot tracking updates.
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
- Not breaking: no `agent-runner migrate` step, no systemd unit change. `status --json` gains a `system_managed` field; existing fields unchanged. The one newly-rejected configuration is a containerized agent whose `runtime.log_dir` sits inside `work_dir` — move `log_dir` outside `work_dir` (the default already is).
|
|
35
|
+
|
|
8
36
|
## [0.2.21] - 2026-09-11
|
|
9
37
|
|
|
10
38
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
4
4
|
Summary: Restart-on-exit supervisor for autonomous CLI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
|
|
6
6
|
Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
|
|
@@ -82,7 +82,7 @@ agent-runner monitor # live anomaly detection
|
|
|
82
82
|
|
|
83
83
|
Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
84
84
|
|
|
85
|
-
##
|
|
85
|
+
## 17 verbs
|
|
86
86
|
|
|
87
87
|
| Lifecycle | Observation |
|
|
88
88
|
|---|---|
|
|
@@ -57,6 +57,23 @@ a narrower safety net than the host-level reaping a bare agent process gets,
|
|
|
57
57
|
not a wider one — keep running it as a dedicated unprivileged user with no
|
|
58
58
|
passwordless sudo and tight egress limits regardless.
|
|
59
59
|
|
|
60
|
+
**Residual: `agent-runner.toml` lives inside the mount.** The startup check
|
|
61
|
+
above stops the agent's pid and lock files from sitting inside a mounted
|
|
62
|
+
`work_dir` (the injected cidfile is separately placed in a host-private temp
|
|
63
|
+
dir outside any mount, so it's protected independently of `log_dir`) — but
|
|
64
|
+
`agent-runner.toml` itself is scaffolded at the `work_dir` root, and the
|
|
65
|
+
container recipes in
|
|
66
|
+
[docs/recipes/container-pi.md](docs/recipes/container-pi.md) bind-mount all
|
|
67
|
+
of `work_dir` read-write. Each round re-reads the config, so a containerized
|
|
68
|
+
agent that can write its own mount can rewrite `[agent] command` /
|
|
69
|
+
`exec_prefix` — or `runtime.stop_file`, if it's pointed inside the mount —
|
|
70
|
+
and have it run on the host next round. This is consistent with the threat
|
|
71
|
+
model above: agent-runner bounds a *misbehaving* agent, not a *hostile* one,
|
|
72
|
+
and containment is the container/VM's job, not agent-runner's. An operator
|
|
73
|
+
who needs to contain a not-fully-trusted agent should keep the config (and
|
|
74
|
+
`runtime.stop_file`) outside any bind-mount, or otherwise treat the
|
|
75
|
+
container boundary as their own responsibility to shape.
|
|
76
|
+
|
|
60
77
|
## Scope
|
|
61
78
|
|
|
62
79
|
In scope: the `agent_runner` Python package, its CLI, its bundled systemd
|
|
@@ -57,6 +57,7 @@ from agent_runner._emit.serve import ( # noqa: F401 — public re-export
|
|
|
57
57
|
emit_max_rounds_reached,
|
|
58
58
|
emit_mem_loop,
|
|
59
59
|
emit_mem_loop_persistent,
|
|
60
|
+
emit_phase_window_overlap,
|
|
60
61
|
emit_rate_limit_stop,
|
|
61
62
|
emit_schedule_paused,
|
|
62
63
|
emit_schedule_phase_skipped,
|
|
@@ -78,6 +79,7 @@ __all__ = [
|
|
|
78
79
|
"emit_mem_loop",
|
|
79
80
|
"emit_mem_loop_persistent",
|
|
80
81
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
82
|
+
"emit_phase_window_overlap",
|
|
81
83
|
"emit_rate_limit_stop",
|
|
82
84
|
"emit_round_cgroup_memory",
|
|
83
85
|
"emit_round_container_orphan_risk",
|
|
@@ -191,3 +191,23 @@ def emit_schedule_resumed(log_dir: Path, *, paused_for_s: int) -> None:
|
|
|
191
191
|
from agent_runner.events import SCHEDULE_RESUMED, emit
|
|
192
192
|
|
|
193
193
|
emit(log_dir, SCHEDULE_RESUMED, paused_for_s=paused_for_s)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def emit_phase_window_overlap(
|
|
197
|
+
log_dir: Path, *, phase_a: str, phase_b: str, window_a: str, window_b: str
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Emit phase_window_overlap: two agent-overriding phases' own run_windows
|
|
200
|
+
collide (a rotation footgun; a later release promotes this exact detection
|
|
201
|
+
to a hard error). Called once at serve boot -- the collision is a property of the
|
|
202
|
+
static config, not of any one round, so (like emit_max_rounds_reached's
|
|
203
|
+
cgroup-probe sibling) this must not be called from the per-round path."""
|
|
204
|
+
from agent_runner.events import PHASE_WINDOW_OVERLAP, emit
|
|
205
|
+
|
|
206
|
+
emit(
|
|
207
|
+
log_dir,
|
|
208
|
+
PHASE_WINDOW_OVERLAP,
|
|
209
|
+
phase_a=phase_a,
|
|
210
|
+
phase_b=phase_b,
|
|
211
|
+
window_a=window_a,
|
|
212
|
+
window_b=window_b,
|
|
213
|
+
)
|
|
@@ -154,21 +154,41 @@ def _latest_metric_dict(metrics: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
154
154
|
return metrics[-1] if metrics else {}
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def _metric_int(latest: dict[str, Any], key: str, default: int) -> int:
|
|
158
|
+
"""``_coerce_int`` for a metric field, silent on absence.
|
|
159
|
+
|
|
160
|
+
A fresh project has no metric events yet, so ``latest`` is ``{}`` and
|
|
161
|
+
every field reads as None -- "no sample yet", not malformed data. Only a
|
|
162
|
+
present-but-non-numeric value should still warn (delegated to
|
|
163
|
+
``_coerce_int``, which owns that contract)."""
|
|
164
|
+
from agent_runner._throttle import _coerce_int
|
|
165
|
+
|
|
166
|
+
value = latest.get(key)
|
|
167
|
+
return default if value is None else _coerce_int(value, default)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _metric_float(latest: dict[str, Any], key: str, default: float) -> float:
|
|
171
|
+
"""Float sibling of ``_metric_int`` -- see its docstring."""
|
|
172
|
+
from agent_runner._throttle import _coerce_float
|
|
173
|
+
|
|
174
|
+
value = latest.get(key)
|
|
175
|
+
return default if value is None else _coerce_float(value, default)
|
|
176
|
+
|
|
177
|
+
|
|
157
178
|
def assemble_project_state(source: LocalSource, *, project: str) -> ProjectState:
|
|
158
179
|
metrics = parse_events_from_jsonl_files(source.metrics_files())
|
|
159
180
|
status = read_json(source.status_path()) or {}
|
|
160
181
|
orphan = read_json(source.orphan_path())
|
|
161
182
|
latest = _latest_metric_dict(metrics)
|
|
162
|
-
from agent_runner._throttle import _coerce_float, _coerce_int
|
|
163
183
|
|
|
164
184
|
system = SystemMetrics(
|
|
165
|
-
mem_total_mb=
|
|
166
|
-
mem_available_mb=
|
|
167
|
-
disk_used_pct=
|
|
168
|
-
disk_free_gb=
|
|
185
|
+
mem_total_mb=_metric_int(latest, "mem_total_mb", 0),
|
|
186
|
+
mem_available_mb=_metric_int(latest, "mem_available_mb", 0),
|
|
187
|
+
disk_used_pct=_metric_float(latest, "disk_used_pct", 0.0),
|
|
188
|
+
disk_free_gb=_metric_float(latest, "disk_free_gb", 0.0),
|
|
169
189
|
load_1m=latest.get("load_1m"),
|
|
170
190
|
cpu_pct=latest.get("cpu_pct"),
|
|
171
|
-
agent_process_count=
|
|
191
|
+
agent_process_count=_metric_int(latest, "agent_process_count", 0),
|
|
172
192
|
)
|
|
173
193
|
return ProjectState(
|
|
174
194
|
project=project,
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.23'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 23)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -14,6 +14,7 @@ import re
|
|
|
14
14
|
import shutil
|
|
15
15
|
import signal
|
|
16
16
|
import subprocess # noqa: TID251 — sanctioned subprocess caller
|
|
17
|
+
import tempfile
|
|
17
18
|
import threading
|
|
18
19
|
from collections.abc import Callable
|
|
19
20
|
from dataclasses import dataclass
|
|
@@ -246,11 +247,6 @@ def _live_children(
|
|
|
246
247
|
return live, ignored
|
|
247
248
|
|
|
248
249
|
|
|
249
|
-
# Exact compact bytes — matches claude CLI's no-whitespace JSONL output.
|
|
250
|
-
# A future CLI variant emitting `{"type": "result", ...}` (with space) would
|
|
251
|
-
# bypass this scan; revisit if that happens.
|
|
252
|
-
_RESULT_MARKER = b'"type":"result"'
|
|
253
|
-
|
|
254
250
|
# Decoupled from the 0.2s poll tick: the marker scan only needs to notice the
|
|
255
251
|
# result within max_grace_after_result_s (seconds, integer), so scanning more
|
|
256
252
|
# often than this buys nothing but re-read cost on a growing round log.
|
|
@@ -290,6 +286,14 @@ def resolve_exec_target(command0: str, work_dir: Path, env_path: str | None = No
|
|
|
290
286
|
# ladder) is out of scope here -- left for a future release.
|
|
291
287
|
_CONTAINER_RUNTIMES = frozenset({"docker", "podman"})
|
|
292
288
|
|
|
289
|
+
# A real docker/podman container id is hex, 12-64 chars (short or full form).
|
|
290
|
+
# The cidfile is a trust-bearing control input -- validated before it's ever
|
|
291
|
+
# handed to a `stop` subprocess argv.
|
|
292
|
+
_CONTAINER_ID_RE = re.compile(r"^[0-9a-fA-F]{12,64}$")
|
|
293
|
+
# A real container id is <=64 hex chars; cap the read so a poisoned giant
|
|
294
|
+
# file can't OOM a constrained host.
|
|
295
|
+
_CIDFILE_READ_CAP = 128
|
|
296
|
+
|
|
293
297
|
# docker/podman GLOBAL flags (before the subcommand) that take a separate
|
|
294
298
|
# value token -- skipped in pairs so `docker -H unix:///var/run/docker.sock
|
|
295
299
|
# run ...` / `docker --context foo run ...` still resolve to `run`. Not
|
|
@@ -507,14 +511,18 @@ def _best_effort_container_stop(
|
|
|
507
511
|
if cidfile is None:
|
|
508
512
|
return None, None
|
|
509
513
|
try:
|
|
510
|
-
|
|
514
|
+
with cidfile.open("rb") as fh:
|
|
515
|
+
raw = fh.read(_CIDFILE_READ_CAP + 1)
|
|
511
516
|
except OSError:
|
|
512
517
|
return None, None
|
|
513
|
-
if
|
|
514
|
-
return None, None
|
|
518
|
+
if len(raw) > _CIDFILE_READ_CAP:
|
|
519
|
+
return None, None # oversize — not a real container id
|
|
520
|
+
cid = raw.decode("utf-8", "replace").strip()
|
|
521
|
+
if not _CONTAINER_ID_RE.match(cid):
|
|
522
|
+
return None, None # not a hex id — detection already fired the loud warn + event
|
|
515
523
|
try:
|
|
516
524
|
result = subprocess.run(
|
|
517
|
-
[runtime, "stop", "-t", str(REAP_GRACE_S), cid],
|
|
525
|
+
[runtime, "stop", "-t", str(REAP_GRACE_S), "--", cid],
|
|
518
526
|
stdout=subprocess.DEVNULL,
|
|
519
527
|
stderr=subprocess.PIPE,
|
|
520
528
|
timeout=_CONTAINER_STOP_TIMEOUT_S,
|
|
@@ -560,6 +568,7 @@ def run(
|
|
|
560
568
|
log_path: Path,
|
|
561
569
|
env_extra: dict[str, str],
|
|
562
570
|
max_grace_after_result_s: int = 0,
|
|
571
|
+
terminal_marker: str = '"type":"result"', # mirrors config.DEFAULT_TERMINAL_MARKER
|
|
563
572
|
progress_callback: Callable[[dict], None] | None = None,
|
|
564
573
|
progress_interval_s: int = 0,
|
|
565
574
|
on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
|
|
@@ -575,13 +584,19 @@ def run(
|
|
|
575
584
|
already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
|
|
576
585
|
own (e.g. pi) depend on this.
|
|
577
586
|
|
|
578
|
-
max_grace_after_result_s: when > 0, start a countdown after
|
|
579
|
-
|
|
587
|
+
max_grace_after_result_s: when > 0, start a countdown after
|
|
588
|
+
``terminal_marker`` is first detected in the log. After it elapses, reap the
|
|
580
589
|
process group only if the agent has no live worker processes left (a
|
|
581
590
|
genuine hang). If a worker is still running (e.g. a backgrounded build),
|
|
582
591
|
do not reap — invoke ``on_grace_extended`` once and keep waiting until the
|
|
583
592
|
round finishes or hits the wall-clock ``timeout_s`` ceiling. 0 = disabled.
|
|
584
593
|
|
|
594
|
+
terminal_marker: exact compact byte substring the grace-kill scan looks
|
|
595
|
+
for (default matches claude CLI's no-whitespace JSONL ``{"type":"result",
|
|
596
|
+
...}`` output). An empty string disables the marker scan outright, even
|
|
597
|
+
with max_grace_after_result_s > 0 (honest opt-out, not an
|
|
598
|
+
empty-substring-matches-everything trap).
|
|
599
|
+
|
|
585
600
|
progress_callback: when not None and progress_interval_s > 0, called every
|
|
586
601
|
progress_interval_s seconds with a dict of log stats (log_size_kb,
|
|
587
602
|
last_write_age_s, wall_age_s). Keeps agent_runtime event-free; callers
|
|
@@ -606,6 +621,12 @@ def run(
|
|
|
606
621
|
attempt where covered; it just isn't reported).
|
|
607
622
|
"""
|
|
608
623
|
stdin_mode = prompt_delivery == "stdin"
|
|
624
|
+
marker_bytes = terminal_marker.encode("utf-8")
|
|
625
|
+
# Raising work (mkdir can fail on disk-full/perms/removed log_dir) must
|
|
626
|
+
# happen BEFORE the container-detection block below creates the
|
|
627
|
+
# host-private cidfile tempdir -- otherwise a raise here would skip the
|
|
628
|
+
# try/finally that cleans that tempdir up, leaking it once per round.
|
|
629
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
609
630
|
# Container-orphan defense: detect BEFORE building argv (spawn_command may
|
|
610
631
|
# gain an injected --cidfile) -- see _detect_container_run's module-level
|
|
611
632
|
# note. A non-container command's argv is byte-identical to before:
|
|
@@ -638,8 +659,13 @@ def run(
|
|
|
638
659
|
# no-silent-orphan floor.
|
|
639
660
|
pass
|
|
640
661
|
else:
|
|
641
|
-
|
|
642
|
-
|
|
662
|
+
# A supervisor control file must not live where the container can
|
|
663
|
+
# write it; put our injected cidfile in a host-private 0700 dir
|
|
664
|
+
# outside any mount. Docker writes the file host-side; the
|
|
665
|
+
# container never needs it.
|
|
666
|
+
container_cid_dir = Path(tempfile.mkdtemp(prefix="agent-runner-cid-"))
|
|
667
|
+
# Not created here — docker/podman refuse an existing cidfile.
|
|
668
|
+
container_cidfile = container_cid_dir / "round.cid"
|
|
643
669
|
container_cidfile_is_ours = True
|
|
644
670
|
spawn_command = _inject_cidfile(command, run_idx, container_cidfile)
|
|
645
671
|
|
|
@@ -669,7 +695,6 @@ def run(
|
|
|
669
695
|
# PWD pinned last — it mirrors cwd= (a correctness pin, not a knob), so
|
|
670
696
|
# an [agent.env] PWD cannot silently diverge from where the child runs.
|
|
671
697
|
env = {**os.environ, **env_extra, "PWD": str(work_dir)}
|
|
672
|
-
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
673
698
|
proc: subprocess.Popen | None = None
|
|
674
699
|
log_file = None
|
|
675
700
|
try:
|
|
@@ -727,7 +752,7 @@ def run(
|
|
|
727
752
|
exit_code=exit_code, duration_s=duration, timed_out=True, pid=proc.pid
|
|
728
753
|
)
|
|
729
754
|
# Grace kill: result emitted but subprocess still running.
|
|
730
|
-
if max_grace_after_result_s > 0:
|
|
755
|
+
if max_grace_after_result_s > 0 and marker_bytes:
|
|
731
756
|
if result_seen_at is None and now - last_result_scan >= _RESULT_SCAN_INTERVAL_S:
|
|
732
757
|
last_result_scan = now
|
|
733
758
|
try:
|
|
@@ -736,12 +761,16 @@ def run(
|
|
|
736
761
|
chunk = f.read()
|
|
737
762
|
result_scan_offset += len(chunk)
|
|
738
763
|
haystack = result_scan_carry + chunk
|
|
739
|
-
if
|
|
764
|
+
if marker_bytes in haystack:
|
|
740
765
|
result_seen_at = now
|
|
741
766
|
else:
|
|
742
767
|
# Keep the tail so a marker split across two read
|
|
743
768
|
# chunks (this scan vs. the next) still re-forms.
|
|
744
|
-
|
|
769
|
+
# A 1-byte marker can't be split, so carry nothing
|
|
770
|
+
# -- haystack[-0:] is the WHOLE buffer, not empty,
|
|
771
|
+
# so the zero case needs its own branch.
|
|
772
|
+
_carry = len(marker_bytes) - 1
|
|
773
|
+
result_scan_carry = haystack[-_carry:] if _carry else b""
|
|
745
774
|
except OSError:
|
|
746
775
|
pass # log not flushed yet; retry next interval
|
|
747
776
|
if result_seen_at is not None and now - result_seen_at > max_grace_after_result_s:
|
|
@@ -805,10 +834,10 @@ def run(
|
|
|
805
834
|
finally:
|
|
806
835
|
if log_file is not None:
|
|
807
836
|
log_file.close()
|
|
808
|
-
# Cleanup, not correctness: our own injected cidfile
|
|
809
|
-
# round-scoped
|
|
810
|
-
# (read-back for the best-effort stop, if any, already
|
|
811
|
-
# above). Only ours: an operator-supplied --cidfile is never
|
|
812
|
-
# delete.
|
|
837
|
+
# Cleanup, not correctness: our own injected cidfile lives in a tiny,
|
|
838
|
+
# round-scoped private tempdir -- remove that whole dir once the round
|
|
839
|
+
# is fully done (read-back for the best-effort stop, if any, already
|
|
840
|
+
# happened above). Only ours: an operator-supplied --cidfile is never
|
|
841
|
+
# ours to delete.
|
|
813
842
|
if container_cidfile_is_ours and container_cidfile is not None:
|
|
814
|
-
container_cidfile.
|
|
843
|
+
shutil.rmtree(container_cidfile.parent, ignore_errors=True)
|
|
@@ -193,6 +193,11 @@ def init(
|
|
|
193
193
|
_SYSTEM_UNITS_DIR = Path("/etc/systemd/system")
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
def _system_unit_exists(project: str) -> bool:
|
|
197
|
+
"""A --system install's root-owned unit; detect_service_mode can't see it (user-scope only)."""
|
|
198
|
+
return (_SYSTEM_UNITS_DIR / serve_unit_filename(project)).exists()
|
|
199
|
+
|
|
200
|
+
|
|
196
201
|
def _install_system(
|
|
197
202
|
cfg: Config, project: str, *, config_path: Path, with_monitor: bool
|
|
198
203
|
) -> InstallResult:
|
|
@@ -283,6 +288,15 @@ def uninstall(work_dir: Path | None = None) -> bool:
|
|
|
283
288
|
if work_dir is None:
|
|
284
289
|
work_dir = Path.cwd()
|
|
285
290
|
project = _project_name(work_dir)
|
|
291
|
+
if _system_unit_exists(project): # root-owned -- don't silently no-op the user-scope dir
|
|
292
|
+
units = [serve_unit_filename(project)]
|
|
293
|
+
if (_SYSTEM_UNITS_DIR / monitor_unit_filename(project)).exists():
|
|
294
|
+
units.append(monitor_unit_filename(project))
|
|
295
|
+
remedy = " && ".join(
|
|
296
|
+
f"sudo systemctl disable --now {u} && sudo rm {_SYSTEM_UNITS_DIR / u}" for u in units
|
|
297
|
+
)
|
|
298
|
+
print(f"{project} is managed by a system unit; run: {remedy}")
|
|
299
|
+
return False
|
|
286
300
|
units_dir = lifecycle._user_systemd_dir()
|
|
287
301
|
serve = units_dir / serve_unit_filename(project)
|
|
288
302
|
monitor = units_dir / monitor_unit_filename(project)
|
|
@@ -295,7 +309,10 @@ def uninstall(work_dir: Path | None = None) -> bool:
|
|
|
295
309
|
lifecycle.stop_unit_draining(p.name, clock=SYSTEM_CLOCK, confirm_s=_PID_SIGNAL_GRACE_S)
|
|
296
310
|
_systemctl_user("disable", p.name)
|
|
297
311
|
p.unlink(missing_ok=True)
|
|
298
|
-
|
|
312
|
+
try:
|
|
313
|
+
_systemctl_user("daemon-reload")
|
|
314
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
315
|
+
pass # no user bus on this host -- units above (if any) already gone
|
|
299
316
|
return True
|
|
300
317
|
|
|
301
318
|
|
|
@@ -348,7 +365,10 @@ def _round_holder_pid(log_dir: Path) -> int | None:
|
|
|
348
365
|
pid = data.get("pid")
|
|
349
366
|
if not lifecycle._valid_pid(pid):
|
|
350
367
|
return None
|
|
351
|
-
|
|
368
|
+
if not pid_alive(pid):
|
|
369
|
+
return None
|
|
370
|
+
recorded = data.get("create_time")
|
|
371
|
+
return pid if lifecycle.create_time_matches(pid, recorded) else None
|
|
352
372
|
|
|
353
373
|
|
|
354
374
|
def _terminate_round_pid(pid: int) -> None:
|
|
@@ -435,6 +455,11 @@ def restart(project: str | Path, *, force: bool = False) -> ServiceStatus:
|
|
|
435
455
|
# SYSTEMD_USER unit, so restarting a PID_FILE/NONE service would stop it and
|
|
436
456
|
# never bring it back — the half-execution this fix eliminates.
|
|
437
457
|
pname = _resolve_project(project)
|
|
458
|
+
if _system_unit_exists(pname): # root-owned -- below's message would mislead
|
|
459
|
+
raise RuntimeError(
|
|
460
|
+
f"{pname} is managed by a system unit; run: "
|
|
461
|
+
f"sudo systemctl restart {serve_unit_filename(pname)}"
|
|
462
|
+
)
|
|
438
463
|
log_dir = _log_dir_for_project(project)
|
|
439
464
|
mode = detect_service_mode(pname, log_dir=log_dir)
|
|
440
465
|
if mode != ServiceMode.SYSTEMD_USER:
|
|
@@ -463,14 +488,18 @@ def status(project: str | Path) -> ServiceStatus:
|
|
|
463
488
|
pname = _resolve_project(project)
|
|
464
489
|
log_dir = _log_dir_for_project(project)
|
|
465
490
|
mode = detect_service_mode(pname, log_dir=log_dir)
|
|
466
|
-
if mode == ServiceMode.PID_FILE:
|
|
467
|
-
pid = PIDFile(log_dir / "serve.pid").read()
|
|
468
|
-
return ServiceStatus(mode=mode, active=pid is not None and pid_alive(pid), pid=pid)
|
|
469
491
|
if mode == ServiceMode.SYSTEMD_USER:
|
|
470
492
|
unit = lifecycle._user_systemd_dir() / serve_unit_filename(pname)
|
|
471
493
|
active = _systemd_active(serve_unit_filename(pname), log_dir)
|
|
472
494
|
return ServiceStatus(mode=mode, active=active, unit_file=unit)
|
|
473
|
-
|
|
495
|
+
# detect_service_mode only sees the user-scope unit dir; label a --system
|
|
496
|
+
# install found here (PID_FILE/NONE) via system_managed, not unit_file.
|
|
497
|
+
system_managed = _system_unit_exists(pname)
|
|
498
|
+
if mode == ServiceMode.PID_FILE:
|
|
499
|
+
pid = PIDFile(log_dir / "serve.pid").read()
|
|
500
|
+
active = pid is not None and pid_alive(pid)
|
|
501
|
+
return ServiceStatus(mode=mode, active=active, pid=pid, system_managed=system_managed)
|
|
502
|
+
return ServiceStatus(mode=ServiceMode.NONE, active=False, system_managed=system_managed)
|
|
474
503
|
|
|
475
504
|
|
|
476
505
|
def _resolve_target(project: str | Path | None) -> tuple[str, Path]:
|
|
@@ -816,6 +845,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
816
845
|
emit_mem_loop,
|
|
817
846
|
emit_mem_loop_persistent,
|
|
818
847
|
emit_mem_pressure_deferred_to_cgroup,
|
|
848
|
+
emit_phase_window_overlap,
|
|
819
849
|
emit_rate_limit_stop,
|
|
820
850
|
emit_round_cgroup_memory,
|
|
821
851
|
emit_round_container_orphan_risk,
|
|
@@ -62,6 +62,11 @@ class ServiceStatus:
|
|
|
62
62
|
uptime_s: float | None = None
|
|
63
63
|
unit_file: Path | None = None
|
|
64
64
|
rate_limit: RateLimitState | None = None
|
|
65
|
+
# True when a `--system` install's root-owned unit owns this project but
|
|
66
|
+
# detect_service_mode fell through to PID_FILE/NONE (it only sees the
|
|
67
|
+
# user-scope unit dir) -- distinct from `unit_file`, which stays the
|
|
68
|
+
# live, user-owned, CLI-restartable systemd_user unit's path.
|
|
69
|
+
system_managed: bool = False
|
|
65
70
|
|
|
66
71
|
|
|
67
72
|
@dataclass(frozen=True)
|
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
|
|
18
18
|
from agent_runner import __version__
|
|
19
19
|
from agent_runner.cli import (
|
|
20
|
+
doctor_cmd,
|
|
20
21
|
events_cmd,
|
|
21
22
|
init_cmd,
|
|
22
23
|
install_cmd,
|
|
@@ -71,6 +72,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
71
72
|
round_cmd.add_parser(sub, parent)
|
|
72
73
|
upgrade_cmd.add_parser(sub, parent)
|
|
73
74
|
migrate_cmd.add_parser(sub, parent)
|
|
75
|
+
doctor_cmd.add_parser(sub, parent)
|
|
74
76
|
|
|
75
77
|
return parser
|
|
76
78
|
|