cli-agent-runner 0.1.38__tar.gz → 0.1.40__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.38 → cli_agent_runner-0.1.40}/.gitignore +3 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CHANGELOG.md +25 -1
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/PKG-INFO +1 -1
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_docgen.py +0 -6
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_emit.py +15 -2
- cli_agent_runner-0.1.40/agent_runner/_redact.py +102 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/agent_runtime.py +45 -17
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/config.py +30 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/hooks.py +4 -3
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/claude.toml +1 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/runner.py +7 -2
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/commands.md +24 -4
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/configuration.md +9 -3
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/long-running-agents.md +1 -1
- cli_agent_runner-0.1.40/docs/migrations/0.1.39.md +74 -0
- cli_agent_runner-0.1.40/docs/migrations/0.1.40.md +42 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/plugins.md +4 -2
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/runbook.md +23 -4
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/thesis.md +4 -3
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_grace_kill_emission.py +64 -1
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_architecture.py +3 -16
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_catalogs.py +0 -1
- cli_agent_runner-0.1.40/tests/invariants/test_doc_claims_match_ssot.py +102 -0
- cli_agent_runner-0.1.40/tests/unit/test_agent_runtime_grace.py +268 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_observation.py +3 -2
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_claude_error_detector.py +17 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config.py +45 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_docgen.py +19 -13
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_hooks.py +9 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_presets.py +20 -0
- cli_agent_runner-0.1.40/tests/unit/test_redact.py +136 -0
- cli_agent_runner-0.1.38/tests/unit/test_agent_runtime_grace.py +0 -159
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.codecov.yml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/LICENSE +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/README.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/README.zh.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/SECURITY.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/detector_helpers.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/build.sh +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/example-agent-runner.toml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/launchd.plist.tmpl +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/run-loop.sh +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/systemd.service.tmpl +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/README.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/architecture.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/events.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/marketing/README.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/marketing/promo-cn.html +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.28.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.30.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.35.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/pyproject.toml +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/conftest.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_detector_helpers.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_vcs_state.py +0 -0
|
@@ -5,7 +5,31 @@ 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.40] - 2026-05-31
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- 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.
|
|
12
|
+
- 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).
|
|
13
|
+
- Pre-0.1.40 `events-*.jsonl` may contain unredacted argv/excerpts — see `docs/migrations/0.1.40.md`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Docs: `configuration.md` `[monitor.host_health]` example points to the generated schema table instead of restating default values.
|
|
17
|
+
|
|
18
|
+
## [0.1.39] - 2026-05-29
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `[runtime] grace_kill_ignore_patterns: list[str]` — regex patterns; matching child cmdlines are excluded from the grace-kill liveness check.
|
|
25
|
+
- `round_grace_extended` event payload gains `ignored_children` — cmdlines filtered by `grace_kill_ignore_patterns`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Docs: `commands.md` documents `monitor --mode/--port` and `init --preset`; the Chinese verb list and `[monitor]` default values now point to the generated tables instead of restating them; runbook upgrade examples use a version placeholder.
|
|
29
|
+
|
|
30
|
+
### Internal
|
|
31
|
+
- New invariant `test_doc_claims_match_ssot` gates documented counts (detectors / defenses / verbs) and config value-sets (`dirty_action` / `context_injection_mode` / transient classification) against their code SSOT — count/enum doc drift now fails CI at the introducing commit.
|
|
32
|
+
- Removed the unused `alert-kinds` docgen renderer; de-duplicated redundant defense-count and alert-kind guards to one canonical tripwire each.
|
|
9
33
|
|
|
10
34
|
## [0.1.38] - 2026-05-24
|
|
11
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.40
|
|
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
|
|
@@ -110,11 +110,6 @@ def render_defenses_table() -> str:
|
|
|
110
110
|
return "\n".join(lines)
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def render_alert_kinds_list() -> str:
|
|
114
|
-
"""Flat bullet list of all known alert kinds, alphabetised."""
|
|
115
|
-
return "\n".join(f"- `{k}`" for k in sorted(KNOWN_ALERT_KINDS))
|
|
116
|
-
|
|
117
|
-
|
|
118
113
|
def render_detector_list() -> str:
|
|
119
114
|
"""Bullet list of detectors; auto-stop kinds flagged inline."""
|
|
120
115
|
lines: list[str] = []
|
|
@@ -155,7 +150,6 @@ def render_verb_table() -> str:
|
|
|
155
150
|
|
|
156
151
|
RENDERERS: dict[str, Callable[[], str]] = {
|
|
157
152
|
"defenses-table": render_defenses_table,
|
|
158
|
-
"alert-kinds": render_alert_kinds_list,
|
|
159
153
|
"detector-list": render_detector_list,
|
|
160
154
|
"event-kinds": render_event_kinds_list,
|
|
161
155
|
"config-schema": render_config_schema_table,
|
|
@@ -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,11 +262,18 @@ def emit_round_grace_extended(
|
|
|
257
262
|
*,
|
|
258
263
|
round_num: int,
|
|
259
264
|
grace_s: int,
|
|
260
|
-
live_children: list[
|
|
265
|
+
live_children: list[dict],
|
|
266
|
+
ignored_children: list[dict] | None = None,
|
|
261
267
|
) -> None:
|
|
262
268
|
"""Emit when the grace-after-result timer expired but the agent still had
|
|
263
269
|
live worker processes (e.g. a backgrounded build), so the round was NOT
|
|
264
270
|
killed; it continues until it finishes or hits round_timeout_s.
|
|
271
|
+
|
|
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).
|
|
265
277
|
"""
|
|
266
278
|
from agent_runner.events import ROUND_GRACE_EXTENDED, emit
|
|
267
279
|
|
|
@@ -271,6 +283,7 @@ def emit_round_grace_extended(
|
|
|
271
283
|
round_num=round_num,
|
|
272
284
|
grace_s=grace_s,
|
|
273
285
|
live_children=live_children,
|
|
286
|
+
ignored_children=ignored_children or [],
|
|
274
287
|
)
|
|
275
288
|
|
|
276
289
|
|
|
@@ -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.40'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 40)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -11,6 +11,7 @@ Defenses encoded here:
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
|
+
import re
|
|
14
15
|
import signal
|
|
15
16
|
import subprocess # noqa: TID251 — sanctioned subprocess caller
|
|
16
17
|
import time
|
|
@@ -30,7 +31,7 @@ class RunResult:
|
|
|
30
31
|
timed_out: bool
|
|
31
32
|
pid: int
|
|
32
33
|
killed_for_grace: bool = False
|
|
33
|
-
grace_kill_children: list[
|
|
34
|
+
grace_kill_children: list[dict] = field(default_factory=list)
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
|
|
@@ -57,29 +58,51 @@ def _kill_pgroup(proc: subprocess.Popen) -> None:
|
|
|
57
58
|
pass
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def _live_children(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
def _live_children(
|
|
62
|
+
proc: subprocess.Popen,
|
|
63
|
+
*,
|
|
64
|
+
ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
65
|
+
max_n: int = 5,
|
|
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.
|
|
66
75
|
"""
|
|
67
76
|
try:
|
|
68
77
|
parent = psutil.Process(proc.pid)
|
|
69
78
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
70
|
-
return []
|
|
71
|
-
|
|
79
|
+
return [], []
|
|
80
|
+
live: list[dict] = []
|
|
81
|
+
ignored: list[dict] = []
|
|
72
82
|
for child in parent.children(recursive=True):
|
|
73
83
|
try:
|
|
74
84
|
if child.status() == psutil.STATUS_ZOMBIE:
|
|
75
85
|
continue
|
|
76
|
-
|
|
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
|
|
77
90
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
78
91
|
continue
|
|
79
|
-
|
|
80
|
-
if
|
|
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:
|
|
99
|
+
if len(ignored) < max_n:
|
|
100
|
+
ignored.append({"name": name, "pid": pid, "matched": matched})
|
|
101
|
+
elif len(live) < max_n:
|
|
102
|
+
live.append({"name": name, "pid": pid})
|
|
103
|
+
if len(live) >= max_n and len(ignored) >= max_n:
|
|
81
104
|
break
|
|
82
|
-
return
|
|
105
|
+
return live, ignored
|
|
83
106
|
|
|
84
107
|
|
|
85
108
|
# Exact compact bytes — matches claude CLI's no-whitespace JSONL output.
|
|
@@ -99,7 +122,8 @@ def run(
|
|
|
99
122
|
max_grace_after_result_s: int = 0,
|
|
100
123
|
progress_callback: Callable[[dict], None] | None = None,
|
|
101
124
|
progress_interval_s: int = 0,
|
|
102
|
-
on_grace_extended: Callable[[list[
|
|
125
|
+
on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
|
|
126
|
+
grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
103
127
|
) -> RunResult:
|
|
104
128
|
"""Spawn the agent subprocess and wait for exit or timeout.
|
|
105
129
|
|
|
@@ -116,6 +140,10 @@ def run(
|
|
|
116
140
|
progress_interval_s seconds with a dict of log stats (log_size_kb,
|
|
117
141
|
last_write_age_s, wall_age_s). Keeps agent_runtime event-free; callers
|
|
118
142
|
build the callback to emit events.
|
|
143
|
+
|
|
144
|
+
grace_kill_ignore_patterns: pre-compiled regex patterns; child cmdlines
|
|
145
|
+
matching any pattern (re.search) are excluded from the liveness count
|
|
146
|
+
(persistent helpers that aren't real workers). None = no filtering.
|
|
119
147
|
"""
|
|
120
148
|
argv = _build_argv(command, prompt_arg_template, prompt)
|
|
121
149
|
env = {**os.environ, **env_extra}
|
|
@@ -157,13 +185,13 @@ def run(
|
|
|
157
185
|
except OSError:
|
|
158
186
|
pass # log not flushed yet; check next tick
|
|
159
187
|
if result_seen_at is not None and now - result_seen_at > max_grace_after_result_s:
|
|
160
|
-
|
|
161
|
-
if
|
|
188
|
+
live, ignored = _live_children(proc, ignore_patterns=grace_kill_ignore_patterns)
|
|
189
|
+
if live:
|
|
162
190
|
# Busy: a backgrounded worker is still running. Don't
|
|
163
191
|
# reap — defer to the wall-clock ceiling. Signal once.
|
|
164
192
|
if not grace_extended_emitted:
|
|
165
193
|
if on_grace_extended is not None:
|
|
166
|
-
on_grace_extended(
|
|
194
|
+
on_grace_extended(live, ignored)
|
|
167
195
|
grace_extended_emitted = True
|
|
168
196
|
else:
|
|
169
197
|
_kill_pgroup(proc)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import tomllib
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from pathlib import Path
|
|
@@ -40,6 +41,12 @@ class RuntimeConfig:
|
|
|
40
41
|
fresh_eyes_every_n: int | None = None # None = disabled
|
|
41
42
|
dry_run: bool = False
|
|
42
43
|
max_grace_after_result_s: int = 0 # 0 = disabled
|
|
44
|
+
grace_kill_ignore_patterns: list[str] = field(default_factory=list)
|
|
45
|
+
"""Regex patterns (re.search) tested against each child process's joined
|
|
46
|
+
cmdline. Matching children are excluded from the grace-kill liveness
|
|
47
|
+
check — for persistent helper subprocesses (e.g. claude's shell-snapshot
|
|
48
|
+
bash) that would otherwise defeat max_grace_after_result_s. Empty list
|
|
49
|
+
= no filtering (0.1.38 behavior preserved)."""
|
|
43
50
|
|
|
44
51
|
|
|
45
52
|
@dataclass(frozen=True)
|
|
@@ -221,6 +228,25 @@ def _validate_remote_failure_tolerance(value: Any) -> int:
|
|
|
221
228
|
return v
|
|
222
229
|
|
|
223
230
|
|
|
231
|
+
def _validate_regex_list(value: Any, *, field: str) -> list[str]:
|
|
232
|
+
"""Validate a list of regex pattern strings (each must compile). Returns the
|
|
233
|
+
raw strings unchanged; callers compile when they need ``re.Pattern`` objects."""
|
|
234
|
+
if not isinstance(value, list):
|
|
235
|
+
raise ValueError(f"{field}: expected a list of regex strings, got {type(value).__name__}")
|
|
236
|
+
out: list[str] = []
|
|
237
|
+
for p in value:
|
|
238
|
+
if not isinstance(p, str):
|
|
239
|
+
raise ValueError(
|
|
240
|
+
f"{field}: each pattern must be a string, got {type(p).__name__}: {p!r}"
|
|
241
|
+
)
|
|
242
|
+
try:
|
|
243
|
+
re.compile(p)
|
|
244
|
+
except re.error as e:
|
|
245
|
+
raise ValueError(f"{field}: invalid regex {p!r}: {e}") from e
|
|
246
|
+
out.append(p)
|
|
247
|
+
return out
|
|
248
|
+
|
|
249
|
+
|
|
224
250
|
_PHASE_OVERRIDE_ALLOWED_FIELDS = frozenset(
|
|
225
251
|
{
|
|
226
252
|
"round_timeout_s",
|
|
@@ -392,6 +418,10 @@ def load_config(toml_path: Path) -> Config:
|
|
|
392
418
|
runtime_d.get("max_grace_after_result_s", 0),
|
|
393
419
|
field="runtime.max_grace_after_result_s",
|
|
394
420
|
),
|
|
421
|
+
grace_kill_ignore_patterns=_validate_regex_list(
|
|
422
|
+
runtime_d.get("grace_kill_ignore_patterns", []),
|
|
423
|
+
field="runtime.grace_kill_ignore_patterns",
|
|
424
|
+
),
|
|
395
425
|
)
|
|
396
426
|
prompt_d = raw.get("prompt", {})
|
|
397
427
|
mode = prompt_d.get("context_injection_mode", "prepend")
|
|
@@ -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
|
}
|
|
@@ -10,6 +10,7 @@ import hashlib
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
12
|
import random
|
|
13
|
+
import re
|
|
13
14
|
import sys
|
|
14
15
|
import time
|
|
15
16
|
import traceback as tb_mod
|
|
@@ -466,12 +467,15 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
466
467
|
**stats,
|
|
467
468
|
)
|
|
468
469
|
|
|
469
|
-
|
|
470
|
+
grace_kill_ignore_patterns = [re.compile(p) for p in cfg.runtime.grace_kill_ignore_patterns]
|
|
471
|
+
|
|
472
|
+
def _grace_extended_emit(live: list[str], ignored: list[str]) -> None:
|
|
470
473
|
api.emit_round_grace_extended(
|
|
471
474
|
log_dir,
|
|
472
475
|
round_num=round_num,
|
|
473
476
|
grace_s=cfg.runtime.max_grace_after_result_s,
|
|
474
|
-
live_children=
|
|
477
|
+
live_children=live,
|
|
478
|
+
ignored_children=ignored,
|
|
475
479
|
)
|
|
476
480
|
|
|
477
481
|
result = agent_runtime.run(
|
|
@@ -485,6 +489,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
485
489
|
progress_callback=_progress_emit,
|
|
486
490
|
progress_interval_s=cfg.monitor.round_progress_interval_s,
|
|
487
491
|
on_grace_extended=_grace_extended_emit,
|
|
492
|
+
grace_kill_ignore_patterns=grace_kill_ignore_patterns,
|
|
488
493
|
)
|
|
489
494
|
events.emit(
|
|
490
495
|
log_dir,
|
|
@@ -34,8 +34,15 @@ are shared between `peek`, `watch`, and `monitor`.
|
|
|
34
34
|
Scaffold a new project: writes `agent-runner.toml`, `prompts/main.md`, and
|
|
35
35
|
appends `logs/` to `.gitignore`. By default also creates a git commit.
|
|
36
36
|
|
|
37
|
+
Flags:
|
|
38
|
+
|
|
39
|
+
- `--preset {claude,aider,gemini}` — agent CLI preset to scaffold (default: `claude`)
|
|
40
|
+
- `--force` — overwrite an existing `agent-runner.toml`
|
|
41
|
+
- `--no-commit` — skip the initial git commit
|
|
42
|
+
|
|
37
43
|
```bash
|
|
38
|
-
agent-runner init # default: commit
|
|
44
|
+
agent-runner init # default: claude preset, commit
|
|
45
|
+
agent-runner init --preset aider # aider preset
|
|
39
46
|
agent-runner init --no-commit # skip the commit
|
|
40
47
|
agent-runner init --force # overwrite an existing toml
|
|
41
48
|
```
|
|
@@ -94,6 +101,9 @@ back to package-only mode automatically.
|
|
|
94
101
|
`--no-restart` forces package-only even on a systemd --user host (upgrade the
|
|
95
102
|
package now, restart your service yourself).
|
|
96
103
|
|
|
104
|
+
Operator walkthrough (per-deployment decision table, rollback, failure modes,
|
|
105
|
+
postmortem trail): see `docs/runbook.md` § "Upgrading agent-runner".
|
|
106
|
+
|
|
97
107
|
## Observation
|
|
98
108
|
|
|
99
109
|
### `agent-runner peek [flags]`
|
|
@@ -133,7 +143,7 @@ agent-runner events --kind transient_error_backoff_capped --tail
|
|
|
133
143
|
|
|
134
144
|
`peek` in a clear-and-refresh loop. Default 2s interval. Stop with Ctrl-C.
|
|
135
145
|
|
|
136
|
-
### `agent-runner monitor [--host SSH-ALIAS] [--interval N] [--json]`
|
|
146
|
+
### `agent-runner monitor [--host SSH-ALIAS] [--interval N] [--mode MODE] [--port PORT] [--json]`
|
|
137
147
|
|
|
138
148
|
Anomaly-detection daemon. Runs the 12 detectors against the live state on every
|
|
139
149
|
poll. Without `--host`, watches local logs at default 30s interval. With
|
|
@@ -143,15 +153,25 @@ When OAuth-fail or disk-critical detectors fire, monitor automatically issues a
|
|
|
143
153
|
graceful stop (locally via `api.stop`; remotely via `ssh <host> 'agent-runner stop'`).
|
|
144
154
|
Override with `[monitor]` config block (see configuration.md).
|
|
145
155
|
|
|
156
|
+
Flags:
|
|
157
|
+
|
|
158
|
+
- `--mode {anomaly,narrate,events,http}` — output mode (default: `anomaly`). `narrate`
|
|
159
|
+
streams a human-readable narrative; `events` streams raw event JSON; `http` serves
|
|
160
|
+
a local progress page.
|
|
161
|
+
- `--port PORT` — HTTP port for `--mode http` (default: `8765`, local-only).
|
|
162
|
+
- `--host SSH-ALIAS` — watch a remote agent-runner via ssh (anomaly mode only).
|
|
163
|
+
|
|
146
164
|
```bash
|
|
147
|
-
agent-runner monitor # local
|
|
165
|
+
agent-runner monitor # local anomaly mode
|
|
148
166
|
agent-runner monitor --host pi # remote
|
|
167
|
+
agent-runner monitor --mode narrate # streaming narrative
|
|
168
|
+
agent-runner monitor --mode http --port 9000 # HTTP progress page on port 9000
|
|
149
169
|
agent-runner monitor --json | jq -c # pipe alerts to a downstream consumer
|
|
150
170
|
```
|
|
151
171
|
|
|
152
172
|
## 中文摘要
|
|
153
173
|
|
|
154
|
-
16
|
|
174
|
+
16 个动词,完整列表见上方动词表(自动生成)。
|
|
155
175
|
|
|
156
176
|
观察类(peek/watch/monitor)三视角对称,全部共用 `--round / --log / --events / --select / --json` 下钻参数。
|
|
157
177
|
|
|
@@ -47,6 +47,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
47
47
|
| `fresh_eyes_every_n` | `int | None` | None |
|
|
48
48
|
| `dry_run` | `bool` | False |
|
|
49
49
|
| `max_grace_after_result_s` | `int` | 0 |
|
|
50
|
+
| `grace_kill_ignore_patterns` | `list[str]` | [] |
|
|
50
51
|
|
|
51
52
|
### `[prompt]`
|
|
52
53
|
|
|
@@ -200,6 +201,10 @@ Unconfigured phases (and configs without `[phases]`) keep using the global
|
|
|
200
201
|
|
|
201
202
|
## `[monitor]` (optional, defaults shown)
|
|
202
203
|
|
|
204
|
+
> Authoritative field-level defaults are in the generated schema table above
|
|
205
|
+
> (`[monitor]` section). The snippet below shows only the fields most commonly
|
|
206
|
+
> customised, with operational notes.
|
|
207
|
+
|
|
203
208
|
```toml
|
|
204
209
|
[monitor]
|
|
205
210
|
auto_stop_on = ["oauth_fail", "disk_critical"]
|
|
@@ -207,9 +212,10 @@ round_progress_interval_s = 0 # 0 = disabled; set >0 to emit round_progress hea
|
|
|
207
212
|
# supervisor_stale_threshold_s = 2700 # unset = round_timeout_s * 1.5; 0 = disable
|
|
208
213
|
|
|
209
214
|
[monitor.host_health]
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
# Thresholds for mem_pressure / disk_warning / disk_critical. Defaults are
|
|
216
|
+
# authoritative in the config-schema table above — set a field here only to
|
|
217
|
+
# override. (mem_avail_min_mb: mem_pressure when mem_available_mb below it;
|
|
218
|
+
# disk_warning_pct / disk_critical_pct: fire when disk_used_pct at/above.)
|
|
213
219
|
```
|
|
214
220
|
|
|
215
221
|
Comment out individual entries to disable; e.g. `# auto_stop_on = []` disables
|
|
@@ -140,7 +140,7 @@ token breakdown + cost (where the underlying CLI exposes it).
|
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
Use as input to a cost-tracking detector or external billing reconciler.
|
|
143
|
-
See `docs/migrations/0.1.28.md` for the current
|
|
143
|
+
See `docs/migrations/0.1.28.md` for the current payload schema
|
|
144
144
|
(includes `cache_creation_tokens`, `tool_call_count`, `phase`, `success`)
|
|
145
145
|
plus a consumer dispatcher sketch. Aggregation (rollups, budget warnings)
|
|
146
146
|
is the consumer's responsibility — agent-runner emits raw per-round
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Migrating to 0.1.39
|
|
2
|
+
|
|
3
|
+
## TL;DR
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install --upgrade cli-agent-runner==0.1.39
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Claude users running 0.1.38**: add one line to `[runtime]` to unblock
|
|
10
|
+
grace-kill against claude's persistent shell-snapshot helper (see below).
|
|
11
|
+
New `agent-runner init --preset=claude` scaffolds get this automatically.
|
|
12
|
+
|
|
13
|
+
**Everyone else**: no action.
|
|
14
|
+
|
|
15
|
+
## Persistent-helper exclusion (the live fix)
|
|
16
|
+
|
|
17
|
+
0.1.38's grace-kill liveness check was correctly conservative — it refused to
|
|
18
|
+
reap a round with live worker children — but claude's `-p` mode keeps a
|
|
19
|
+
persistent Bash-tool shell-snapshot subprocess alive for the whole session.
|
|
20
|
+
That subprocess is not doing work; it's idle infrastructure. 0.1.38 saw it as
|
|
21
|
+
a live worker and deferred every post-result hang to `round_timeout_s` instead
|
|
22
|
+
of reaping at `max_grace_after_result_s`. This is the "persistent-helper
|
|
23
|
+
caveat" 0.1.38's migration doc flagged.
|
|
24
|
+
|
|
25
|
+
0.1.39 adds `[runtime] grace_kill_ignore_patterns` — a list of regex patterns;
|
|
26
|
+
child cmdlines matching any pattern (via `re.search`) are excluded from the
|
|
27
|
+
liveness count. `presets/claude.toml` ships a default pattern matching
|
|
28
|
+
claude's shell-snapshot.
|
|
29
|
+
|
|
30
|
+
### Existing claude operators — one line
|
|
31
|
+
|
|
32
|
+
Add to your `[runtime]` block:
|
|
33
|
+
|
|
34
|
+
```toml
|
|
35
|
+
[runtime]
|
|
36
|
+
grace_kill_ignore_patterns = ['\.claude/shell-snapshots/snapshot-bash-']
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or run `agent-runner init --preset=claude` in a scratch directory and diff
|
|
40
|
+
the generated `agent-runner.toml` against yours.
|
|
41
|
+
|
|
42
|
+
After the change, post-result hangs are reaped at `max_grace_after_result_s`.
|
|
43
|
+
Without it, they continue to defer to `round_timeout_s` (the 0.1.38 behavior).
|
|
44
|
+
|
|
45
|
+
### Verifying the pattern is firing
|
|
46
|
+
|
|
47
|
+
The `round_grace_extended` event payload gains `ignored_children` listing
|
|
48
|
+
cmdlines that matched a pattern. Use it to:
|
|
49
|
+
|
|
50
|
+
- confirm the shell-snapshot is being filtered (`ignored_children` non-empty)
|
|
51
|
+
- catch the day claude renames its helper (`live_children` shows a new
|
|
52
|
+
unfiltered persistent process)
|
|
53
|
+
|
|
54
|
+
### Other presets
|
|
55
|
+
|
|
56
|
+
`aider.toml` and `gemini.toml` ship no default patterns. Add operator-specific
|
|
57
|
+
patterns to your own `agent-runner.toml` if needed.
|
|
58
|
+
|
|
59
|
+
## SSOT consistency hardening (also in 0.1.39)
|
|
60
|
+
|
|
61
|
+
A new invariant `test_doc_claims_match_ssot` gates documented counts
|
|
62
|
+
(detectors / defenses / verbs) and config value-sets against code SSOT.
|
|
63
|
+
`commands.md` documents `monitor --mode/--port` and `init --preset`.
|
|
64
|
+
Redundant count guards collapsed to one canonical tripwire each. The unused
|
|
65
|
+
`alert-kinds` docgen renderer was removed. No action required.
|
|
66
|
+
|
|
67
|
+
## What did NOT change
|
|
68
|
+
|
|
69
|
+
- The 0.1.38 grace-kill liveness semantics (still process-group-based;
|
|
70
|
+
patterns are an exclusion filter on top).
|
|
71
|
+
- `round_grace_kill` (still fires only when the post-filter live set is empty).
|
|
72
|
+
- `round_timeout_s` (still the hard ceiling).
|
|
73
|
+
- `max_grace_after_result_s` (knob unchanged).
|
|
74
|
+
- For non-claude deployments: zero behavior change.
|