cli-agent-runner 0.2.3__tar.gz → 0.2.5__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.3 → cli_agent_runner-0.2.5}/.github/workflows/release.yml +7 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.vulture-whitelist.py +0 -4
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CHANGELOG.md +34 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/PKG-INFO +22 -11
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/README.md +20 -9
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_emit.py +37 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/agent_runtime.py +34 -7
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/api.py +63 -66
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/api_types.py +9 -0
- cli_agent_runner-0.2.5/agent_runner/builtin_plugins/_constants.py +125 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/claude_rate_limit.py +11 -27
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/codewhale.py +16 -36
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/gemini.py +6 -28
- cli_agent_runner-0.2.5/agent_runner/builtin_plugins/kimi.py +93 -0
- cli_agent_runner-0.2.5/agent_runner/builtin_plugins/pi.py +279 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/events_cmd.py +126 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/monitor_cmd.py +61 -10
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/config.py +10 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/hooks.py +5 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/monitor.py +98 -102
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/pi.toml +2 -3
- cli_agent_runner-0.2.5/agent_runner/remote_relay.py +270 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/round_log.py +30 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/runner.py +6 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/service_unit.py +7 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/startup_check.py +19 -20
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/architecture.md +33 -13
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/commands.md +56 -11
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/configuration.md +7 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/events.md +3 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.28.md +2 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.30.md +3 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.35.md +1 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.0.md +2 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.3.md +5 -8
- cli_agent_runner-0.2.5/docs/migrations/0.2.4.md +66 -0
- cli_agent_runner-0.2.5/docs/migrations/0.2.5.md +99 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/plugins.md +106 -20
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/aider.md +5 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/pi.md +14 -7
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/runbook.md +141 -28
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/thesis.md +4 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/pyproject.toml +5 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/_test_helpers.py +26 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_monitor_seeded.py +2 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_run_one_round_with_fake_agent.py +2 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_claims_match_ssot.py +2 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_module_boundaries.py +1 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime.py +21 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_observation.py +34 -172
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_service.py +2 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_auto_stop_gating.py +1 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_claude_error_detector.py +31 -30
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_service_peek_monitor.py +89 -39
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_codewhale_plugin.py +14 -12
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config.py +59 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_events.py +39 -0
- cli_agent_runner-0.2.5/tests/unit/test_events_cmd.py +343 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_events_cmd_tail.py +39 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_gemini_plugin.py +12 -12
- cli_agent_runner-0.2.5/tests/unit/test_kimi_plugin.py +147 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detectors.py +57 -0
- cli_agent_runner-0.2.5/tests/unit/test_monitor_remote.py +320 -0
- cli_agent_runner-0.2.5/tests/unit/test_pi_plugin.py +449 -0
- cli_agent_runner-0.2.5/tests/unit/test_plugin_constants.py +61 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_presets.py +1 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_round_log_helpers.py +60 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_runner.py +0 -39
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_service_unit.py +9 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_startup_check.py +1 -1
- cli_agent_runner-0.2.3/agent_runner/builtin_plugins/_constants.py +0 -61
- cli_agent_runner-0.2.3/tests/unit/test_events_cmd.py +0 -179
- cli_agent_runner-0.2.3/tests/unit/test_monitor_remote.py +0 -167
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.gitignore +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/LICENSE +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/README.zh.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/build.sh +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/README.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.42.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.1.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.2.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_vcs_state.py +0 -0
|
@@ -9,9 +9,13 @@ permissions:
|
|
|
9
9
|
|
|
10
10
|
# Trusted Publishing: the `environment` name below MUST match the
|
|
11
11
|
# `Environment name` field in the PyPI pending publisher config.
|
|
12
|
-
# A mismatch fails publish with an opaque OIDC error.
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# A mismatch fails publish with an opaque OIDC error. Keep the environment
|
|
13
|
+
# declared even when it carries no protection rules — it is the OIDC claim,
|
|
14
|
+
# not just an approval gate; deleting it breaks publishing outright. Whether
|
|
15
|
+
# a tag publishes straight through or waits for a reviewer is configured in
|
|
16
|
+
# the repo's environment settings, not here.
|
|
17
|
+
# Note: TestPyPI stage intentionally omitted — the tag is the release signal;
|
|
18
|
+
# pre-tag verification is `./build.sh check` plus the CI matrix on main.
|
|
15
19
|
|
|
16
20
|
concurrency:
|
|
17
21
|
group: release-${{ github.ref }}
|
|
@@ -5,9 +5,35 @@ 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.
|
|
8
|
+
## [0.2.5] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `events --since <ISO ts>` — replays every matching event with `ts >= since`, across month files, one-shot or as the backlog phase of `--tail`. At-least-once: a client whose stream dropped reconnects with the last ts it saw and loses nothing (that one event may repeat).
|
|
12
|
+
- `monitor --host <alias> --mode events` — a managed ssh event relay, replacing a hand-rolled `ssh … --tail; sleep` loop. Passes the remote's JSONL through unmodified, reconnects with `--since <last ts>` so a dropped link replays its gap, emits `monitor_remote_blip` per drop and `monitor_remote_giveup` + exit 1 once the outage passes `[monitor] remote_failure_tolerance_s`, and kills the ssh process GROUP on exit so no orphan tree survives. New flags `--kind` and `--remote-config`.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
- The polling remote monitor (`RemoteSource`, `run_remote_command`, `MonitorRemoteError`, auto-stop over ssh) — it read remote paths on the local filesystem and was already failing loud since 0.2.4. Detection is on-host by design; remote observation is the relay above. `monitor.on_alert` lost its `host` parameter and `monitor_auto_stop_triggered` its `host` field; `monitor_auto_stop_failed` now reports a failed *local* stop (previously a failed ssh stop) instead of crashing the monitor loop.
|
|
16
|
+
|
|
17
|
+
See `docs/migrations/0.2.5.md`.
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
## [0.2.4] - 2026-07-27
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `kimi_error_detector` plugin — classifies transient errors from Kimi Code CLI rounds (`turn.step.retrying` records) so a rate-limited round backs off instead of hot-restarting. No usage events: the CLI's stream-json output carries no token counters.
|
|
23
|
+
- `agent_auth_error_detected` event — a plugin reports an auth failure named by the agent CLI's own structured output, and the `oauth_fail` detector counts those rounds directly. Makes pi's 401 loop visible (pi exits 0); the text-heuristic path keeps its nonzero-exit gate unchanged.
|
|
24
|
+
- `pi_error_detector` plugin — emits per-round token usage (summed across the round's assistant messages, since pi reports usage per message) and classifies transient errors from Pi Coding Agent rounds. pi exits 0 on provider failure, so the classifier reads the final message's `stopReason`/`errorMessage` rather than the exit code.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Round-log tail scanning hardened: plugin JSONL parsers filter non-JSON chatter *before* windowing (a stderr burst of any size can no longer evict the terminal event), monitor detectors share the same 200-line window (was 50 — the oauth/network text scans had the identical eviction risk), and each monitor poll reads only the newest 20 round logs instead of every log ever written.
|
|
28
|
+
- A relative `runtime.work_dir` (and every path derived from it) now resolves against the config file's directory instead of the caller's cwd, so `--config /abs/proj/agent-runner.toml` drives `/abs/proj` no matter where the supervisor was launched from.
|
|
29
|
+
- Remote monitor (`monitor --host <alias>`) was silently observing an empty world and reporting healthy — it listed remote filenames over ssh but read every path locally. It now fails loudly at startup (exit 1, with guidance to run the monitor on the host) until remote reads are implemented.
|
|
30
|
+
- Agent round logs (`{log_dir}/rounds/R*-*.log`) are now pruned under `runtime.round_log_retention` at the start of every round — previously that family grew unboundedly, since only the serve-level `round-<N>.log` family was pruned, and only at serve startup.
|
|
31
|
+
- Startup validation resolves the agent command exactly as the spawn does (child's PATH — `[agent.env]` may override it — and `work_dir` base); `PWD` now stays pinned to `work_dir` even if `[agent.env]` sets it. The stdout+stderr merge is now pinned by a behavioral test, not just prose.
|
|
32
|
+
- Generated serve systemd units now set `KillMode=mixed`: with systemd's default `control-group`, `systemctl stop` SIGTERMed the whole cgroup — agent child included — making the graceful round drain structurally ineffective. Existing installs: re-run `agent-runner install` (or add a drop-in) to pick this up.
|
|
33
|
+
|
|
34
|
+
See `docs/migrations/0.2.4.md`.
|
|
35
|
+
|
|
36
|
+
## [0.2.3] - 2026-07-26
|
|
11
37
|
|
|
12
38
|
### Added
|
|
13
39
|
- `--preset kimi` — a preset for [Kimi Code CLI](https://github.com/MoonshotAI/kimi-code) (`kimi -p --output-format stream-json`). `docs/recipes/kimi.md` also documents running Kimi K3 through the existing `claude` preset via Moonshot's Anthropic-compatible endpoint.
|
|
@@ -17,6 +43,8 @@ Details: [docs/migrations/0.2.3.md](docs/migrations/0.2.3.md)
|
|
|
17
43
|
- The agent subprocess now runs in `runtime.work_dir` (`cwd=` on spawn). Previously it inherited the supervisor's cwd and only launch conventions (systemd `WorkingDirectory=`, relative `--config`) kept the two aligned — fatal for agent CLIs with no working-directory flag of their own (e.g. `pi`). The startup check now also validates a relative `agent.command[0]` against `work_dir`, matching where it executes.
|
|
18
44
|
- Plugin round-log tail window widened 50 → 200 lines: a stderr burst after the agent's terminal JSONL event could evict it from the scan window, silently dropping usage/transient classification. The round log's merged stdout+stderr contract is now documented (`HookContext.agent_log_path`, `docs/long-running-agents.md`) — the merge is deliberate; auth/network detection reads stderr text from it.
|
|
19
45
|
|
|
46
|
+
See `docs/migrations/0.2.3.md`.
|
|
47
|
+
|
|
20
48
|
## [0.2.2] - 2026-07-18
|
|
21
49
|
|
|
22
50
|
### Removed
|
|
@@ -631,6 +659,7 @@ No breaking changes. Plugin authors:
|
|
|
631
659
|
between rounds, opt them out:
|
|
632
660
|
```python
|
|
633
661
|
from agent_runner.vcs_state import register_plugin_owned_paths
|
|
662
|
+
|
|
634
663
|
register_plugin_owned_paths(["your-output-dir/", "logs/your-plugin/**/*"])
|
|
635
664
|
```
|
|
636
665
|
- If you followed the old `_register()` pattern from docs and noticed
|
|
@@ -921,7 +950,9 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
921
950
|
- Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
|
|
922
951
|
gated by a manual approval on the `pypi` GitHub environment.
|
|
923
952
|
|
|
924
|
-
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.
|
|
953
|
+
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.5...HEAD
|
|
954
|
+
[0.2.5]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.4...v0.2.5
|
|
955
|
+
[0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
|
|
925
956
|
[0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
|
|
926
957
|
[0.2.2]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.1...v0.2.2
|
|
927
958
|
[0.2.1]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.0...v0.2.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
@@ -28,7 +28,7 @@ Requires-Dist: psutil>=5.9
|
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
30
30
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
|
-
Requires-Dist: ruff
|
|
31
|
+
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
|
|
32
32
|
Requires-Dist: vulture>=2.13; extra == 'dev'
|
|
33
33
|
Provides-Extra: e2e
|
|
34
34
|
Requires-Dist: pytest>=8; extra == 'e2e'
|
|
@@ -116,20 +116,31 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
116
116
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
117
117
|
- `disk_critical` — writing to a near-full disk risks corruption
|
|
118
118
|
|
|
119
|
-
Runs
|
|
119
|
+
Runs against the supervised project's local logs:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
agent-runner monitor #
|
|
123
|
-
agent-runner monitor --host pi # remote, 60s poll
|
|
122
|
+
agent-runner monitor # 30s poll
|
|
124
123
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
125
124
|
```
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
Watch a remote host's event stream from your laptop — one command instead of a
|
|
127
|
+
hand-rolled `ssh … ; sleep` loop:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agent-runner monitor --host pi --mode events # managed ssh relay, JSONL stdout
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The relay reconnects with `--since <last ts>` so a dropped link replays its gap,
|
|
134
|
+
gives up (exit 1) once the outage passes `remote_failure_tolerance_s`, and kills
|
|
135
|
+
the ssh process group on exit so no orphan tree is left behind.
|
|
136
|
+
|
|
137
|
+
> **Detection stays on the host.** `--host` with `--mode anomaly | narrate |
|
|
138
|
+
> http` exits with an error: the detectors read the supervised host's logs and
|
|
139
|
+
> auto-stop its service, which must keep working with your laptop closed. Run
|
|
140
|
+
> the monitor there (`ssh <alias>`, then `agent-runner monitor`) and relay its
|
|
141
|
+
> events here. See [`docs/runbook.md`](docs/runbook.md) § "Remote event relay &
|
|
142
|
+
> SSH trust" — the SSH trust boundary applies to any agent-runner command you
|
|
143
|
+
> drive over ssh.
|
|
133
144
|
|
|
134
145
|
## Documentation
|
|
135
146
|
|
|
@@ -80,20 +80,31 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
80
80
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
81
81
|
- `disk_critical` — writing to a near-full disk risks corruption
|
|
82
82
|
|
|
83
|
-
Runs
|
|
83
|
+
Runs against the supervised project's local logs:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
agent-runner monitor #
|
|
87
|
-
agent-runner monitor --host pi # remote, 60s poll
|
|
86
|
+
agent-runner monitor # 30s poll
|
|
88
87
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
89
88
|
```
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
Watch a remote host's event stream from your laptop — one command instead of a
|
|
91
|
+
hand-rolled `ssh … ; sleep` loop:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
agent-runner monitor --host pi --mode events # managed ssh relay, JSONL stdout
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The relay reconnects with `--since <last ts>` so a dropped link replays its gap,
|
|
98
|
+
gives up (exit 1) once the outage passes `remote_failure_tolerance_s`, and kills
|
|
99
|
+
the ssh process group on exit so no orphan tree is left behind.
|
|
100
|
+
|
|
101
|
+
> **Detection stays on the host.** `--host` with `--mode anomaly | narrate |
|
|
102
|
+
> http` exits with an error: the detectors read the supervised host's logs and
|
|
103
|
+
> auto-stop its service, which must keep working with your laptop closed. Run
|
|
104
|
+
> the monitor there (`ssh <alias>`, then `agent-runner monitor`) and relay its
|
|
105
|
+
> events here. See [`docs/runbook.md`](docs/runbook.md) § "Remote event relay &
|
|
106
|
+
> SSH trust" — the SSH trust boundary applies to any agent-runner command you
|
|
107
|
+
> drive over ssh.
|
|
97
108
|
|
|
98
109
|
## Documentation
|
|
99
110
|
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
|
+
"emit_agent_auth_error_detected",
|
|
17
18
|
"emit_agent_usage_recorded",
|
|
18
19
|
"emit_anomaly_repetitive_tool",
|
|
19
20
|
"emit_fresh_eyes_round_triggered",
|
|
@@ -150,6 +151,38 @@ def emit_transient_error_detected(
|
|
|
150
151
|
)
|
|
151
152
|
|
|
152
153
|
|
|
154
|
+
def emit_agent_auth_error_detected(
|
|
155
|
+
log_dir: Path,
|
|
156
|
+
*,
|
|
157
|
+
round_num: int,
|
|
158
|
+
agent: str,
|
|
159
|
+
raw: str,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Emit an authentication/authorization failure the agent itself reported.
|
|
162
|
+
|
|
163
|
+
Contract: emitted by a per-CLI plugin only when the agent's OWN structured
|
|
164
|
+
output names the failure (e.g. an HTTP 401 in its JSON event stream). That
|
|
165
|
+
is certain evidence, unlike the monitor's ``oauth_fail`` text heuristic,
|
|
166
|
+
which scans free-text log tails and therefore needs a nonzero-exit shield
|
|
167
|
+
against prose that merely mentions "401". The monitor counts a round
|
|
168
|
+
carrying this event without that shield — which is what makes an auth loop
|
|
169
|
+
visible for a CLI that exits 0 on provider failure.
|
|
170
|
+
|
|
171
|
+
No back-off partner event: an auth failure is permanent until an operator
|
|
172
|
+
fixes the credential, so it is deliberately not a transient classification.
|
|
173
|
+
"""
|
|
174
|
+
from agent_runner._redact import redact_secrets
|
|
175
|
+
from agent_runner.events import AGENT_AUTH_ERROR_DETECTED, emit
|
|
176
|
+
|
|
177
|
+
emit(
|
|
178
|
+
log_dir,
|
|
179
|
+
AGENT_AUTH_ERROR_DETECTED,
|
|
180
|
+
round_num=round_num,
|
|
181
|
+
agent=agent,
|
|
182
|
+
raw=redact_secrets(raw),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
153
186
|
def emit_transient_error_recovered(
|
|
154
187
|
log_dir: Path,
|
|
155
188
|
*,
|
|
@@ -205,7 +238,10 @@ def emit_agent_usage_recorded(
|
|
|
205
238
|
Claude: count of ``tool_use`` content blocks across all assistant events.
|
|
206
239
|
Gemini: ``stats.tool_calls``.
|
|
207
240
|
- ``phase``: phase label from HookContext (e.g. "planning"); empty string when None.
|
|
208
|
-
- ``success``:
|
|
241
|
+
- ``success``: the supervisor's clean-exit predicate (``RoundResult.ok``).
|
|
242
|
+
A plugin MAY additionally fold in the agent's own terminal verdict when
|
|
243
|
+
its CLI's exit code is unreliable — pi does, because pi exits 0 on
|
|
244
|
+
provider failure.
|
|
209
245
|
"""
|
|
210
246
|
from agent_runner.events import AGENT_USAGE_RECORDED, emit
|
|
211
247
|
|
|
@@ -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.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 5)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
import re
|
|
14
|
+
import shutil
|
|
14
15
|
import signal
|
|
15
16
|
import subprocess # noqa: TID251 — sanctioned subprocess caller
|
|
16
17
|
import threading
|
|
@@ -52,6 +53,15 @@ class RunResult:
|
|
|
52
53
|
killed_for_grace: bool = False
|
|
53
54
|
grace_kill_children: list[dict] = field(default_factory=list)
|
|
54
55
|
|
|
56
|
+
@property
|
|
57
|
+
def ok(self) -> bool:
|
|
58
|
+
"""The supervisor's round-success predicate: clean exit, no timeout.
|
|
59
|
+
|
|
60
|
+
Single definition of "did this round fail" at the supervisor level. A
|
|
61
|
+
plugin MAY narrow it further when its CLI's exit code is unreliable.
|
|
62
|
+
"""
|
|
63
|
+
return self.exit_code == 0 and not self.timed_out
|
|
64
|
+
|
|
55
65
|
|
|
56
66
|
def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
|
|
57
67
|
"""Build full argv: command + prompt args (with {prompt} substituted)."""
|
|
@@ -130,6 +140,24 @@ def _live_children(
|
|
|
130
140
|
_RESULT_MARKER = b'"type":"result"'
|
|
131
141
|
|
|
132
142
|
|
|
143
|
+
def resolve_exec_target(command0: str, work_dir: Path, env_path: str | None = None) -> str | None:
|
|
144
|
+
"""Model of Popen's POSIX exec resolution for argv[0].
|
|
145
|
+
|
|
146
|
+
Kept beside the ``Popen`` in :func:`run` so validation and exec cannot
|
|
147
|
+
drift: a slash-containing argv[0] resolves against the child's cwd
|
|
148
|
+
(``work_dir``); a bare name is looked up on the CHILD's PATH
|
|
149
|
+
(``env_path`` — pass ``[agent.env]``'s PATH override if set; ``None``
|
|
150
|
+
falls back to the supervisor's, matching env inheritance). Returns the
|
|
151
|
+
resolved executable path, or ``None`` if it would not exec.
|
|
152
|
+
"""
|
|
153
|
+
if "/" in command0:
|
|
154
|
+
candidate = Path(command0)
|
|
155
|
+
if not candidate.is_absolute():
|
|
156
|
+
candidate = work_dir / candidate
|
|
157
|
+
return shutil.which(str(candidate))
|
|
158
|
+
return shutil.which(command0, path=env_path)
|
|
159
|
+
|
|
160
|
+
|
|
133
161
|
def run(
|
|
134
162
|
*,
|
|
135
163
|
command: list[str],
|
|
@@ -150,11 +178,9 @@ def run(
|
|
|
150
178
|
|
|
151
179
|
Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → REAP_GRACE_S → SIGKILL.
|
|
152
180
|
|
|
153
|
-
work_dir: the agent child's working directory
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
with no --cwd flag of their own (e.g. pi). Callers pass the already-absolute
|
|
157
|
-
cfg.runtime.work_dir.
|
|
181
|
+
work_dir: the agent child's working directory; callers pass the
|
|
182
|
+
already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
|
|
183
|
+
own (e.g. pi) depend on this.
|
|
158
184
|
|
|
159
185
|
max_grace_after_result_s: when > 0, start a countdown after the first
|
|
160
186
|
type=result event is detected in the log. After it elapses, reap the
|
|
@@ -182,8 +208,9 @@ def run(
|
|
|
182
208
|
if stdin_mode
|
|
183
209
|
else _build_argv(command, prompt_arg_template, prompt)
|
|
184
210
|
)
|
|
185
|
-
# PWD pinned
|
|
186
|
-
|
|
211
|
+
# PWD pinned last — it mirrors cwd= (a correctness pin, not a knob), so
|
|
212
|
+
# an [agent.env] PWD cannot silently diverge from where the child runs.
|
|
213
|
+
env = {**os.environ, **env_extra, "PWD": str(work_dir)}
|
|
187
214
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
188
215
|
log_file = log_path.open("w", encoding="utf-8")
|
|
189
216
|
start = time.time()
|
|
@@ -16,9 +16,9 @@ import signal
|
|
|
16
16
|
import subprocess # noqa: TID251 — api uses systemctl + ssh, both subprocess
|
|
17
17
|
import sysconfig
|
|
18
18
|
import time
|
|
19
|
-
from collections.abc import Iterator
|
|
19
|
+
from collections.abc import Iterator, Sequence
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Any, Literal
|
|
21
|
+
from typing import Any, Literal, TextIO
|
|
22
22
|
|
|
23
23
|
from agent_runner import events, lifecycle
|
|
24
24
|
from agent_runner.api_types import (
|
|
@@ -383,8 +383,6 @@ from agent_runner import defenses, monitor # noqa: E402
|
|
|
383
383
|
from agent_runner.events import ( # noqa: E402
|
|
384
384
|
AGENT_NETWORK_BLIP,
|
|
385
385
|
HOOK_FAILED,
|
|
386
|
-
MONITOR_REMOTE_BLIP,
|
|
387
|
-
MONITOR_REMOTE_GIVEUP,
|
|
388
386
|
MONITOR_STARTED,
|
|
389
387
|
)
|
|
390
388
|
|
|
@@ -479,14 +477,13 @@ def peek(
|
|
|
479
477
|
return state if select is None else select_path(state, select)
|
|
480
478
|
|
|
481
479
|
|
|
482
|
-
def _poll_once(project: str | Path
|
|
480
|
+
def _poll_once(project: str | Path) -> list[monitor.Alert]:
|
|
483
481
|
work_dir = project if isinstance(project, Path) else Path.cwd()
|
|
484
482
|
cfg = load_config(work_dir / "agent-runner.toml")
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
src = monitor.RemoteSource(host=host, project=_project_name(work_dir))
|
|
483
|
+
# Always local: detection runs on the supervised host by design. Remote
|
|
484
|
+
# observation is an event RELAY (``monitor --host X --mode events``), not a
|
|
485
|
+
# remote poll — see agent_runner/remote_relay.py.
|
|
486
|
+
src: monitor.StateSource = monitor.LocalSource(log_dir=cfg.runtime.log_dir)
|
|
490
487
|
events = monitor.parse_events_from_jsonl_files(src.events_files())
|
|
491
488
|
metrics = monitor.parse_events_from_jsonl_files(src.metrics_files())
|
|
492
489
|
log_tails = monitor.load_round_log_tails(src.rounds_dir())
|
|
@@ -510,11 +507,6 @@ def _poll_once(project: str | Path, *, host: str | None) -> list[monitor.Alert]:
|
|
|
510
507
|
return builtin + plugin
|
|
511
508
|
|
|
512
509
|
|
|
513
|
-
# Backoff schedule for remote-failure retries: each element is the sleep duration
|
|
514
|
-
# in seconds for that attempt index (capped at 30s per step).
|
|
515
|
-
_REMOTE_FAILURE_BACKOFF = (1, 2, 4, 8, 16, 30)
|
|
516
|
-
|
|
517
|
-
|
|
518
510
|
def monitor_loop(
|
|
519
511
|
project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
|
|
520
512
|
) -> Iterator[monitor.Alert]:
|
|
@@ -525,12 +517,25 @@ def monitor_loop(
|
|
|
525
517
|
to that kind as the canonical "supervision is up" signal (monitor is otherwise
|
|
526
518
|
silent during healthy operation by design).
|
|
527
519
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
520
|
+
``host`` raises ``MonitorRemoteUnsupportedError`` immediately: detection runs
|
|
521
|
+
on the supervised host by design, so there is no remote polling mode. The
|
|
522
|
+
check is eager — this wrapper validates before handing back the generator,
|
|
523
|
+
so the failure lands at startup rather than at the first ``next()``. For a
|
|
524
|
+
remote event stream, see ``relay_remote_events``.
|
|
525
|
+
"""
|
|
526
|
+
if host is not None:
|
|
527
|
+
raise monitor.MonitorRemoteUnsupportedError(host)
|
|
528
|
+
return _monitor_loop_iter(project, host=host, interval_s=interval_s)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _monitor_loop_iter(
|
|
532
|
+
project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
|
|
533
|
+
) -> Iterator[monitor.Alert]:
|
|
534
|
+
"""Polling generator behind ``monitor_loop``.
|
|
535
|
+
|
|
536
|
+
``host`` is None by construction (``monitor_loop`` rejects anything else);
|
|
537
|
+
it is carried into the ``monitor_started`` payload as an explicit record
|
|
538
|
+
that this monitor watches its own host.
|
|
534
539
|
"""
|
|
535
540
|
import json as _json
|
|
536
541
|
|
|
@@ -547,51 +552,8 @@ def monitor_loop(
|
|
|
547
552
|
mode="anomaly-only",
|
|
548
553
|
)
|
|
549
554
|
|
|
550
|
-
tolerance_s = cfg.monitor.remote_failure_tolerance_s
|
|
551
|
-
blip_start: float | None = None
|
|
552
|
-
attempt = 0
|
|
553
|
-
|
|
554
555
|
while True:
|
|
555
|
-
|
|
556
|
-
alerts = _poll_once(work_dir, host=host)
|
|
557
|
-
# Success: reset retry state.
|
|
558
|
-
blip_start = None
|
|
559
|
-
attempt = 0
|
|
560
|
-
except monitor.MonitorRemoteError as e:
|
|
561
|
-
if tolerance_s == 0:
|
|
562
|
-
raise # 0.1.10 behavior preserved
|
|
563
|
-
now = time.monotonic()
|
|
564
|
-
if blip_start is None:
|
|
565
|
-
blip_start = now
|
|
566
|
-
attempt += 1
|
|
567
|
-
elapsed = now - blip_start
|
|
568
|
-
if elapsed >= tolerance_s:
|
|
569
|
-
events.emit(
|
|
570
|
-
cfg.runtime.log_dir,
|
|
571
|
-
MONITOR_REMOTE_GIVEUP,
|
|
572
|
-
host=host,
|
|
573
|
-
total_attempts=attempt,
|
|
574
|
-
total_elapsed_s=elapsed,
|
|
575
|
-
cap_s=tolerance_s,
|
|
576
|
-
final_error=e.stderr,
|
|
577
|
-
)
|
|
578
|
-
raise
|
|
579
|
-
backoff_idx = min(attempt - 1, len(_REMOTE_FAILURE_BACKOFF) - 1)
|
|
580
|
-
next_sleep_s = min(_REMOTE_FAILURE_BACKOFF[backoff_idx], tolerance_s - elapsed)
|
|
581
|
-
events.emit(
|
|
582
|
-
cfg.runtime.log_dir,
|
|
583
|
-
MONITOR_REMOTE_BLIP,
|
|
584
|
-
host=host,
|
|
585
|
-
error=e.stderr,
|
|
586
|
-
attempt=attempt,
|
|
587
|
-
elapsed_s=elapsed,
|
|
588
|
-
cap_s=tolerance_s,
|
|
589
|
-
interval_s=interval_s,
|
|
590
|
-
next_sleep_s=next_sleep_s,
|
|
591
|
-
)
|
|
592
|
-
time.sleep(next_sleep_s)
|
|
593
|
-
continue
|
|
594
|
-
|
|
556
|
+
alerts = _poll_once(work_dir)
|
|
595
557
|
for alert in alerts:
|
|
596
558
|
key = f"{alert.detector}:{_json.dumps(alert.context, sort_keys=True)}"
|
|
597
559
|
if key in seen:
|
|
@@ -601,7 +563,6 @@ def monitor_loop(
|
|
|
601
563
|
monitor.on_alert(
|
|
602
564
|
alert,
|
|
603
565
|
project=_project_name(work_dir),
|
|
604
|
-
host=host,
|
|
605
566
|
log_dir=cfg.runtime.log_dir,
|
|
606
567
|
allowed_stop_names=cfg.monitor.auto_stop_on,
|
|
607
568
|
)
|
|
@@ -771,6 +732,7 @@ def check_self_terminated_sentinel(log_dir: Path) -> bool:
|
|
|
771
732
|
# Re-export emit_* wrappers from _emit module (extracted for size hygiene).
|
|
772
733
|
# Preserves the public import surface: `from agent_runner.api import emit_*` continues to work.
|
|
773
734
|
from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-export
|
|
735
|
+
emit_agent_auth_error_detected,
|
|
774
736
|
emit_agent_usage_recorded,
|
|
775
737
|
emit_anomaly_repetitive_tool,
|
|
776
738
|
emit_config_broken,
|
|
@@ -829,6 +791,41 @@ def stream_events_jsonl(log_dir: Path, *, poll_interval_s: float = 0.1) -> Itera
|
|
|
829
791
|
yield from _tail_events_jsonl(log_dir, start_at_now=True, poll_interval_s=poll_interval_s)
|
|
830
792
|
|
|
831
793
|
|
|
794
|
+
def relay_remote_events(
|
|
795
|
+
host: str,
|
|
796
|
+
*,
|
|
797
|
+
log_dir: Path,
|
|
798
|
+
kinds: Sequence[str] | None = None,
|
|
799
|
+
remote_config: str | None = None,
|
|
800
|
+
failure_tolerance_s: float = 90.0,
|
|
801
|
+
out: TextIO | None = None,
|
|
802
|
+
) -> int:
|
|
803
|
+
"""Remote sibling of ``stream_events_jsonl``: relay ``host``'s event stream.
|
|
804
|
+
|
|
805
|
+
Spawns a managed ``ssh <host> -- agent-runner events --tail`` and passes its
|
|
806
|
+
JSONL through, reconnecting with ``--since`` so a dropped link replays its
|
|
807
|
+
gap. Returns a CLI exit code (0 on interrupt, 1 once the link stays down
|
|
808
|
+
past ``failure_tolerance_s``). Blocks until then.
|
|
809
|
+
|
|
810
|
+
``log_dir`` is this CLIENT's log dir: the ``monitor_remote_blip`` /
|
|
811
|
+
``monitor_remote_giveup`` events it writes describe the local machine's link
|
|
812
|
+
to ``host``, not the supervised project. Detection is deliberately NOT
|
|
813
|
+
relayed — the detectors run on ``host`` (see ``monitor_loop``).
|
|
814
|
+
"""
|
|
815
|
+
# Late import: pulls agent_runtime (psutil, threading) only for the callers
|
|
816
|
+
# that actually stream from a remote host.
|
|
817
|
+
from agent_runner.remote_relay import relay_remote_events as _relay
|
|
818
|
+
|
|
819
|
+
return _relay(
|
|
820
|
+
host,
|
|
821
|
+
log_dir=log_dir,
|
|
822
|
+
kinds=kinds,
|
|
823
|
+
remote_config=remote_config,
|
|
824
|
+
failure_tolerance_s=failure_tolerance_s,
|
|
825
|
+
out=out,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
|
|
832
829
|
def _format_narrate_line(evt: dict[str, Any]) -> str:
|
|
833
830
|
"""Format an event dict as a one-line human-readable string.
|
|
834
831
|
|
|
@@ -173,6 +173,15 @@ class RoundResult:
|
|
|
173
173
|
dirty_outcome: DirtyOutcome | None = None
|
|
174
174
|
killed_for_grace: bool = False
|
|
175
175
|
|
|
176
|
+
@property
|
|
177
|
+
def ok(self) -> bool:
|
|
178
|
+
"""The supervisor's round-success predicate: clean exit, no timeout.
|
|
179
|
+
|
|
180
|
+
Single definition of "did this round fail" at the supervisor level. A
|
|
181
|
+
plugin MAY narrow it further when its CLI's exit code is unreliable.
|
|
182
|
+
"""
|
|
183
|
+
return self.exit_code == 0 and not self.timed_out
|
|
184
|
+
|
|
176
185
|
|
|
177
186
|
@dataclass(frozen=True)
|
|
178
187
|
class InitResult:
|