cli-agent-runner 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/workflows/ci.yml +6 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.vulture-whitelist.py +22 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CHANGELOG.md +15 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/PKG-INFO +4 -1
- cli_agent_runner-0.3.2/agent_runner/__init__.py +224 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/__init__.py +16 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/memory.py +30 -0
- cli_agent_runner-0.3.2/agent_runner/_emit/plugins.py +76 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_observe.py +16 -2
- cli_agent_runner-0.3.2/agent_runner/_plugin_checksum.py +62 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_plugin_manifest.py +48 -4
- cli_agent_runner-0.3.2/agent_runner/_plugin_sandbox.py +535 -0
- cli_agent_runner-0.3.2/agent_runner/_registry.py +69 -0
- cli_agent_runner-0.3.2/agent_runner/_sandbox_probe.py +326 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/agent_runtime.py +20 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/api.py +7 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/api_types.py +12 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_round.py +219 -6
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/common.py +16 -13
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/doctor_cmd.py +20 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/peek_cmd.py +7 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/serve_cmd.py +39 -4
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/loader.py +6 -4
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/models.py +9 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/parsers.py +62 -3
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/validators.py +13 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/events.py +7 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/hooks.py +153 -10
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/host_health.py +22 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/runner.py +3 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/architecture.md +7 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/configuration.md +6 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/memory-efficiency.md +11 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/plugins.md +15 -3
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/thesis.md +1 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/pyproject.toml +11 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/_test_helpers.py +99 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/conftest.py +50 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/contract/test_public_api_surface.py +16 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_dirty_handler_seam.py +12 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_run_one_round_with_fake_agent.py +7 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_architecture.py +20 -3
- cli_agent_runner-0.3.2/tests/invariants/test_builtin_plugin_names_sync.py +21 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_import_footprint.py +17 -10
- cli_agent_runner-0.3.2/tests/invariants/test_kill_tests_not_skipped.py +20 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_module_boundaries.py +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_peek_schema_version.py +12 -1
- cli_agent_runner-0.3.2/tests/linux/test_plugin_sandbox_kill.py +149 -0
- cli_agent_runner-0.3.2/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_grace.py +63 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_observation.py +33 -0
- cli_agent_runner-0.3.2/tests/unit/test_builtin_provenance.py +31 -0
- cli_agent_runner-0.3.2/tests/unit/test_collapse_spawn_decisions.py +33 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config.py +38 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_dirty_handlers.py +9 -4
- cli_agent_runner-0.3.2/tests/unit/test_dispatch_dirty_owner_branch.py +261 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_doctor_cmd.py +61 -0
- cli_agent_runner-0.3.2/tests/unit/test_doctor_sandbox.py +75 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events.py +27 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_example_dirty_handler.py +4 -1
- cli_agent_runner-0.3.2/tests/unit/test_gate_serve_boot.py +90 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_host_health.py +53 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_init_entry_points.py +26 -22
- cli_agent_runner-0.3.2/tests/unit/test_load_fail_closed.py +165 -0
- cli_agent_runner-0.3.2/tests/unit/test_loader_split.py +61 -0
- cli_agent_runner-0.3.2/tests/unit/test_manifest_spawn_hooks.py +66 -0
- cli_agent_runner-0.3.2/tests/unit/test_peek_sandbox_block.py +128 -0
- cli_agent_runner-0.3.2/tests/unit/test_plugin_checksum.py +44 -0
- cli_agent_runner-0.3.2/tests/unit/test_plugin_name_mismatch.py +71 -0
- cli_agent_runner-0.3.2/tests/unit/test_plugin_sandbox_wire.py +313 -0
- cli_agent_runner-0.3.2/tests/unit/test_plugins_config_fields.py +59 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner.py +14 -0
- cli_agent_runner-0.3.2/tests/unit/test_sandbox_probe.py +74 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_cgroup_spine.py +168 -0
- cli_agent_runner-0.3.2/tests/unit/test_spawn_seam.py +397 -0
- cli_agent_runner-0.3.0/agent_runner/__init__.py +0 -102
- cli_agent_runner-0.3.0/agent_runner/_registry.py +0 -23
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.gitignore +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/LICENSE +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/README.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/README.zh.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/build.sh +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/README.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/commands.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/events.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.0/tests/literate → cli_agent_runner-0.3.2/tests/linux}/__init__.py +0 -0
- {cli_agent_runner-0.3.0/tests/unit → cli_agent_runner-0.3.2/tests/literate}/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_vcs_state.py +0 -0
|
@@ -82,7 +82,12 @@ jobs:
|
|
|
82
82
|
python-version: ${{ matrix.python }}
|
|
83
83
|
cache: pip
|
|
84
84
|
- name: install
|
|
85
|
-
|
|
85
|
+
# [sandbox] pulls the pure-Python Landlock/seccomp bindings only on
|
|
86
|
+
# Linux (their sys_platform=='linux' markers make this a no-op on
|
|
87
|
+
# macOS), so the Linux legs exercise real seccomp enforcement in the
|
|
88
|
+
# kill-tests; test_kill_tests_not_skipped.py fails loudly if a
|
|
89
|
+
# capable runner ever skips them instead of asserting the denial.
|
|
90
|
+
run: pip install -e ".[dev,sandbox]"
|
|
86
91
|
- name: ruff
|
|
87
92
|
run: |
|
|
88
93
|
ruff check .
|
|
@@ -20,6 +20,7 @@ context_enrichers
|
|
|
20
20
|
post_round_hooks
|
|
21
21
|
serve_startup_hooks
|
|
22
22
|
dirty_handlers
|
|
23
|
+
spawn_hooks
|
|
23
24
|
detectors
|
|
24
25
|
event_kinds
|
|
25
26
|
|
|
@@ -34,6 +35,12 @@ usage_capable_by_agent
|
|
|
34
35
|
newest_usage_ts_by_agent
|
|
35
36
|
backoff_exponent_by_agent
|
|
36
37
|
|
|
38
|
+
# agent_runner._sandbox_probe.SandboxProbe
|
|
39
|
+
achieved_tier
|
|
40
|
+
landlock_abi
|
|
41
|
+
seccomp
|
|
42
|
+
unconfined_reason
|
|
43
|
+
|
|
37
44
|
# agent_runner.agent_runtime.RunResult
|
|
38
45
|
exit_code
|
|
39
46
|
duration_s
|
|
@@ -77,6 +84,7 @@ service
|
|
|
77
84
|
recent_events
|
|
78
85
|
recent_hook_failures
|
|
79
86
|
recent_blips
|
|
87
|
+
recent_cgroup_growth_warnings
|
|
80
88
|
schedule
|
|
81
89
|
|
|
82
90
|
# agent_runner.api_types.RateLimitState
|
|
@@ -122,6 +130,11 @@ unit_file
|
|
|
122
130
|
rate_limit
|
|
123
131
|
system_managed
|
|
124
132
|
|
|
133
|
+
# agent_runner.api_types.SpawnDecision
|
|
134
|
+
action
|
|
135
|
+
defer_s
|
|
136
|
+
reason
|
|
137
|
+
|
|
125
138
|
# agent_runner.api_types.SystemMetrics
|
|
126
139
|
mem_total_mb
|
|
127
140
|
mem_available_mb
|
|
@@ -142,6 +155,7 @@ phase
|
|
|
142
155
|
checks
|
|
143
156
|
overlaps
|
|
144
157
|
plan
|
|
158
|
+
sandbox
|
|
145
159
|
|
|
146
160
|
# agent_runner.config.models.AgentConfig
|
|
147
161
|
command
|
|
@@ -192,6 +206,9 @@ phase_policy
|
|
|
192
206
|
|
|
193
207
|
# agent_runner.config.models.PluginsConfig
|
|
194
208
|
disable
|
|
209
|
+
spawn_override_allow
|
|
210
|
+
sandbox
|
|
211
|
+
pin
|
|
195
212
|
raw
|
|
196
213
|
|
|
197
214
|
# agent_runner.config.models.Profile
|
|
@@ -248,6 +265,7 @@ full_avg10_critical
|
|
|
248
265
|
some_avg10_warning
|
|
249
266
|
critical_consecutive_samples
|
|
250
267
|
in_round_terminate
|
|
268
|
+
cgroup_growth_rate_warning_mb_per_min
|
|
251
269
|
|
|
252
270
|
# agent_runner.context_store.OrphanState
|
|
253
271
|
round_num
|
|
@@ -287,6 +305,10 @@ anomaly_repetitive_window
|
|
|
287
305
|
anomaly_repetitive_threshold
|
|
288
306
|
vcs
|
|
289
307
|
|
|
308
|
+
# agent_runner.hooks.SpawnView
|
|
309
|
+
argv
|
|
310
|
+
env
|
|
311
|
+
|
|
290
312
|
# agent_runner.hooks.VcsHookView
|
|
291
313
|
dirty_action
|
|
292
314
|
stash_idempotency_s
|
|
@@ -5,6 +5,21 @@ 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.3.2] - 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Opt-in confinement for third-party plugin code (the hooks the supervisor runs in its own process — NOT the agent): a per-call Landlock+seccomp trampoline around third-party spawn hooks and dirty handlers, behind the `[sandbox]` extra. `[plugins] sandbox = require|prefer|off` (default `prefer`: run unconfined but announce it; `require` refuses boot when the sandbox can't engage). Built-in plugins are trusted by module provenance, not by name.
|
|
12
|
+
- Checksum pinning: `[plugins.pin]` verifies a third-party plugin's sha256 before import and fails closed on a mismatch; `doctor` prints each third-party plugin's computed hash to paste in.
|
|
13
|
+
- A `SpawnHook` plugin family that can defer or skip a round's spawn, gated by `[plugins] spawn_override_allow` (empty by default — no plugin alters a spawn until named).
|
|
14
|
+
- `peek --json` (schema `2.2`) and `doctor` report the achieved sandbox tier, `libseccomp` presence, and per-plugin pin status, naming only the families actually confined.
|
|
15
|
+
- New events: `plugin_checksum_mismatch`, `plugin_sandbox_degraded`, `plugin_sandbox_kill`, `plugin_spawn_decision`, `plugin_spawn_override_ignored`, `plugin_builtin_name_squat`.
|
|
16
|
+
|
|
17
|
+
## [0.3.1] - 2026-09-14
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- `monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min` (default 512.0): a mid-round memory-growth-rate detector gives an earlier growth signal than the existing PSI/MemFree floors catch, ahead of a runaway climb reaching them. Observability only — no round action.
|
|
21
|
+
- `cgroup_growth_rate_warning` event, surfaced in `peek --json` as `recent_cgroup_growth_warnings` (schema `2.1`).
|
|
22
|
+
|
|
8
23
|
## [0.3.0] - 2026-09-13
|
|
9
24
|
|
|
10
25
|
**The one breaking release of the 0.3 line.** Run `agent-runner migrate` before upgrading — it rewrites everything below in one pass.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -34,6 +34,9 @@ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
|
|
|
34
34
|
Requires-Dist: vulture>=2.13; extra == 'dev'
|
|
35
35
|
Provides-Extra: e2e
|
|
36
36
|
Requires-Dist: pytest>=8; extra == 'e2e'
|
|
37
|
+
Provides-Extra: sandbox
|
|
38
|
+
Requires-Dist: py-landlock==0.1.1; (sys_platform == 'linux') and extra == 'sandbox'
|
|
39
|
+
Requires-Dist: pyseccomp==0.1.2; (sys_platform == 'linux') and extra == 'sandbox'
|
|
37
40
|
Description-Content-Type: text/markdown
|
|
38
41
|
|
|
39
42
|
> **[中文](README.zh.md)** · English
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Agent Runner — restart-on-exit supervisor for autonomous CLI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from agent_runner._version import __version__
|
|
7
|
+
except ImportError: # editable install before hatch-vcs has generated _version.py
|
|
8
|
+
__version__ = "0.0.0+unknown"
|
|
9
|
+
|
|
10
|
+
_PLUGIN_GROUP = "agent_runner.plugins"
|
|
11
|
+
|
|
12
|
+
# Tracks the names passed to the most recent ``apply_plugin_disable`` call.
|
|
13
|
+
# Surfaced via peek --json `plugins.disabled` for operator visibility.
|
|
14
|
+
_DISABLED_PLUGIN_NAMES: list[str] = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_DISCOVERED_PLUGIN_ENTRIES: list[tuple[str, str]] = [] # (name, "module:attr"), populated once
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _entry_point_module_path(value: str) -> str:
|
|
21
|
+
"""The resolvable module-path portion of an entry-point ``value``
|
|
22
|
+
(``"module.path:attr [extras]"``), with any ``:attr`` suffix and trailing
|
|
23
|
+
extras marker stripped.
|
|
24
|
+
|
|
25
|
+
Shared by ``load_and_register_plugins``'s import resolution and
|
|
26
|
+
``doctor``'s per-plugin checksum print (``cli/doctor_cmd.py``) — the
|
|
27
|
+
checksum print and the actual import MUST resolve the identical module,
|
|
28
|
+
or an operator's pinned digest would silently protect the wrong file.
|
|
29
|
+
"""
|
|
30
|
+
return value.partition("[")[0].rstrip().partition(":")[0]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _discover_plugin_manifests() -> None:
|
|
34
|
+
"""Package-import-time step. Scans entry_points.txt ONLY (scan_entry_points) —
|
|
35
|
+
imports nothing, executes no plugin code.
|
|
36
|
+
|
|
37
|
+
Discovery goes through the ``entry_points.txt`` scanner (cheaper than a
|
|
38
|
+
fresh ``importlib.metadata.entry_points()`` scan per process; see
|
|
39
|
+
``_plugin_scan``), with a hard fallback to ``importlib.metadata`` on any
|
|
40
|
+
parse failure.
|
|
41
|
+
"""
|
|
42
|
+
import sys
|
|
43
|
+
|
|
44
|
+
from agent_runner._plugin_scan import scan_entry_points
|
|
45
|
+
|
|
46
|
+
global _DISCOVERED_PLUGIN_ENTRIES
|
|
47
|
+
_DISCOVERED_PLUGIN_ENTRIES = scan_entry_points(sys.path, _PLUGIN_GROUP)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_discover_plugin_manifests()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_and_register_plugins(plugins_cfg, log_dir=None) -> None:
|
|
54
|
+
"""Called from config.loader.load_config once [plugins] is parsed — the
|
|
55
|
+
load-bearing fix: verification now has a Config to gate on. For each
|
|
56
|
+
discovered (name, value) NOT in plugins_cfg.disable and not already
|
|
57
|
+
registered, resolve module_path/attr_path, import + register_manifest. A
|
|
58
|
+
disabled name is never imported. A per-plugin failure isolates as a
|
|
59
|
+
UserWarning and skips that ONE plugin; it never aborts the whole load.
|
|
60
|
+
|
|
61
|
+
``name`` here is always the entry-point name (the ``agent_runner.plugins``
|
|
62
|
+
pyproject.toml key) — it's the identifier known BEFORE import, so it's what
|
|
63
|
+
``_admit_third_party`` gates on (pin lookup) and what ``doctor`` prints a
|
|
64
|
+
checksum against. It is NOT necessarily ``PluginManifest.name`` (only known
|
|
65
|
+
after import); for every builtin the two are pinned equal by
|
|
66
|
+
``test_entry_point_names_should_match_manifest_name``, but a third-party
|
|
67
|
+
plugin has no such guarantee — which is fine, since admission never reads
|
|
68
|
+
the manifest name at all.
|
|
69
|
+
|
|
70
|
+
Builtin trust is decided by ``is_builtin_provenance(name, module_path)`` —
|
|
71
|
+
a reserved name AND a module genuinely under
|
|
72
|
+
``agent_runner.builtin_plugins``. A reserved name alone is NOT enough: a
|
|
73
|
+
name-squatter (reserved name, foreign module) is treated as third-party,
|
|
74
|
+
flows through ``_admit_third_party``, and raises a loud
|
|
75
|
+
``plugin_builtin_name_squat`` signal. Only genuine builtins skip the gate.
|
|
76
|
+
|
|
77
|
+
Every non-builtin entry passes through ``_admit_third_party`` BEFORE import
|
|
78
|
+
— a pin mismatch or an unpinned plugin under ``sandbox = "require"`` refuses
|
|
79
|
+
to import that ONE plugin (fail-closed; never loads unconfined).
|
|
80
|
+
"""
|
|
81
|
+
import warnings
|
|
82
|
+
|
|
83
|
+
from agent_runner._plugin_manifest import loaded_manifest_names, register_manifest
|
|
84
|
+
from agent_runner._registry import (
|
|
85
|
+
BUILTIN_PLUGIN_NAMES,
|
|
86
|
+
is_builtin_provenance,
|
|
87
|
+
resolve_entry_target,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
disable = set(plugins_cfg.disable)
|
|
91
|
+
already = set(loaded_manifest_names())
|
|
92
|
+
for name, value in _DISCOVERED_PLUGIN_ENTRIES:
|
|
93
|
+
if name in disable or name in already:
|
|
94
|
+
continue
|
|
95
|
+
# An entry-point value may carry a trailing extras marker
|
|
96
|
+
# (``module:attr [extra1,extra2]``); importlib.metadata.EntryPoint's
|
|
97
|
+
# own module/attr grammar excludes "[", so anything from the first
|
|
98
|
+
# "[" onward is always extras, never part of the path — strip it
|
|
99
|
+
# before resolving, or it glues onto attr_path and breaks getattr().
|
|
100
|
+
stripped = value.partition("[")[0].rstrip()
|
|
101
|
+
module_path, _, attr_path = stripped.partition(":")
|
|
102
|
+
# Builtin trust is keyed on PROVENANCE (module genuinely under
|
|
103
|
+
# agent_runner.builtin_plugins), never on the entry-point NAME alone —
|
|
104
|
+
# a reserved name is collidable, so a third-party package shipping an
|
|
105
|
+
# entry named e.g. "pi" is a name-squatter, not a builtin.
|
|
106
|
+
is_builtin = is_builtin_provenance(name, module_path)
|
|
107
|
+
if not is_builtin:
|
|
108
|
+
if name in BUILTIN_PLUGIN_NAMES:
|
|
109
|
+
_warn_builtin_name_squat(name, module_path, log_dir)
|
|
110
|
+
if not _admit_third_party(name, module_path, plugins_cfg, log_dir):
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
target = resolve_entry_target(module_path, attr_path)
|
|
114
|
+
register_manifest(
|
|
115
|
+
target, builtin=is_builtin, module_path=module_path, attr_path=attr_path
|
|
116
|
+
)
|
|
117
|
+
except Exception as e: # noqa: BLE001 — a broken plugin must never crash the supervisor
|
|
118
|
+
warnings.warn(f"failed to load {_PLUGIN_GROUP} plugin {name!r}: {e}", stacklevel=3)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _warn_builtin_name_squat(name: str, module_path: str, log_dir) -> None:
|
|
122
|
+
"""A discovered entry point claims a RESERVED builtin name while resolving
|
|
123
|
+
to a non-core module — a name-squatter. Builtin trust is denied (the entry
|
|
124
|
+
still flows through ``_admit_third_party``'s pin/sandbox gate); this makes
|
|
125
|
+
the claim LOUD (a warning always, plus an auditable event when a log_dir is
|
|
126
|
+
available) rather than a silent trust bypass."""
|
|
127
|
+
import warnings
|
|
128
|
+
|
|
129
|
+
warnings.warn(
|
|
130
|
+
f"{_PLUGIN_GROUP} entry {name!r} claims a reserved builtin name but resolves to "
|
|
131
|
+
f"non-core module {module_path!r}; treating as third-party (pin/sandbox gate applies)",
|
|
132
|
+
stacklevel=4,
|
|
133
|
+
)
|
|
134
|
+
if log_dir is not None:
|
|
135
|
+
from agent_runner.api import emit_plugin_builtin_name_squat
|
|
136
|
+
|
|
137
|
+
emit_plugin_builtin_name_squat(log_dir, name=name, module_path=module_path)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _admit_third_party(name: str, module_path: str, plugins_cfg, log_dir) -> bool:
|
|
141
|
+
"""Fail-closed pin/sandbox gate for a non-builtin plugin (any entry lacking
|
|
142
|
+
genuine builtin provenance, INCLUDING a reserved-name squatter). Returns
|
|
143
|
+
False to refuse (and emit) — skipping this ONE plugin; every other
|
|
144
|
+
discovered entry still loads.
|
|
145
|
+
|
|
146
|
+
Both ``name`` and ``pins``' keys are entry-point names — see
|
|
147
|
+
``load_and_register_plugins``'s docstring. Runs BEFORE
|
|
148
|
+
``importlib.import_module``, so no plugin code executes for a refused
|
|
149
|
+
entry.
|
|
150
|
+
"""
|
|
151
|
+
from agent_runner._plugin_checksum import verify_pin
|
|
152
|
+
from agent_runner._sandbox_probe import probe_sandbox_capability
|
|
153
|
+
from agent_runner.api import emit_plugin_checksum_mismatch, emit_plugin_sandbox_degraded
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
verdict, actual = verify_pin(name, module_path, plugins_cfg.pin)
|
|
157
|
+
except Exception: # noqa: BLE001 — unresolvable/unreadable module == refuse (fail closed)
|
|
158
|
+
verdict, actual = "mismatch", "sha256:unreadable"
|
|
159
|
+
if verdict == "mismatch":
|
|
160
|
+
if log_dir is not None:
|
|
161
|
+
emit_plugin_checksum_mismatch(
|
|
162
|
+
log_dir,
|
|
163
|
+
name=name,
|
|
164
|
+
expected=plugins_cfg.pin.get(name, ""),
|
|
165
|
+
actual=actual or "sha256:unreadable",
|
|
166
|
+
)
|
|
167
|
+
return False
|
|
168
|
+
if verdict == "unpinned" and plugins_cfg.sandbox == "require":
|
|
169
|
+
if log_dir is not None:
|
|
170
|
+
probe = probe_sandbox_capability()
|
|
171
|
+
emit_plugin_sandbox_degraded(
|
|
172
|
+
log_dir,
|
|
173
|
+
requested="require",
|
|
174
|
+
achieved_tier=probe.achieved_tier,
|
|
175
|
+
reason=f"{name}: unpinned third-party plugin under sandbox=require",
|
|
176
|
+
)
|
|
177
|
+
return False
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def apply_plugin_disable(names: list[str]) -> None:
|
|
182
|
+
"""Remove plugins matching ``names`` (manifest names) from all in-memory
|
|
183
|
+
registries.
|
|
184
|
+
|
|
185
|
+
Called after config-load to honor ``[plugins] disable``. Idempotent for
|
|
186
|
+
already-removed names. Emits a UserWarning for names that match no
|
|
187
|
+
loaded manifest (typo catcher; tolerates cross-env config drift).
|
|
188
|
+
|
|
189
|
+
A disabled name is never imported by ``load_and_register_plugins`` in the
|
|
190
|
+
first place, so this is belt-and-suspenders cleanup for anything that
|
|
191
|
+
reached the registries another way (e.g. a manifest registered directly
|
|
192
|
+
via ``register_manifest``, not through entry-point discovery).
|
|
193
|
+
|
|
194
|
+
Known limitation: vcs_state._PLUGIN_OWNED_PATHS lacks per-plugin name
|
|
195
|
+
attribution today, so owned-paths are NOT filtered here. Disabled plugin's
|
|
196
|
+
paths remain registered (mostly inert).
|
|
197
|
+
"""
|
|
198
|
+
import warnings
|
|
199
|
+
|
|
200
|
+
from agent_runner._plugin_manifest import unregister_by_name
|
|
201
|
+
|
|
202
|
+
if not names:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
global _DISABLED_PLUGIN_NAMES
|
|
206
|
+
_DISABLED_PLUGIN_NAMES = list(names)
|
|
207
|
+
|
|
208
|
+
found = unregister_by_name(set(names))
|
|
209
|
+
unknown = set(names) - found
|
|
210
|
+
if unknown:
|
|
211
|
+
warnings.warn(
|
|
212
|
+
f"[plugins] disable references unknown plugin name(s): {sorted(unknown)}. "
|
|
213
|
+
f"(Names matched no loaded PluginManifest; check spelling or installed packages.)",
|
|
214
|
+
stacklevel=2,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def disabled_plugin_names() -> list[str]:
|
|
219
|
+
"""Names passed to the most recent ``apply_plugin_disable`` call.
|
|
220
|
+
|
|
221
|
+
Used by peek --json to surface ``plugins.disabled`` for operator visibility.
|
|
222
|
+
Returns the LIST (not set) preserving configured order.
|
|
223
|
+
"""
|
|
224
|
+
return list(_DISABLED_PLUGIN_NAMES)
|
|
@@ -29,6 +29,7 @@ from agent_runner._emit.agent import ( # noqa: F401 — public re-export
|
|
|
29
29
|
emit_transient_error_recovered,
|
|
30
30
|
)
|
|
31
31
|
from agent_runner._emit.memory import ( # noqa: F401 — public re-export
|
|
32
|
+
emit_cgroup_growth_rate_warning,
|
|
32
33
|
emit_host_cgroup_memory_limit,
|
|
33
34
|
emit_mem_pressure_deferred_to_cgroup,
|
|
34
35
|
emit_round_cgroup_memory,
|
|
@@ -38,6 +39,14 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
|
|
|
38
39
|
emit_round_oom_killed,
|
|
39
40
|
emit_round_resumed,
|
|
40
41
|
)
|
|
42
|
+
from agent_runner._emit.plugins import ( # noqa: F401 — public re-export
|
|
43
|
+
emit_plugin_builtin_name_squat,
|
|
44
|
+
emit_plugin_checksum_mismatch,
|
|
45
|
+
emit_plugin_sandbox_degraded,
|
|
46
|
+
emit_plugin_sandbox_kill,
|
|
47
|
+
emit_plugin_spawn_decision,
|
|
48
|
+
emit_plugin_spawn_override_ignored,
|
|
49
|
+
)
|
|
41
50
|
from agent_runner._emit.rounds import ( # noqa: F401 — public re-export
|
|
42
51
|
emit_fresh_eyes_round_triggered,
|
|
43
52
|
emit_round_container_orphan_risk,
|
|
@@ -69,6 +78,7 @@ __all__ = [
|
|
|
69
78
|
"emit_agent_auth_error_detected",
|
|
70
79
|
"emit_agent_usage_recorded",
|
|
71
80
|
"emit_anomaly_repetitive_tool",
|
|
81
|
+
"emit_cgroup_growth_rate_warning",
|
|
72
82
|
"emit_config_broken",
|
|
73
83
|
"emit_config_migrated",
|
|
74
84
|
"emit_crash_loop",
|
|
@@ -78,6 +88,12 @@ __all__ = [
|
|
|
78
88
|
"emit_mem_loop",
|
|
79
89
|
"emit_mem_loop_persistent",
|
|
80
90
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
91
|
+
"emit_plugin_builtin_name_squat",
|
|
92
|
+
"emit_plugin_checksum_mismatch",
|
|
93
|
+
"emit_plugin_sandbox_degraded",
|
|
94
|
+
"emit_plugin_sandbox_kill",
|
|
95
|
+
"emit_plugin_spawn_decision",
|
|
96
|
+
"emit_plugin_spawn_override_ignored",
|
|
81
97
|
"emit_rate_limit_stop",
|
|
82
98
|
"emit_round_cgroup_memory",
|
|
83
99
|
"emit_round_container_orphan_risk",
|
|
@@ -238,3 +238,33 @@ def emit_mem_pressure_deferred_to_cgroup(
|
|
|
238
238
|
from agent_runner.events import MEM_PRESSURE_DEFERRED_TO_CGROUP, emit
|
|
239
239
|
|
|
240
240
|
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def emit_cgroup_growth_rate_warning(
|
|
244
|
+
log_dir: Path,
|
|
245
|
+
*,
|
|
246
|
+
round_num: int,
|
|
247
|
+
rate_mb_per_min: float,
|
|
248
|
+
threshold_mb_per_min: float,
|
|
249
|
+
source: str,
|
|
250
|
+
context: dict,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Emit on a False->True crossing of _spawn_round's mid-round growth-rate
|
|
253
|
+
derivative (host_health.cgroup_growth_rate_pressure) -- observability
|
|
254
|
+
only, no action path. ``source`` names which per-tick reading the
|
|
255
|
+
derivative used: "cgroup" (a finite cgroup memory.max bound exists,
|
|
256
|
+
memory.current is read) or "rss_sum" (no finite bound -- the psutil
|
|
257
|
+
children_rss_sum_bytes tree sum is used instead). Edge-triggered: fires
|
|
258
|
+
once per crossing episode, not once per tick, so a sustained climb does
|
|
259
|
+
not spam the event stream."""
|
|
260
|
+
from agent_runner.events import CGROUP_GROWTH_RATE_WARNING, emit
|
|
261
|
+
|
|
262
|
+
emit(
|
|
263
|
+
log_dir,
|
|
264
|
+
CGROUP_GROWTH_RATE_WARNING,
|
|
265
|
+
round_num=round_num,
|
|
266
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
267
|
+
threshold_mb_per_min=threshold_mb_per_min,
|
|
268
|
+
source=source,
|
|
269
|
+
context=context,
|
|
270
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Event emission wrappers -- plugin-security domain (sandbox trampoline).
|
|
2
|
+
Re-exported from ``agent_runner._emit`` (the package facade) -- see its docstring."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def emit_plugin_sandbox_kill(
|
|
10
|
+
log_dir: Path, *, hook: str, signal: int, syscall: str | None = None
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Emit when a trampoline child died by signal (seccomp KILL_PROCESS) -- a
|
|
13
|
+
denied syscall. Distinct from the caller's own hook_failed isolation, which
|
|
14
|
+
also fires: this event names the DENIAL specifically."""
|
|
15
|
+
from agent_runner.events import PLUGIN_SANDBOX_KILL, emit
|
|
16
|
+
|
|
17
|
+
emit(log_dir, PLUGIN_SANDBOX_KILL, hook=hook, signal=signal, syscall=syscall)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def emit_plugin_spawn_decision(
|
|
21
|
+
log_dir: Path, *, hook: str, action: str, defer_s: int, reason: str
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Emit the collapsed pre-spawn verdict the serve admission gate acted on --
|
|
24
|
+
``defer`` or ``skip`` (a ``proceed`` is the silent default, no event). ``hook``
|
|
25
|
+
is the winning hook by the collapse's own precedence (skip > max-defer)."""
|
|
26
|
+
from agent_runner.events import PLUGIN_SPAWN_DECISION, emit
|
|
27
|
+
|
|
28
|
+
emit(log_dir, PLUGIN_SPAWN_DECISION, hook=hook, action=action, defer_s=defer_s, reason=reason)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def emit_plugin_spawn_override_ignored(log_dir: Path, *, hook: str, action: str) -> None:
|
|
32
|
+
"""Emit when a spawn hook returned a non-``proceed`` verdict but is NOT in
|
|
33
|
+
``[plugins] spawn_override_allow`` -- its blocking power is denied (the
|
|
34
|
+
decision is downgraded to ``proceed`` before the collapse). Blocking a round
|
|
35
|
+
is an operator-granted capability, so an un-listed hook's ``defer``/``skip``
|
|
36
|
+
is audited here rather than honored silently."""
|
|
37
|
+
from agent_runner.events import PLUGIN_SPAWN_OVERRIDE_IGNORED, emit
|
|
38
|
+
|
|
39
|
+
emit(log_dir, PLUGIN_SPAWN_OVERRIDE_IGNORED, hook=hook, action=action)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def emit_plugin_builtin_name_squat(log_dir: Path, *, name: str, module_path: str) -> None:
|
|
43
|
+
"""Emit when a discovered entry point claims a RESERVED builtin name while
|
|
44
|
+
resolving to a module OUTSIDE the core ``agent_runner.builtin_plugins``
|
|
45
|
+
namespace -- a name-squatter. Builtin trust is denied: the entry falls
|
|
46
|
+
through to the third-party pin/sandbox gate. This event makes the claim
|
|
47
|
+
auditable rather than silent."""
|
|
48
|
+
from agent_runner.events import PLUGIN_BUILTIN_NAME_SQUAT, emit
|
|
49
|
+
|
|
50
|
+
emit(log_dir, PLUGIN_BUILTIN_NAME_SQUAT, name=name, module_path=module_path)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def emit_plugin_checksum_mismatch(log_dir: Path, *, name: str, expected: str, actual: str) -> None:
|
|
54
|
+
"""Emit when a third-party plugin's computed sha256 doesn't match its
|
|
55
|
+
``[plugins.pin]`` entry (or the pin is unreadable) -- the loader refuses
|
|
56
|
+
to import that ONE plugin when this fires; it never loads unconfined."""
|
|
57
|
+
from agent_runner.events import PLUGIN_CHECKSUM_MISMATCH, emit
|
|
58
|
+
|
|
59
|
+
emit(log_dir, PLUGIN_CHECKSUM_MISMATCH, name=name, expected=expected, actual=actual)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def emit_plugin_sandbox_degraded(
|
|
63
|
+
log_dir: Path, *, requested: str, achieved_tier: str, reason: str
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Emit when a plugin loads (or would run) at a WEAKER tier than
|
|
66
|
+
``requested`` -- e.g. an unpinned third-party plugin refused under
|
|
67
|
+
``sandbox = "require"``, or a ``"prefer"`` load proceeding unconfined."""
|
|
68
|
+
from agent_runner.events import PLUGIN_SANDBOX_DEGRADED, emit
|
|
69
|
+
|
|
70
|
+
emit(
|
|
71
|
+
log_dir,
|
|
72
|
+
PLUGIN_SANDBOX_DEGRADED,
|
|
73
|
+
requested=requested,
|
|
74
|
+
achieved_tier=achieved_tier,
|
|
75
|
+
reason=reason,
|
|
76
|
+
)
|
|
@@ -21,12 +21,14 @@ from agent_runner.clock import SYSTEM_CLOCK
|
|
|
21
21
|
from agent_runner.config import load_config
|
|
22
22
|
from agent_runner.events import (
|
|
23
23
|
AGENT_NETWORK_BLIP,
|
|
24
|
+
CGROUP_GROWTH_RATE_WARNING,
|
|
24
25
|
HOOK_FAILED,
|
|
25
26
|
MONITOR_STARTED,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
_RECENT_HOOK_FAILURES_LIMIT = 10
|
|
29
30
|
_RECENT_BLIPS_LIMIT = 5
|
|
31
|
+
_RECENT_GROWTH_LIMIT = 5
|
|
30
32
|
_MONITOR_SEEN_CAP = 512
|
|
31
33
|
"""Bound on `_monitor_loop_iter`'s dedup set — an unbounded set of alert-identity
|
|
32
34
|
keys would grow forever across a long-lived monitor process; oldest-episode
|
|
@@ -59,12 +61,20 @@ def peek(
|
|
|
59
61
|
log: bool = False,
|
|
60
62
|
events: int | None = None,
|
|
61
63
|
select: str | None = None,
|
|
64
|
+
cfg: Any = None,
|
|
62
65
|
) -> ProjectState | Any:
|
|
63
|
-
"""Build a ProjectState snapshot. With select, return that subtree.
|
|
66
|
+
"""Build a ProjectState snapshot. With select, return that subtree.
|
|
67
|
+
|
|
68
|
+
``cfg`` (an already-loaded ``Config``) lets a caller that has loaded config
|
|
69
|
+
once — ``cli.peek_cmd.cmd_peek`` does, for the ``emit`` block — pass it in so
|
|
70
|
+
``peek`` does not re-``load_config`` (which re-runs plugin load/checksum/probe)
|
|
71
|
+
a second time per invocation. ``None`` loads it here (the default, unchanged
|
|
72
|
+
for every other caller)."""
|
|
64
73
|
from agent_runner import round_view
|
|
65
74
|
|
|
66
75
|
work_dir = project if isinstance(project, Path) else Path.cwd()
|
|
67
|
-
cfg
|
|
76
|
+
if cfg is None:
|
|
77
|
+
cfg = load_config(work_dir / "agent-runner.toml")
|
|
68
78
|
log_dir = cfg.runtime.log_dir
|
|
69
79
|
src = monitor.LocalSource(log_dir=log_dir)
|
|
70
80
|
base_state = monitor.assemble_project_state(src, project=_project_name(work_dir))
|
|
@@ -80,6 +90,9 @@ def peek(
|
|
|
80
90
|
parsed_events, HOOK_FAILED, _RECENT_HOOK_FAILURES_LIMIT
|
|
81
91
|
)
|
|
82
92
|
recent_blips = _recent_events_of_kind(parsed_events, AGENT_NETWORK_BLIP, _RECENT_BLIPS_LIMIT)
|
|
93
|
+
recent_cgroup_growth_warnings = _recent_events_of_kind(
|
|
94
|
+
parsed_events, CGROUP_GROWTH_RATE_WARNING, _RECENT_GROWTH_LIMIT
|
|
95
|
+
)
|
|
83
96
|
|
|
84
97
|
from agent_runner._throttle import effective_throttle_view
|
|
85
98
|
|
|
@@ -121,6 +134,7 @@ def peek(
|
|
|
121
134
|
recent_events=recent,
|
|
122
135
|
recent_hook_failures=recent_hook_failures,
|
|
123
136
|
recent_blips=recent_blips,
|
|
137
|
+
recent_cgroup_growth_warnings=recent_cgroup_growth_warnings,
|
|
124
138
|
schedule=monitor.latest_schedule_state(parsed_events),
|
|
125
139
|
)
|
|
126
140
|
return state if select is None else select_path(state, select)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""sha256 pinning of a plugin's declared entry-point module FILE (coarse but
|
|
2
|
+
deterministic — one file, one read). Fail-closed: an unresolvable/unreadable
|
|
3
|
+
module raises, and the loader turns that into a refusal, same as a mismatch.
|
|
4
|
+
|
|
5
|
+
Keyed on the entry-point NAME (the ``agent_runner.plugins`` group's own
|
|
6
|
+
pyproject.toml key), not ``PluginManifest.name`` — a third-party plugin
|
|
7
|
+
hasn't been imported yet at pin-check time, so its manifest name isn't even
|
|
8
|
+
known; the entry-point name is the only stable, pre-import identifier an
|
|
9
|
+
operator can write into ``[plugins.pin]``. See
|
|
10
|
+
``agent_runner.__init__._admit_third_party`` for the call site.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import importlib.util
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
PinVerdict = Literal["verified", "mismatch", "unpinned"]
|
|
20
|
+
|
|
21
|
+
_CHECKSUM_CACHE: dict[str, str] = {}
|
|
22
|
+
"""Process-wide memo of ``module_path -> digest``, keyed post-resolution.
|
|
23
|
+
A ``doctor``/``peek --json`` invocation hashes each pinned third-party file
|
|
24
|
+
twice (once at load via ``verify_pin``, once for the snapshot print) — this
|
|
25
|
+
avoids the second read of the SAME file in the SAME process. Never
|
|
26
|
+
invalidated (a process's view of its own already-imported modules doesn't
|
|
27
|
+
change mid-run); the comparison logic below is untouched."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def compute_plugin_checksum(module_path: str) -> str:
|
|
31
|
+
"""sha256 of the resolved module's source FILE, as ``"sha256:<64hex>"``.
|
|
32
|
+
|
|
33
|
+
This exact recipe (sha256 of the raw bytes of ``find_spec(module_path)
|
|
34
|
+
.origin``) is also what ``doctor`` prints and what ``verify_pin`` checks
|
|
35
|
+
against — an operator copy-pastes doctor's printed digest straight into
|
|
36
|
+
``[plugins.pin]``, so all three MUST compute the identical value.
|
|
37
|
+
"""
|
|
38
|
+
cached = _CHECKSUM_CACHE.get(module_path)
|
|
39
|
+
if cached is not None:
|
|
40
|
+
return cached
|
|
41
|
+
spec = importlib.util.find_spec(module_path)
|
|
42
|
+
if spec is None or spec.origin is None:
|
|
43
|
+
raise ModuleNotFoundError(f"cannot resolve module file for {module_path!r}")
|
|
44
|
+
with open(spec.origin, "rb") as f:
|
|
45
|
+
digest = hashlib.sha256(f.read()).hexdigest()
|
|
46
|
+
checksum = f"sha256:{digest}"
|
|
47
|
+
_CHECKSUM_CACHE[module_path] = checksum
|
|
48
|
+
return checksum
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def verify_pin(name: str, module_path: str, pins: dict[str, str]) -> tuple[PinVerdict, str | None]:
|
|
52
|
+
"""Returns ``(verdict, actual)`` where ``actual`` is the computed
|
|
53
|
+
``"sha256:<hex>"`` digest of the module file, or ``None`` when the name is
|
|
54
|
+
unpinned (no digest is computed in that case). The caller reuses ``actual``
|
|
55
|
+
for the mismatch event instead of hashing the file a second time.
|
|
56
|
+
|
|
57
|
+
``name`` is the entry-point name (the ``[plugins.pin]`` key an operator
|
|
58
|
+
writes), NOT ``PluginManifest.name`` — see module docstring."""
|
|
59
|
+
if name not in pins:
|
|
60
|
+
return "unpinned", None
|
|
61
|
+
actual = compute_plugin_checksum(module_path)
|
|
62
|
+
return ("verified" if actual == pins[name] else "mismatch"), actual
|