cli-agent-runner 0.1.39__tar.gz → 0.1.41__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.1.39 → cli_agent_runner-0.1.41}/.gitignore +3 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/CHANGELOG.md +17 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/PKG-INFO +1 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_emit.py +13 -7
- cli_agent_runner-0.1.41/agent_runner/_redact.py +102 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/agent_runtime.py +27 -19
- cli_agent_runner-0.1.41/agent_runner/builtin_plugins/codewhale.py +133 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/init_cmd.py +13 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/hooks.py +4 -3
- cli_agent_runner-0.1.41/agent_runner/presets/codewhale.toml +30 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/scaffold.py +2 -2
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/commands.md +4 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/configuration.md +11 -7
- cli_agent_runner-0.1.41/docs/migrations/0.1.40.md +42 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/quickstart.md +1 -1
- cli_agent_runner-0.1.41/docs/recipes/codewhale.md +98 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/runbook.md +12 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/thesis.md +26 -1
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/pyproject.toml +1 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_grace_kill_emission.py +3 -3
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_scaffold_presets.py +2 -2
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_doc_claims_match_ssot.py +11 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_agent_runtime_grace.py +47 -5
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_claude_error_detector.py +17 -0
- cli_agent_runner-0.1.41/tests/unit/test_codewhale_plugin.py +155 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_hooks.py +9 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_presets.py +30 -2
- cli_agent_runner-0.1.41/tests/unit/test_redact.py +136 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.codecov.yml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/LICENSE +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/README.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/README.zh.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/SECURITY.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/config.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/detector_helpers.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/build.sh +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/deploy/example-agent-runner.toml +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/deploy/launchd.plist.tmpl +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/deploy/run-loop.sh +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/deploy/systemd.service.tmpl +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/README.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/architecture.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/events.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/marketing/README.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/marketing/promo-cn.html +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.28.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.30.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.35.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/plugins.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/conftest.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_detector_helpers.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.41}/tests/unit/test_vcs_state.py +0 -0
|
@@ -5,7 +5,23 @@ 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
|
-
## [
|
|
8
|
+
## [0.1.41] - 2026-06-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- New `codewhale` preset — supervise Hmbown/CodeWhale (DeepSeek terminal agent) via `codewhale exec --auto --output-format stream-json`. `agent-runner init --preset codewhale`.
|
|
12
|
+
- New built-in `codewhale_error_detector` plugin — emits `agent_usage_recorded` (model + token counts) from codewhale's stream-json output. Transient-error classification is best-effort (mappable buckets only); auth failures surface via the existing monitor `oauth_fail` detector.
|
|
13
|
+
|
|
14
|
+
## [0.1.40] - 2026-05-31
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
- Grace-kill child-process fields (`live_children` / `ignored_children` in `round_grace_extended` / `round_grace_kill`) no longer store raw command lines — only the executable basename + pid (and, for ignored children, which ignore-pattern matched). This structurally prevents secrets passed in a child's arguments from reaching `events-*.jsonl`. The field shape changed from a list of strings to a list of objects.
|
|
18
|
+
- Free-text event excerpts that can carry agent output — `transient_error_detected.raw`, `hook_failed.error_message`/`traceback`, `serve_startup_hook_failed.exc_msg` — are now best-effort redacted (auth headers, tokens, credential URLs, `KEY=value` secrets, known key-prefixes, JWT, PEM).
|
|
19
|
+
- Pre-0.1.40 `events-*.jsonl` may contain unredacted argv/excerpts — see `docs/migrations/0.1.40.md`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Docs: `configuration.md` `[monitor.host_health]` example points to the generated schema table instead of restating default values.
|
|
23
|
+
|
|
24
|
+
## [0.1.39] - 2026-05-29
|
|
9
25
|
|
|
10
26
|
### Fixed
|
|
11
27
|
- Grace-kill (`max_grace_after_result_s`) is no longer defeated by long-lived helper subprocesses (e.g. claude's persistent Bash-tool shell-snapshot). `[runtime] grace_kill_ignore_patterns` lists regexes for cmdlines to exclude from the liveness count; the claude preset ships a matching default.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.41
|
|
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
|
|
@@ -112,8 +112,10 @@ def emit_transient_error_detected(
|
|
|
112
112
|
raw: str,
|
|
113
113
|
) -> None:
|
|
114
114
|
"""Emit detection of a transient agent error (rate limit / 5xx / timeout)."""
|
|
115
|
+
from agent_runner._redact import redact_secrets
|
|
115
116
|
from agent_runner.events import TRANSIENT_ERROR_DETECTED, emit
|
|
116
117
|
|
|
118
|
+
raw = redact_secrets(raw)
|
|
117
119
|
emit(
|
|
118
120
|
log_dir,
|
|
119
121
|
TRANSIENT_ERROR_DETECTED,
|
|
@@ -234,12 +236,15 @@ def emit_round_grace_kill(
|
|
|
234
236
|
*,
|
|
235
237
|
round_num: int,
|
|
236
238
|
grace_s: int,
|
|
237
|
-
live_children: list[
|
|
239
|
+
live_children: list[dict] | None = None,
|
|
238
240
|
) -> None:
|
|
239
241
|
"""Emit when the subprocess was killed because the grace-after-result timer
|
|
240
242
|
expired AND the agent's process group had no live worker processes left
|
|
241
243
|
(a genuine hang). Distinct from round_grace_extended (grace elapsed but a
|
|
242
244
|
worker was still running) and round_timeout_kill (wall-clock exceeded).
|
|
245
|
+
|
|
246
|
+
live_children: list of ``{"name": <exe basename>, "pid": <int>}`` dicts
|
|
247
|
+
(0.1.40+; previously list of cmdline strings).
|
|
243
248
|
"""
|
|
244
249
|
from agent_runner.events import ROUND_GRACE_KILL, emit
|
|
245
250
|
|
|
@@ -257,17 +262,18 @@ def emit_round_grace_extended(
|
|
|
257
262
|
*,
|
|
258
263
|
round_num: int,
|
|
259
264
|
grace_s: int,
|
|
260
|
-
live_children: list[
|
|
261
|
-
ignored_children: list[
|
|
265
|
+
live_children: list[dict],
|
|
266
|
+
ignored_children: list[dict] | None = None,
|
|
262
267
|
) -> None:
|
|
263
268
|
"""Emit when the grace-after-result timer expired but the agent still had
|
|
264
269
|
live worker processes (e.g. a backgrounded build), so the round was NOT
|
|
265
270
|
killed; it continues until it finishes or hits round_timeout_s.
|
|
266
271
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
live_children: list of ``{"name": <exe basename>, "pid": <int>}`` dicts
|
|
273
|
+
(0.1.40+; previously list of cmdline strings).
|
|
274
|
+
ignored_children: list of ``{"name": ..., "pid": ..., "matched": <pattern>}``
|
|
275
|
+
dicts for children that matched a grace_kill_ignore_patterns entry
|
|
276
|
+
and were excluded from the liveness count (0.1.40+; previously cmdline strings).
|
|
271
277
|
"""
|
|
272
278
|
from agent_runner.events import ROUND_GRACE_EXTENDED, emit
|
|
273
279
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Mask secret-bearing tokens in semi-trusted free text before it is persisted
|
|
2
|
+
to durable, default-readable event logs (events-*.jsonl).
|
|
3
|
+
|
|
4
|
+
Single source of truth for secret-shaping. Pure, dependency-free, idempotent.
|
|
5
|
+
BEST-EFFORT: applied only to free-text excerpts (transient error output, hook
|
|
6
|
+
exception messages/tracebacks), never as the sole control. The grace-kill child
|
|
7
|
+
fields avoid this entirely by storing basename+pid, not argv.
|
|
8
|
+
|
|
9
|
+
Patterns are length/charset-anchored so benign argv (sk-report.md, psql -h db,
|
|
10
|
+
'Basic auth disabled') passes through unchanged.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
_MASK = "<redacted>"
|
|
18
|
+
|
|
19
|
+
_LONG_FLAGS = [
|
|
20
|
+
"--token",
|
|
21
|
+
"--password",
|
|
22
|
+
"--passwd",
|
|
23
|
+
"--api-key",
|
|
24
|
+
"--apikey",
|
|
25
|
+
"--secret",
|
|
26
|
+
"--secret-access-key",
|
|
27
|
+
"--access-key",
|
|
28
|
+
"--auth",
|
|
29
|
+
"--authorization",
|
|
30
|
+
"--client-secret",
|
|
31
|
+
"--aws-session-token",
|
|
32
|
+
"--bearer",
|
|
33
|
+
"--auth-token",
|
|
34
|
+
"--private-key",
|
|
35
|
+
]
|
|
36
|
+
# (prefix, min-tail-len) — anchored so short filenames don't match.
|
|
37
|
+
_PREFIX_RES = [
|
|
38
|
+
r"sk-ant-[A-Za-z0-9\-_]{16,}",
|
|
39
|
+
r"sk-[A-Za-z0-9]{16,}",
|
|
40
|
+
r"ghp_[A-Za-z0-9]{20,}",
|
|
41
|
+
r"gho_[A-Za-z0-9]{20,}",
|
|
42
|
+
r"ghs_[A-Za-z0-9]{20,}",
|
|
43
|
+
r"ghu_[A-Za-z0-9]{20,}",
|
|
44
|
+
r"github_pat_[A-Za-z0-9_]{20,}",
|
|
45
|
+
r"xox[bpars]-[A-Za-z0-9-]{10,}",
|
|
46
|
+
r"xapp-[A-Za-z0-9-]{10,}",
|
|
47
|
+
r"AKIA[0-9A-Z]{16}",
|
|
48
|
+
r"ASIA[0-9A-Z]{16}",
|
|
49
|
+
r"AIza[0-9A-Za-z_\-]{35}",
|
|
50
|
+
r"glpat-[A-Za-z0-9_\-]{20}",
|
|
51
|
+
r"ya29\.[A-Za-z0-9._\-]{20,}",
|
|
52
|
+
r"(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}",
|
|
53
|
+
r"npm_[A-Za-z0-9]{20,}",
|
|
54
|
+
r"hf_[A-Za-z0-9]{20,}",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
_FLAG_RE = re.compile(r"(?i)(" + "|".join(re.escape(f) for f in _LONG_FLAGS) + r")(\s+|=)(\S+)")
|
|
58
|
+
# Short HTTP-basic flag `-u user:pass` (curl/wget). Case-SENSITIVE so `-U`
|
|
59
|
+
# (psql/pg username) is left alone; a colon is required so `sort -u file` and
|
|
60
|
+
# bare `-u username` (no password) are not masked; and the value must NOT be a
|
|
61
|
+
# URL (`://`) — a `-u <url>` is left for _URL_USERINFO_RE, which masks only the
|
|
62
|
+
# userinfo and preserves the host (e.g. redis://<redacted>@cache:6379/0).
|
|
63
|
+
_SHORT_USER_RE = re.compile(r"(?<![\w-])-u(\s+|=)(?!\S*://)(\S+:\S+)")
|
|
64
|
+
_HEADER_NAME_RE = re.compile(
|
|
65
|
+
r"(?im)\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie|"
|
|
66
|
+
r"X-Api-Key|X-Auth-Token|X-Amz-Security-Token)(\s*:\s*)([^\r\n]+)"
|
|
67
|
+
)
|
|
68
|
+
_SCHEME_RE = re.compile(
|
|
69
|
+
r"\b(Bearer|Basic|Token|ApiKey|Digest|Negotiate|NTLM)(\s+)([A-Za-z0-9+/=._\-]{12,})"
|
|
70
|
+
)
|
|
71
|
+
_URL_USERINFO_RE = re.compile(r"(?i)([a-z][a-z0-9+.\-]*://)([^/\s@]+)@")
|
|
72
|
+
_URL_QUERY_RE = re.compile(
|
|
73
|
+
r"(?i)([?&#](?:access_token|api_?key|token|auth|secret|sig|signature|"
|
|
74
|
+
r"password|client_secret|x-amz-security-token|x-amz-signature)=)([^&#\s]+)"
|
|
75
|
+
)
|
|
76
|
+
_ENV_RE = re.compile(
|
|
77
|
+
r"(?i)((?:(?<=\s)|^)[A-Za-z_][A-Za-z0-9_]*"
|
|
78
|
+
r"(?:PASSWORD|PASSWD|PWD|TOKEN|SECRET|API[_-]?KEY|ACCESS[_-]?KEY|CREDENTIAL|PRIVATE[_-]?KEY)"
|
|
79
|
+
r"[A-Za-z0-9_]*)=(\S+)"
|
|
80
|
+
)
|
|
81
|
+
_PREFIX_RE = re.compile(r"(?<![A-Za-z0-9])(?:" + "|".join(_PREFIX_RES) + r")")
|
|
82
|
+
_JWT_RE = re.compile(r"\beyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+")
|
|
83
|
+
_PEM_RE = re.compile(
|
|
84
|
+
r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.DOTALL
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def redact_secrets(text: str) -> str:
|
|
89
|
+
"""Return *text* with secret-bearing tokens replaced by ``<redacted>``."""
|
|
90
|
+
if not text:
|
|
91
|
+
return text
|
|
92
|
+
out = _PEM_RE.sub(_MASK, text)
|
|
93
|
+
out = _ENV_RE.sub(rf"\1={_MASK}", out)
|
|
94
|
+
out = _FLAG_RE.sub(rf"\1\2{_MASK}", out)
|
|
95
|
+
out = _SHORT_USER_RE.sub(rf"-u\1{_MASK}", out)
|
|
96
|
+
out = _HEADER_NAME_RE.sub(rf"\1\2{_MASK}", out)
|
|
97
|
+
out = _SCHEME_RE.sub(rf"\1\2{_MASK}", out)
|
|
98
|
+
out = _URL_USERINFO_RE.sub(rf"\1{_MASK}@", out)
|
|
99
|
+
out = _URL_QUERY_RE.sub(rf"\1{_MASK}", out)
|
|
100
|
+
out = _JWT_RE.sub(_MASK, out)
|
|
101
|
+
out = _PREFIX_RE.sub(_MASK, out)
|
|
102
|
+
return out
|
|
@@ -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.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.41'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 41)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -31,7 +31,7 @@ class RunResult:
|
|
|
31
31
|
timed_out: bool
|
|
32
32
|
pid: int
|
|
33
33
|
killed_for_grace: bool = False
|
|
34
|
-
grace_kill_children: list[
|
|
34
|
+
grace_kill_children: list[dict] = field(default_factory=list)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
|
|
@@ -63,35 +63,43 @@ def _live_children(
|
|
|
63
63
|
*,
|
|
64
64
|
ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
65
65
|
max_n: int = 5,
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
) -> tuple[list[dict], list[dict]]:
|
|
67
|
+
"""Live (non-zombie) descendants of ``proc``, split into ``(live, ignored)``.
|
|
68
|
+
|
|
69
|
+
Each entry is ``{"name": <executable basename>, "pid": <int>}``; an ignored
|
|
70
|
+
entry also carries ``"matched": <pattern str>``. We store only basename+pid,
|
|
71
|
+
NOT argv — process arguments are where secrets leak (PGPASSWORD=…, --api-key
|
|
72
|
+
…, redis://:pass@…) and these lists are persisted to events-*.jsonl.
|
|
73
|
+
Ignore-pattern MATCHING runs against the full cmdline (detection unchanged);
|
|
74
|
+
only what we STORE is minimized.
|
|
74
75
|
"""
|
|
75
76
|
try:
|
|
76
77
|
parent = psutil.Process(proc.pid)
|
|
77
78
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
78
79
|
return [], []
|
|
79
|
-
live: list[
|
|
80
|
-
ignored: list[
|
|
80
|
+
live: list[dict] = []
|
|
81
|
+
ignored: list[dict] = []
|
|
81
82
|
for child in parent.children(recursive=True):
|
|
82
83
|
try:
|
|
83
84
|
if child.status() == psutil.STATUS_ZOMBIE:
|
|
84
85
|
continue
|
|
85
|
-
|
|
86
|
+
argv = child.cmdline()
|
|
87
|
+
full = " ".join(argv) or child.name() # MATCHING only
|
|
88
|
+
name = (Path(argv[0]).name if argv else "") or child.name()
|
|
89
|
+
pid = child.pid
|
|
86
90
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
87
91
|
continue
|
|
88
|
-
|
|
89
|
-
if ignore_patterns
|
|
92
|
+
matched = None
|
|
93
|
+
if ignore_patterns:
|
|
94
|
+
for p in ignore_patterns:
|
|
95
|
+
if p.search(full):
|
|
96
|
+
matched = p.pattern
|
|
97
|
+
break
|
|
98
|
+
if matched is not None:
|
|
90
99
|
if len(ignored) < max_n:
|
|
91
|
-
ignored.append(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
live.append(short)
|
|
100
|
+
ignored.append({"name": name, "pid": pid, "matched": matched})
|
|
101
|
+
elif len(live) < max_n:
|
|
102
|
+
live.append({"name": name, "pid": pid})
|
|
95
103
|
if len(live) >= max_n and len(ignored) >= max_n:
|
|
96
104
|
break
|
|
97
105
|
return live, ignored
|
|
@@ -114,7 +122,7 @@ def run(
|
|
|
114
122
|
max_grace_after_result_s: int = 0,
|
|
115
123
|
progress_callback: Callable[[dict], None] | None = None,
|
|
116
124
|
progress_interval_s: int = 0,
|
|
117
|
-
on_grace_extended: Callable[[list[
|
|
125
|
+
on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
|
|
118
126
|
grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
119
127
|
) -> RunResult:
|
|
120
128
|
"""Spawn the agent subprocess and wait for exit or timeout.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Built-in post_round_hook for codewhale CLI: usage events + transient classifier.
|
|
2
|
+
|
|
3
|
+
Third built-in plugin (after claude, gemini). Parses codewhale's `exec
|
|
4
|
+
--output-format stream-json` NDJSON stdout tail; emits agent_usage_recorded
|
|
5
|
+
from the terminal metadata record. Transient-error classification is
|
|
6
|
+
best-effort and emits ONLY when an error maps to an existing bucket (like
|
|
7
|
+
gemini): codewhale's exec stdout surfaces a {"type":"error"} record, but the
|
|
8
|
+
only observed case so far is auth failure (oauth_fail territory, not a
|
|
9
|
+
transient bucket), so nothing maps yet -- usage-only today. 429/5xx mapping
|
|
10
|
+
is added when a real rate-limit sample is captured.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from collections import deque
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from agent_runner.api import (
|
|
22
|
+
emit_agent_usage_recorded,
|
|
23
|
+
emit_transient_error_detected,
|
|
24
|
+
)
|
|
25
|
+
from agent_runner.builtin_plugins._constants import (
|
|
26
|
+
_5XX_STATUSES,
|
|
27
|
+
_BACK_OFF_DEFAULTS,
|
|
28
|
+
_RAW_CAP,
|
|
29
|
+
_TAIL_LINES,
|
|
30
|
+
)
|
|
31
|
+
from agent_runner.hooks import HookContext, register_post_round_hook
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CodewhaleErrorDetector:
|
|
35
|
+
"""Parse codewhale round log tail; emit usage + transient_error_detected events."""
|
|
36
|
+
|
|
37
|
+
name = "codewhale_error_detector"
|
|
38
|
+
|
|
39
|
+
def after_round(self, ctx: HookContext, result: Any) -> None:
|
|
40
|
+
if ctx.agent_binary != "codewhale":
|
|
41
|
+
return
|
|
42
|
+
log_path = ctx.agent_log_path
|
|
43
|
+
if log_path is None or not log_path.exists():
|
|
44
|
+
return
|
|
45
|
+
parsed = _parse_codewhale_log(log_path)
|
|
46
|
+
if parsed.get("transient_error"):
|
|
47
|
+
emit_transient_error_detected(
|
|
48
|
+
ctx.log_dir, round_num=ctx.round_num, **parsed["transient_error"]
|
|
49
|
+
)
|
|
50
|
+
if parsed.get("usage"):
|
|
51
|
+
emit_agent_usage_recorded(
|
|
52
|
+
ctx.log_dir,
|
|
53
|
+
round_num=ctx.round_num,
|
|
54
|
+
phase=ctx.phase or "",
|
|
55
|
+
success=(result.exit_code == 0 and not result.timed_out),
|
|
56
|
+
**parsed["usage"],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_codewhale_log(log_path: Path) -> dict[str, Any]:
|
|
61
|
+
"""Scan last _TAIL_LINES of codewhale NDJSON; extract usage from the metadata
|
|
62
|
+
record; classify any {"type":"error"} that maps to a transient bucket.
|
|
63
|
+
|
|
64
|
+
Tolerates non-JSON lines (codewhale prefixes some stdout with terminal
|
|
65
|
+
escapes) via per-line try/except.
|
|
66
|
+
"""
|
|
67
|
+
with log_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
68
|
+
tail = deque(f, maxlen=_TAIL_LINES)
|
|
69
|
+
metadata: dict | None = None
|
|
70
|
+
error_event: dict | None = None
|
|
71
|
+
for line in tail:
|
|
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
|
|
81
|
+
etype = event.get("type")
|
|
82
|
+
if etype == "metadata":
|
|
83
|
+
metadata = event.get("meta") or {}
|
|
84
|
+
elif etype == "error":
|
|
85
|
+
error_event = event
|
|
86
|
+
|
|
87
|
+
out: dict[str, Any] = {}
|
|
88
|
+
|
|
89
|
+
if metadata:
|
|
90
|
+
out["usage"] = {
|
|
91
|
+
"agent": "codewhale",
|
|
92
|
+
"model": str(metadata.get("model", "unknown")),
|
|
93
|
+
"input_tokens": int(metadata.get("input_tokens", 0)),
|
|
94
|
+
"output_tokens": int(metadata.get("output_tokens", 0)),
|
|
95
|
+
"cached_tokens": 0, # codewhale exec stdout exposes no cache counts
|
|
96
|
+
"cost_usd": None, # codewhale exec stdout exposes no USD
|
|
97
|
+
"duration_ms": 0, # not in exec metadata
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if error_event is not None:
|
|
101
|
+
classification = _classify_codewhale_error(error_event)
|
|
102
|
+
if classification:
|
|
103
|
+
duration = _BACK_OFF_DEFAULTS[classification]
|
|
104
|
+
out["transient_error"] = {
|
|
105
|
+
"classification": classification,
|
|
106
|
+
"agent": "codewhale",
|
|
107
|
+
"reset_at_epoch": int(time.time() + duration),
|
|
108
|
+
"raw": str(error_event.get("error", "error"))[:_RAW_CAP],
|
|
109
|
+
}
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _classify_codewhale_error(error_event: dict[str, Any]) -> str | None:
|
|
114
|
+
"""Map a codewhale {"type":"error"} record to a transient bucket, or None.
|
|
115
|
+
|
|
116
|
+
None means 'not a transient error' (e.g. auth failure -> handled by the
|
|
117
|
+
monitor's oauth_fail log-scan, not the transient classifier). codewhale's
|
|
118
|
+
error record currently carries only a free-text 'error' string with no
|
|
119
|
+
status code; until a real rate-limit/5xx sample is captured we cannot map
|
|
120
|
+
to rate_limit_model / api_transient_5xx / api_timeout, so we return None.
|
|
121
|
+
A future revision keys on a numeric status field once observed.
|
|
122
|
+
"""
|
|
123
|
+
code = error_event.get("code") or error_event.get("status_code")
|
|
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
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
register_post_round_hook(CodewhaleErrorDetector())
|
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import importlib.resources
|
|
6
|
+
|
|
5
7
|
from agent_runner import api
|
|
6
8
|
from agent_runner.cli.common import emit, fail, work_dir_from_args
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
def _preset_names() -> list[str]:
|
|
12
|
+
"""Discover scaffold presets from the shipped ``agent_runner/presets/*.toml``.
|
|
13
|
+
|
|
14
|
+
Derived (not hardcoded) so adding a preset is a single new .toml file — the
|
|
15
|
+
``--preset`` choices and validation track the filesystem automatically.
|
|
16
|
+
"""
|
|
17
|
+
presets = importlib.resources.files("agent_runner.presets")
|
|
18
|
+
return sorted(p.name[:-5] for p in presets.iterdir() if p.name.endswith(".toml"))
|
|
19
|
+
|
|
20
|
+
|
|
9
21
|
def add_parser(sub, parent) -> None:
|
|
10
22
|
p = sub.add_parser("init", parents=[parent], help="Scaffold agent-runner project files")
|
|
11
23
|
p.add_argument(
|
|
12
24
|
"--preset",
|
|
13
|
-
choices=
|
|
25
|
+
choices=_preset_names(),
|
|
14
26
|
default="claude",
|
|
15
27
|
help="Which agent CLI preset to scaffold (default: claude)",
|
|
16
28
|
)
|
|
@@ -35,6 +35,7 @@ from pathlib import Path
|
|
|
35
35
|
from typing import Any, Protocol, runtime_checkable
|
|
36
36
|
|
|
37
37
|
from agent_runner import events
|
|
38
|
+
from agent_runner._redact import redact_secrets
|
|
38
39
|
from agent_runner._registry import ensure_unique
|
|
39
40
|
|
|
40
41
|
_HEAD_BYTES = 1024
|
|
@@ -201,7 +202,7 @@ def run_serve_startup_hooks(cfg: Any, log_dir: Path) -> bool:
|
|
|
201
202
|
hook(cfg)
|
|
202
203
|
except Exception as e: # noqa: BLE001 — hook is plugin contract; any failure aborts serve
|
|
203
204
|
exc_type = type(e).__name__
|
|
204
|
-
exc_msg = str(e)[:200]
|
|
205
|
+
exc_msg = redact_secrets(str(e))[:200]
|
|
205
206
|
print(
|
|
206
207
|
f"agent-runner: serve_startup_hook {hook.name} failed: {exc_type}: {exc_msg}",
|
|
207
208
|
file=sys.stderr,
|
|
@@ -232,6 +233,6 @@ def _summarize_error(exc: BaseException, tb: str) -> dict[str, str]:
|
|
|
232
233
|
trimmed = tb[:_HEAD_BYTES] + _TRUNC_MARKER + tb[-_TAIL_BYTES:]
|
|
233
234
|
return {
|
|
234
235
|
"error_type": type(exc).__name__,
|
|
235
|
-
"error_message": str(exc),
|
|
236
|
-
"traceback": trimmed,
|
|
236
|
+
"error_message": redact_secrets(str(exc)),
|
|
237
|
+
"traceback": redact_secrets(trimmed),
|
|
237
238
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# agent-runner.toml — generated by `agent-runner init --preset codewhale`.
|
|
2
|
+
#
|
|
3
|
+
# Prereqs:
|
|
4
|
+
# - codewhale installed (ships `codewhale` + `codewhale-tui`; both on PATH):
|
|
5
|
+
# npm i -g codewhale (or cargo/brew per CodeWhale docs)
|
|
6
|
+
# - DEEPSEEK_API_KEY set on the supervisor host (or a key saved via
|
|
7
|
+
# `codewhale auth set`; resolution order is config > keyring > env)
|
|
8
|
+
# - work_dir is a git repo
|
|
9
|
+
|
|
10
|
+
[agent]
|
|
11
|
+
command = ["codewhale", "exec", "--auto", "--output-format", "stream-json"]
|
|
12
|
+
prompt_arg_template = ["{prompt}"]
|
|
13
|
+
name = "codewhale"
|
|
14
|
+
|
|
15
|
+
[runtime]
|
|
16
|
+
work_dir = "."
|
|
17
|
+
log_dir = "~/.agent-runner/{project}/logs"
|
|
18
|
+
round_timeout_s = 1800
|
|
19
|
+
restart_delay_s = 3
|
|
20
|
+
|
|
21
|
+
[prompt]
|
|
22
|
+
file = "./prompts/main.md"
|
|
23
|
+
inject_context = true
|
|
24
|
+
|
|
25
|
+
[vcs]
|
|
26
|
+
dirty_action = "stash"
|
|
27
|
+
stash_idempotency_s = 5
|
|
28
|
+
|
|
29
|
+
[monitor]
|
|
30
|
+
auth_fail_hint = "Run `codewhale auth status` to inspect provider/credentials, or set DEEPSEEK_API_KEY on the supervisor host."
|
|
@@ -5,8 +5,8 @@ Writes three files into a git repo:
|
|
|
5
5
|
prompts/main.md — neutral 8-line placeholder
|
|
6
6
|
.gitignore — append "logs/" if missing
|
|
7
7
|
|
|
8
|
-
Available presets ship as package data in `agent_runner/presets/*.toml
|
|
9
|
-
|
|
8
|
+
Available presets ship as package data in `agent_runner/presets/*.toml`;
|
|
9
|
+
`agent-runner init --preset <name>` discovers them from that directory.
|
|
10
10
|
|
|
11
11
|
Optionally commits in one step (default true via the CLI).
|
|
12
12
|
"""
|
|
@@ -36,7 +36,7 @@ appends `logs/` to `.gitignore`. By default also creates a git commit.
|
|
|
36
36
|
|
|
37
37
|
Flags:
|
|
38
38
|
|
|
39
|
-
- `--preset {claude,aider,gemini}` — agent CLI preset to scaffold (default: `claude`)
|
|
39
|
+
- `--preset {claude,aider,gemini,codewhale}` — agent CLI preset to scaffold (default: `claude`)
|
|
40
40
|
- `--force` — overwrite an existing `agent-runner.toml`
|
|
41
41
|
- `--no-commit` — skip the initial git commit
|
|
42
42
|
|
|
@@ -101,6 +101,9 @@ back to package-only mode automatically.
|
|
|
101
101
|
`--no-restart` forces package-only even on a systemd --user host (upgrade the
|
|
102
102
|
package now, restart your service yourself).
|
|
103
103
|
|
|
104
|
+
Operator walkthrough (per-deployment decision table, rollback, failure modes,
|
|
105
|
+
postmortem trail): see `docs/runbook.md` § "Upgrading agent-runner".
|
|
106
|
+
|
|
104
107
|
## Observation
|
|
105
108
|
|
|
106
109
|
### `agent-runner peek [flags]`
|
|
@@ -103,10 +103,11 @@ working tree:
|
|
|
103
103
|
`[agent.env]` is a flat `dict[str, str]` of environment variables injected into
|
|
104
104
|
the agent subprocess **per round**. This is preset-supplied per CLI: e.g. the
|
|
105
105
|
claude preset sets `DISABLE_AUTOUPDATER=1` to prevent mid-loop self-updates;
|
|
106
|
-
the aider
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
the aider and codewhale presets omit `[agent.env]` entirely (both resolve their
|
|
107
|
+
API keys from the ambient environment or their own keyrings). Override these
|
|
108
|
+
values in your project's `agent-runner.toml` only when you need to deviate from
|
|
109
|
+
the preset default. The runtime merges `[agent.env]` on top of the supervisor's
|
|
110
|
+
own env; unset (empty string) does not unset an inherited variable.
|
|
110
111
|
|
|
111
112
|
## `[monitor].auth_fail_hint` (preset-supplied)
|
|
112
113
|
|
|
@@ -117,6 +118,8 @@ guidance without authoring it themselves:
|
|
|
117
118
|
- `--preset claude` → recommend `claude /login` / refresh `ANTHROPIC_API_KEY`.
|
|
118
119
|
- `--preset aider` → verify provider env var (`OPENAI_API_KEY` /
|
|
119
120
|
`ANTHROPIC_API_KEY` / `DEEPSEEK_API_KEY` / etc.); run `aider --models`.
|
|
121
|
+
- `--preset codewhale` → run `codewhale auth status` to inspect provider
|
|
122
|
+
credentials, or set `DEEPSEEK_API_KEY` on the supervisor host.
|
|
120
123
|
|
|
121
124
|
Override in your `agent-runner.toml` if you ship a custom CLI.
|
|
122
125
|
|
|
@@ -212,9 +215,10 @@ round_progress_interval_s = 0 # 0 = disabled; set >0 to emit round_progress hea
|
|
|
212
215
|
# supervisor_stale_threshold_s = 2700 # unset = round_timeout_s * 1.5; 0 = disable
|
|
213
216
|
|
|
214
217
|
[monitor.host_health]
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
# Thresholds for mem_pressure / disk_warning / disk_critical. Defaults are
|
|
219
|
+
# authoritative in the config-schema table above — set a field here only to
|
|
220
|
+
# override. (mem_avail_min_mb: mem_pressure when mem_available_mb below it;
|
|
221
|
+
# disk_warning_pct / disk_critical_pct: fire when disk_used_pct at/above.)
|
|
218
222
|
```
|
|
219
223
|
|
|
220
224
|
Comment out individual entries to disable; e.g. `# auto_stop_on = []` disables
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Migrating to 0.1.40
|
|
2
|
+
|
|
3
|
+
## TL;DR
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install --upgrade cli-agent-runner==0.1.40
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
A security release. No config or behavior change for the supervisor. One
|
|
10
|
+
consumer-visible field-shape change (below).
|
|
11
|
+
|
|
12
|
+
## Breaking: grace-kill child fields are now objects, not strings
|
|
13
|
+
|
|
14
|
+
`round_grace_extended` / `round_grace_kill` previously carried
|
|
15
|
+
`live_children` / `ignored_children` as lists of command-line **strings**.
|
|
16
|
+
They are now lists of **objects**:
|
|
17
|
+
|
|
18
|
+
- `live_children`: `[{"name": "<exe basename>", "pid": <int>}, ...]`
|
|
19
|
+
- `ignored_children`: `[{"name": ..., "pid": ..., "matched": "<pattern>"}, ...]`
|
|
20
|
+
|
|
21
|
+
If you parse these fields, update your consumer. The change exists because the
|
|
22
|
+
old strings were full process command lines, which could contain a secret
|
|
23
|
+
passed in a child's arguments (a DB password, an API key, a credential URL).
|
|
24
|
+
Storing only basename + pid removes that leak by construction. Ignore-pattern
|
|
25
|
+
matching is unchanged — it still runs against the full command line; only what
|
|
26
|
+
is **stored** is minimized.
|
|
27
|
+
|
|
28
|
+
## Best-effort redaction of free-text excerpts
|
|
29
|
+
|
|
30
|
+
`transient_error_detected.raw`, `hook_failed.error_message`/`traceback`, and
|
|
31
|
+
`serve_startup_hook_failed.exc_msg` are now passed through a redactor that masks
|
|
32
|
+
auth headers, tokens, credential URLs, `KEY=value` secrets, known key-prefixes,
|
|
33
|
+
JWTs, and PEM blocks. This is **best-effort defense-in-depth**, not a guarantee:
|
|
34
|
+
do not pass secrets as command-line arguments to supervised agents, and treat
|
|
35
|
+
`events-*.jsonl` as sensitive.
|
|
36
|
+
|
|
37
|
+
## One-time action (if applicable)
|
|
38
|
+
|
|
39
|
+
If pre-0.1.40 `events-*.jsonl` were shipped to a log aggregator, shared, or
|
|
40
|
+
fetched cross-host (`monitor --host`), review them for secret-bearing child
|
|
41
|
+
argv / error excerpts and rotate any exposed credential. Post-upgrade events
|
|
42
|
+
are protected.
|
|
@@ -37,7 +37,7 @@ Edit `prompts/main.md` to describe what the agent should do per round.
|
|
|
37
37
|
Edit `agent-runner.toml` if you need to change `round_timeout_s` or `[phases]`.
|
|
38
38
|
|
|
39
39
|
The default preset (`--preset claude`) invokes `claude`. Other built-in
|
|
40
|
-
presets: `--preset aider
|
|
40
|
+
presets: `--preset aider`, `--preset gemini`, and `--preset codewhale`. To use any other CLI,
|
|
41
41
|
edit `agent.command` to your CLI's invocation and `agent.prompt_arg_template`
|
|
42
42
|
to its prompt-argument syntax — for example:
|
|
43
43
|
|