cli-agent-runner 0.2.3__tar.gz → 0.2.4__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.4}/CHANGELOG.md +21 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/PKG-INFO +10 -10
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/README.md +9 -9
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_emit.py +37 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/agent_runtime.py +34 -7
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/api.py +21 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/api_types.py +9 -0
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/_constants.py +125 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/claude_rate_limit.py +11 -27
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/codewhale.py +16 -36
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/gemini.py +6 -28
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/kimi.py +93 -0
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/pi.py +279 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/monitor_cmd.py +5 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/config.py +7 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/hooks.py +5 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/monitor.py +82 -11
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/pi.toml +2 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/round_log.py +30 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/runner.py +6 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/service_unit.py +7 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/startup_check.py +19 -20
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/architecture.md +7 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/commands.md +7 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/configuration.md +2 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.2.3.md +5 -8
- cli_agent_runner-0.2.4/docs/migrations/0.2.4.md +66 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/plugins.md +78 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/recipes/aider.md +5 -3
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/recipes/pi.md +14 -7
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/runbook.md +63 -22
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/thesis.md +3 -2
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/pyproject.toml +2 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/_test_helpers.py +26 -5
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_run_one_round_with_fake_agent.py +2 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_claims_match_ssot.py +2 -1
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime.py +21 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_observation.py +47 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_claude_error_detector.py +31 -30
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli_service_peek_monitor.py +27 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_codewhale_plugin.py +14 -12
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config.py +59 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_events.py +39 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_gemini_plugin.py +12 -12
- cli_agent_runner-0.2.4/tests/unit/test_kimi_plugin.py +147 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detectors.py +57 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_remote.py +9 -0
- cli_agent_runner-0.2.4/tests/unit/test_pi_plugin.py +449 -0
- cli_agent_runner-0.2.4/tests/unit/test_plugin_constants.py +61 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_presets.py +1 -6
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_round_log_helpers.py +60 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_runner.py +0 -39
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_service_unit.py +9 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/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 → cli_agent_runner-0.2.4}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.gitignore +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/LICENSE +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/README.zh.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/build.sh +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/README.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/events.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.28.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.30.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.35.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.1.42.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.2.0.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.2.1.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/migrations/0.2.2.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/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.4}/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.4}/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.4}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/tests/unit/test_vcs_state.py +0 -0
|
@@ -5,9 +5,24 @@ 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.4] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `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.
|
|
12
|
+
- `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.
|
|
13
|
+
- `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.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
- 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.
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
23
|
+
See `docs/migrations/0.2.4.md`.
|
|
9
24
|
|
|
10
|
-
|
|
25
|
+
## [0.2.3] - 2026-07-26
|
|
11
26
|
|
|
12
27
|
### Added
|
|
13
28
|
- `--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 +32,8 @@ Details: [docs/migrations/0.2.3.md](docs/migrations/0.2.3.md)
|
|
|
17
32
|
- 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
33
|
- 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
34
|
|
|
35
|
+
See `docs/migrations/0.2.3.md`.
|
|
36
|
+
|
|
20
37
|
## [0.2.2] - 2026-07-18
|
|
21
38
|
|
|
22
39
|
### Removed
|
|
@@ -921,7 +938,8 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
921
938
|
- Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
|
|
922
939
|
gated by a manual approval on the `pypi` GitHub environment.
|
|
923
940
|
|
|
924
|
-
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.
|
|
941
|
+
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.4...HEAD
|
|
942
|
+
[0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
|
|
925
943
|
[0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
|
|
926
944
|
[0.2.2]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.1...v0.2.2
|
|
927
945
|
[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.4
|
|
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
|
|
@@ -116,20 +116,20 @@ 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
|
-
> See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
132
|
-
>
|
|
126
|
+
> **Remote monitoring is unsupported in this version**: `monitor --host <alias>`
|
|
127
|
+
> cannot read a remote host's round logs or events, so it exits with an error at
|
|
128
|
+
> startup instead of watching an empty world and reporting healthy. Run the
|
|
129
|
+
> monitor on the supervised host itself (`ssh <alias>`, then `agent-runner
|
|
130
|
+
> monitor`). See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
131
|
+
> trust" — the SSH trust boundary applies to any agent-runner command you drive
|
|
132
|
+
> over ssh.
|
|
133
133
|
|
|
134
134
|
## Documentation
|
|
135
135
|
|
|
@@ -80,20 +80,20 @@ 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
|
-
> See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
96
|
-
>
|
|
90
|
+
> **Remote monitoring is unsupported in this version**: `monitor --host <alias>`
|
|
91
|
+
> cannot read a remote host's round logs or events, so it exits with an error at
|
|
92
|
+
> startup instead of watching an empty world and reporting healthy. Run the
|
|
93
|
+
> monitor on the supervised host itself (`ssh <alias>`, then `agent-runner
|
|
94
|
+
> monitor`). See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
95
|
+
> trust" — the SSH trust boundary applies to any agent-runner command you drive
|
|
96
|
+
> over ssh.
|
|
97
97
|
|
|
98
98
|
## Documentation
|
|
99
99
|
|
|
@@ -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.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 4)
|
|
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()
|
|
@@ -486,6 +486,8 @@ def _poll_once(project: str | Path, *, host: str | None) -> list[monitor.Alert]:
|
|
|
486
486
|
if host is None:
|
|
487
487
|
src = monitor.LocalSource(log_dir=cfg.runtime.log_dir)
|
|
488
488
|
else:
|
|
489
|
+
# Dormant: monitor_loop rejects --host before any poll, so this branch
|
|
490
|
+
# is unreachable today. See monitor.MonitorRemoteUnsupportedError.
|
|
489
491
|
src = monitor.RemoteSource(host=host, project=_project_name(work_dir))
|
|
490
492
|
events = monitor.parse_events_from_jsonl_files(src.events_files())
|
|
491
493
|
metrics = monitor.parse_events_from_jsonl_files(src.metrics_files())
|
|
@@ -525,12 +527,29 @@ def monitor_loop(
|
|
|
525
527
|
to that kind as the canonical "supervision is up" signal (monitor is otherwise
|
|
526
528
|
silent during healthy operation by design).
|
|
527
529
|
|
|
530
|
+
``host`` (remote mode) raises ``MonitorRemoteUnsupportedError`` immediately:
|
|
531
|
+
remote reads are unimplemented, so a remote loop would poll an empty world
|
|
532
|
+
and report healthy forever. The check is eager — this wrapper validates
|
|
533
|
+
before handing back the generator, so the failure lands at startup rather
|
|
534
|
+
than at the first ``next()``.
|
|
535
|
+
"""
|
|
536
|
+
if host is not None:
|
|
537
|
+
raise monitor.MonitorRemoteUnsupportedError(host)
|
|
538
|
+
return _monitor_loop_iter(project, host=host, interval_s=interval_s)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _monitor_loop_iter(
|
|
542
|
+
project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
|
|
543
|
+
) -> Iterator[monitor.Alert]:
|
|
544
|
+
"""Polling generator behind ``monitor_loop``.
|
|
545
|
+
|
|
528
546
|
Tolerates transient ``MonitorRemoteError`` failures (from ``--host`` ssh)
|
|
529
547
|
for up to ``cfg.monitor.remote_failure_tolerance_s`` seconds with exponential
|
|
530
548
|
backoff (1s → 2s → 4s → ... → 30s cap). Each retry emits ``monitor_remote_blip``;
|
|
531
549
|
crossing the cap emits one ``monitor_remote_giveup`` and propagates the error
|
|
532
550
|
(CLI exits 1; systemd restarts the process). Setting tolerance to 0 preserves
|
|
533
|
-
the 0.1.10 immediate-propagate behavior with no blip events emitted.
|
|
551
|
+
the 0.1.10 immediate-propagate behavior with no blip events emitted. That
|
|
552
|
+
path is dormant while remote mode is rejected at startup.
|
|
534
553
|
"""
|
|
535
554
|
import json as _json
|
|
536
555
|
|
|
@@ -771,6 +790,7 @@ def check_self_terminated_sentinel(log_dir: Path) -> bool:
|
|
|
771
790
|
# Re-export emit_* wrappers from _emit module (extracted for size hygiene).
|
|
772
791
|
# Preserves the public import surface: `from agent_runner.api import emit_*` continues to work.
|
|
773
792
|
from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-export
|
|
793
|
+
emit_agent_auth_error_detected,
|
|
774
794
|
emit_agent_usage_recorded,
|
|
775
795
|
emit_anomaly_repetitive_tool,
|
|
776
796
|
emit_config_broken,
|
|
@@ -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:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Shared constants and tail-scan helpers for built-in CLI plugins.
|
|
2
|
+
|
|
3
|
+
Extracted to single source of truth so every round-log tail-scanner
|
|
4
|
+
(plugin JSONL parsers and monitor text detectors) uses the same window
|
|
5
|
+
size, raw-text caps, transient-error back-off defaults, and the same
|
|
6
|
+
HTTP-status → classification ladder.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from collections import deque
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, TextIO
|
|
16
|
+
|
|
17
|
+
_TAIL_LINES: int = 200
|
|
18
|
+
"""Number of log lines to scan from the end of a round log.
|
|
19
|
+
|
|
20
|
+
The round log is merged stdout+stderr. JSONL consumers window via
|
|
21
|
+
``json_tail`` (chatter filtered before windowing); plain-text consumers
|
|
22
|
+
(monitor oauth/network detectors) window the raw tail — keep this generous
|
|
23
|
+
so a stderr burst cannot evict what they scan for."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def json_tail(f: TextIO, maxlen: int = _TAIL_LINES) -> deque[str]:
|
|
27
|
+
"""Last ``maxlen`` JSON-looking lines of a merged round log.
|
|
28
|
+
|
|
29
|
+
Non-JSON chatter (stderr text) is filtered BEFORE windowing, so a burst
|
|
30
|
+
of any size cannot evict the terminal JSONL event from the window.
|
|
31
|
+
"""
|
|
32
|
+
return deque((ln for ln in f if ln.lstrip()[:1] in "{["), maxlen=maxlen)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def json_events(log_path: Path) -> Iterator[dict]:
|
|
36
|
+
"""JSON objects in a round log's tail window, in file order.
|
|
37
|
+
|
|
38
|
+
One reader for every plugin's parse loop: window via ``json_tail``, then
|
|
39
|
+
per line strip / skip blank / ``json.loads`` / drop anything that is not an
|
|
40
|
+
object. Non-JSON lines are expected — the round log merges stdout+stderr,
|
|
41
|
+
and every CLI writes some plain text there.
|
|
42
|
+
"""
|
|
43
|
+
with log_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
44
|
+
tail = json_tail(f)
|
|
45
|
+
for line in tail:
|
|
46
|
+
line = line.strip()
|
|
47
|
+
if not line:
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
event = json.loads(line)
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
continue
|
|
53
|
+
if isinstance(event, dict):
|
|
54
|
+
yield event
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_RAW_CAP: int = 200
|
|
58
|
+
"""Maximum length for ``raw`` field in transient_error_detected payload."""
|
|
59
|
+
|
|
60
|
+
# Default back-off durations (seconds) for non-precise transient classifications.
|
|
61
|
+
# rate_limit_account uses exact resetsAt epoch from claude; not in this table.
|
|
62
|
+
_BACK_OFF_DEFAULTS: dict[str, int] = {
|
|
63
|
+
"rate_limit_model": 60,
|
|
64
|
+
"api_transient_5xx": 60,
|
|
65
|
+
"api_timeout": 30,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# 5xx codes treated as transient (retry-worthy server errors per RFC 9110):
|
|
69
|
+
# 500=unexpected, 502=bad gateway, 503=unavailable, 504=gateway timeout,
|
|
70
|
+
# 529=overloaded (Anthropic's non-RFC code emitted during sustained capacity
|
|
71
|
+
# issues; treated as transient per Anthropic SDK behavior).
|
|
72
|
+
# Excluded: 501 (not implemented = permanent), 505 (HTTP version mismatch).
|
|
73
|
+
_5XX_STATUSES: frozenset[int] = frozenset({500, 502, 503, 504, 529})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def classify_transient_status(status: Any) -> str | None:
|
|
77
|
+
"""Map an HTTP status from a CLI's error record to a transient bucket.
|
|
78
|
+
|
|
79
|
+
The one ladder every plugin classifier delegates to, so a status is
|
|
80
|
+
bucketed identically no matter which CLI reported it.
|
|
81
|
+
|
|
82
|
+
``None`` means 'not transient — do not back off'. Notably 401 (auth) and
|
|
83
|
+
404 (unknown model) land here: both are permanent until an operator fixes
|
|
84
|
+
configuration, which is oauth/config territory rather than something a
|
|
85
|
+
back-off would clear. Non-integer or missing statuses are ``None`` too;
|
|
86
|
+
a CLI that reports no status gets no classification.
|
|
87
|
+
"""
|
|
88
|
+
if not isinstance(status, int) or isinstance(status, bool):
|
|
89
|
+
return None
|
|
90
|
+
if status == 429:
|
|
91
|
+
return "rate_limit_model"
|
|
92
|
+
if status in _5XX_STATUSES:
|
|
93
|
+
return "api_transient_5xx"
|
|
94
|
+
if status == 408:
|
|
95
|
+
return "api_timeout"
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_CLASSIFICATIONS: frozenset[str] = frozenset(
|
|
100
|
+
{
|
|
101
|
+
"rate_limit_account",
|
|
102
|
+
"rate_limit_model",
|
|
103
|
+
"api_transient_5xx",
|
|
104
|
+
"api_timeout",
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
"""Canonical set of transient-error classifications.
|
|
108
|
+
|
|
109
|
+
rate_limit_account uses server-provided resetsAt (excluded from
|
|
110
|
+
_BACK_OFF_DEFAULTS table); others use defaults from that table.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
_EXP_CAP: int = 5
|
|
114
|
+
"""Maximum exponent for transient-error consecutive backoff: 2^5 = 32×.
|
|
115
|
+
|
|
116
|
+
Beyond this, the multiplier plateaus. Combined with _ABSOLUTE_CAP_S, this
|
|
117
|
+
prevents runaway wait times during sustained outages (max wait = 30min).
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
_ABSOLUTE_CAP_S: int = 1800
|
|
121
|
+
"""Absolute upper bound on supervisor-applied transient back-off (30 min).
|
|
122
|
+
|
|
123
|
+
Applies after exp multiplier — even if base × 2^5 exceeds this, the wait
|
|
124
|
+
is clipped here. Defends against an indefinitely-stuck supervisor.
|
|
125
|
+
"""
|
{cli_agent_runner-0.2.3 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/claude_rate_limit.py
RENAMED
|
@@ -14,9 +14,7 @@ rate-limit detector was generalized to multi-classification in 0.1.23
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
import json
|
|
18
17
|
import time
|
|
19
|
-
from collections import deque
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
from typing import Any
|
|
22
20
|
|
|
@@ -26,10 +24,10 @@ from agent_runner.api import (
|
|
|
26
24
|
emit_transient_error_detected,
|
|
27
25
|
)
|
|
28
26
|
from agent_runner.builtin_plugins._constants import (
|
|
29
|
-
_5XX_STATUSES,
|
|
30
27
|
_BACK_OFF_DEFAULTS,
|
|
31
28
|
_RAW_CAP,
|
|
32
|
-
|
|
29
|
+
classify_transient_status,
|
|
30
|
+
json_events,
|
|
33
31
|
)
|
|
34
32
|
from agent_runner.hooks import HookContext, register_post_round_hook
|
|
35
33
|
|
|
@@ -60,7 +58,7 @@ class ClaudeErrorDetector:
|
|
|
60
58
|
ctx.log_dir,
|
|
61
59
|
round_num=ctx.round_num,
|
|
62
60
|
phase=ctx.phase or "",
|
|
63
|
-
success=
|
|
61
|
+
success=result.ok,
|
|
64
62
|
**parsed["usage"],
|
|
65
63
|
)
|
|
66
64
|
|
|
@@ -114,26 +112,17 @@ def _parse_claude_log(
|
|
|
114
112
|
anomaly_window: int = 0,
|
|
115
113
|
anomaly_threshold: int = 0,
|
|
116
114
|
) -> dict[str, Any]:
|
|
117
|
-
"""Scan
|
|
115
|
+
"""Scan the JSON tail window for rate_limit/result/assistant events.
|
|
118
116
|
|
|
119
117
|
Returns dict with optional 'transient_error', 'usage', and 'anomaly' keys.
|
|
120
118
|
anomaly_window/anomaly_threshold: when both > 0, slide a window over
|
|
121
119
|
(tool_name, target) tuples; populate 'anomaly' if threshold reached.
|
|
122
120
|
"""
|
|
123
|
-
with log_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
124
|
-
tail = deque(f, maxlen=_TAIL_LINES)
|
|
125
121
|
rate_limit_info: dict | None = None
|
|
126
122
|
result_event: dict | None = None
|
|
127
123
|
assistant_model: str | None = None
|
|
128
124
|
tool_calls: list[tuple[str, str | None]] = []
|
|
129
|
-
for
|
|
130
|
-
line = line.strip()
|
|
131
|
-
if not line:
|
|
132
|
-
continue
|
|
133
|
-
try:
|
|
134
|
-
event = json.loads(line)
|
|
135
|
-
except json.JSONDecodeError:
|
|
136
|
-
continue
|
|
125
|
+
for event in json_events(log_path):
|
|
137
126
|
event_type = event.get("type")
|
|
138
127
|
if event_type == "rate_limit_event":
|
|
139
128
|
rli = event.get("rate_limit_info", {})
|
|
@@ -179,8 +168,8 @@ def _parse_claude_log(
|
|
|
179
168
|
def _classify_transient_error(
|
|
180
169
|
rate_limit_info: dict | None, result_event: dict | None
|
|
181
170
|
) -> dict | None:
|
|
182
|
-
"""
|
|
183
|
-
|
|
171
|
+
"""Claude-specific precedence: an account-level rate_limit_event outranks the
|
|
172
|
+
result event's ``api_error_status``, which is handed to the shared ladder.
|
|
184
173
|
"""
|
|
185
174
|
if rate_limit_info is not None and rate_limit_info.get("rateLimitType") == "five_hour":
|
|
186
175
|
return {
|
|
@@ -193,15 +182,10 @@ def _classify_transient_error(
|
|
|
193
182
|
# classification below.
|
|
194
183
|
if result_event is None or result_event.get("is_error") is not True:
|
|
195
184
|
return None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if status in _5XX_STATUSES:
|
|
201
|
-
return _classify("api_transient_5xx", raw)
|
|
202
|
-
if status == 408:
|
|
203
|
-
return _classify("api_timeout", raw)
|
|
204
|
-
return None
|
|
185
|
+
classification = classify_transient_status(result_event.get("api_error_status"))
|
|
186
|
+
if classification is None:
|
|
187
|
+
return None
|
|
188
|
+
return _classify(classification, str(result_event.get("result", ""))[:_RAW_CAP])
|
|
205
189
|
|
|
206
190
|
|
|
207
191
|
def _extract_usage(result_event: dict, *, model: str | None, tool_call_count: int) -> dict | None:
|
|
@@ -12,9 +12,7 @@ is added when a real rate-limit sample is captured.
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
import json
|
|
16
15
|
import time
|
|
17
|
-
from collections import deque
|
|
18
16
|
from pathlib import Path
|
|
19
17
|
from typing import Any
|
|
20
18
|
|
|
@@ -23,10 +21,10 @@ from agent_runner.api import (
|
|
|
23
21
|
emit_transient_error_detected,
|
|
24
22
|
)
|
|
25
23
|
from agent_runner.builtin_plugins._constants import (
|
|
26
|
-
_5XX_STATUSES,
|
|
27
24
|
_BACK_OFF_DEFAULTS,
|
|
28
25
|
_RAW_CAP,
|
|
29
|
-
|
|
26
|
+
classify_transient_status,
|
|
27
|
+
json_events,
|
|
30
28
|
)
|
|
31
29
|
from agent_runner.hooks import HookContext, register_post_round_hook
|
|
32
30
|
|
|
@@ -52,32 +50,22 @@ class CodewhaleErrorDetector:
|
|
|
52
50
|
ctx.log_dir,
|
|
53
51
|
round_num=ctx.round_num,
|
|
54
52
|
phase=ctx.phase or "",
|
|
55
|
-
success=
|
|
53
|
+
success=result.ok,
|
|
56
54
|
**parsed["usage"],
|
|
57
55
|
)
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
def _parse_codewhale_log(log_path: Path) -> dict[str, Any]:
|
|
61
|
-
"""Scan
|
|
59
|
+
"""Scan the JSON tail window of codewhale NDJSON; extract usage from the metadata
|
|
62
60
|
record; classify any {"type":"error"} that maps to a transient bucket.
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
codewhale prefixes some stdout lines with terminal escapes, so the
|
|
63
|
+
non-JSON lines ``json_events`` drops are routine here, not a corruption
|
|
64
|
+
signal.
|
|
66
65
|
"""
|
|
67
|
-
with log_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
68
|
-
tail = deque(f, maxlen=_TAIL_LINES)
|
|
69
66
|
metadata: dict | None = None
|
|
70
67
|
error_event: dict | None = None
|
|
71
|
-
for
|
|
72
|
-
line = line.strip()
|
|
73
|
-
if not line:
|
|
74
|
-
continue
|
|
75
|
-
try:
|
|
76
|
-
event = json.loads(line)
|
|
77
|
-
except json.JSONDecodeError:
|
|
78
|
-
continue
|
|
79
|
-
if not isinstance(event, dict):
|
|
80
|
-
continue
|
|
68
|
+
for event in json_events(log_path):
|
|
81
69
|
etype = event.get("type")
|
|
82
70
|
if etype == "metadata":
|
|
83
71
|
metadata = event.get("meta") or {}
|
|
@@ -111,23 +99,15 @@ def _parse_codewhale_log(log_path: Path) -> dict[str, Any]:
|
|
|
111
99
|
|
|
112
100
|
|
|
113
101
|
def _classify_codewhale_error(error_event: dict[str, Any]) -> str | None:
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
A future revision keys on a numeric status field once observed.
|
|
102
|
+
"""Pull a status code out of a codewhale {"type":"error"} record for the
|
|
103
|
+
shared ladder.
|
|
104
|
+
|
|
105
|
+
The only error record captured so far carries a free-text 'error' string
|
|
106
|
+
and no status code at all (an auth failure), so nothing maps today; the
|
|
107
|
+
numeric-field lookup is the forward path for when a real rate-limit or 5xx
|
|
108
|
+
sample is captured.
|
|
122
109
|
"""
|
|
123
|
-
|
|
124
|
-
if code == 429:
|
|
125
|
-
return "rate_limit_model"
|
|
126
|
-
if code in _5XX_STATUSES:
|
|
127
|
-
return "api_transient_5xx"
|
|
128
|
-
if code == 408:
|
|
129
|
-
return "api_timeout"
|
|
130
|
-
return None
|
|
110
|
+
return classify_transient_status(error_event.get("code") or error_event.get("status_code"))
|
|
131
111
|
|
|
132
112
|
|
|
133
113
|
register_post_round_hook(CodewhaleErrorDetector())
|