cli-agent-runner 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/workflows/release.yml +7 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.vulture-whitelist.py +4 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/CHANGELOG.md +28 -1
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/PKG-INFO +21 -10
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/README.md +19 -8
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_emit.py +37 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/api.py +52 -74
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/events_cmd.py +126 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/monitor_cmd.py +61 -14
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/serve_cmd.py +21 -1
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/config.py +6 -5
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/defenses.py +11 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/monitor.py +40 -115
- cli_agent_runner-0.2.6/agent_runner/remote_relay.py +270 -0
- cli_agent_runner-0.2.6/agent_runner/round_log.py +160 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/runner.py +11 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/architecture.md +36 -18
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/commands.md +53 -9
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/configuration.md +64 -1
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/events.md +3 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.28.md +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.30.md +3 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.35.md +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.2.0.md +2 -3
- cli_agent_runner-0.2.6/docs/migrations/0.2.5.md +99 -0
- cli_agent_runner-0.2.6/docs/migrations/0.2.6.md +127 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/plugins.md +28 -15
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/recipes/aider.md +6 -5
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/runbook.md +133 -27
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/thesis.md +4 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/pyproject.toml +3 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_monitor_seeded.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_architecture.py +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_module_boundaries.py +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_observation.py +8 -187
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_service.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_auto_stop_gating.py +1 -5
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli_service_peek_monitor.py +79 -56
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config.py +52 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_defenses.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_events.py +28 -0
- cli_agent_runner-0.2.6/tests/unit/test_events_cmd.py +343 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_events_cmd_tail.py +39 -2
- cli_agent_runner-0.2.6/tests/unit/test_monitor_remote.py +320 -0
- cli_agent_runner-0.2.6/tests/unit/test_round_log_helpers.py +269 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_runner.py +67 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_round_log.py +99 -24
- cli_agent_runner-0.2.4/agent_runner/round_log.py +0 -95
- cli_agent_runner-0.2.4/tests/unit/test_events_cmd.py +0 -179
- cli_agent_runner-0.2.4/tests/unit/test_monitor_remote.py +0 -176
- cli_agent_runner-0.2.4/tests/unit/test_round_log_helpers.py +0 -143
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/.gitignore +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/LICENSE +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/README.zh.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/build.sh +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/README.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.1.42.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.2.1.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.2.2.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.2.3.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/migrations/0.2.4.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.6}/tests/unit/test_vcs_state.py +0 -0
|
@@ -9,9 +9,13 @@ permissions:
|
|
|
9
9
|
|
|
10
10
|
# Trusted Publishing: the `environment` name below MUST match the
|
|
11
11
|
# `Environment name` field in the PyPI pending publisher config.
|
|
12
|
-
# A mismatch fails publish with an opaque OIDC error.
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# A mismatch fails publish with an opaque OIDC error. Keep the environment
|
|
13
|
+
# declared even when it carries no protection rules — it is the OIDC claim,
|
|
14
|
+
# not just an approval gate; deleting it breaks publishing outright. Whether
|
|
15
|
+
# a tag publishes straight through or waits for a reviewer is configured in
|
|
16
|
+
# the repo's environment settings, not here.
|
|
17
|
+
# Note: TestPyPI stage intentionally omitted — the tag is the release signal;
|
|
18
|
+
# pre-tag verification is `./build.sh check` plus the CI matrix on main.
|
|
15
19
|
|
|
16
20
|
concurrency:
|
|
17
21
|
group: release-${{ github.ref }}
|
|
@@ -228,9 +228,10 @@ path
|
|
|
228
228
|
# agent_runner.monitor.LocalSource
|
|
229
229
|
log_dir
|
|
230
230
|
|
|
231
|
-
# agent_runner.
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
# agent_runner.round_log.PruneOutcome
|
|
232
|
+
deleted
|
|
233
|
+
deferred
|
|
234
|
+
existing
|
|
234
235
|
|
|
235
236
|
# agent_runner.startup_check.CheckResult
|
|
236
237
|
name
|
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.6] - 2026-07-28
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Round-log pruning is now opt-in.** `runtime.round_log_retention` defaults to `0` (never prune) and `0` is now a legal value — previously both `0` and `-1` were rejected, so there was no off switch. Retaining too much is defended (`disk_warning` at 90%, `disk_critical` at 95% auto-stops the service); deleting too much was not defended at all. Both round-log families are affected, including the serve-level `round-<N>.log` family that had been capped at 100 for many releases. Set `round_log_retention = 100` for the previous behavior.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- A prune that would delete more files than it keeps is now refused wholesale — nothing is deleted — so opting in on an existing backlog, or lowering the value later, can no longer wipe history in one pass. 0.2.4 and 0.2.5 did exactly that on their first post-upgrade round (one deployment: 12,193 of 12,293 transcripts, ~6.4 GB).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `round_logs_prune_deferred` event — emitted on every prune attempt the bulk guard defers, carrying `directory`, `existing`, `keep`, `would_delete` and a hint naming `runtime.round_log_retention`. Resolve by raising retention above the backlog or deleting the files yourself; the guard never blocks a round. Retention `0` emits nothing.
|
|
18
|
+
|
|
19
|
+
See `docs/migrations/0.2.6.md`.
|
|
20
|
+
|
|
21
|
+
## [0.2.5] - 2026-07-27
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `events --since <ISO ts>` — replays every matching event with `ts >= since`, across month files, one-shot or as the backlog phase of `--tail`. At-least-once: a client whose stream dropped reconnects with the last ts it saw and loses nothing (that one event may repeat).
|
|
25
|
+
- `monitor --host <alias> --mode events` — a managed ssh event relay, replacing a hand-rolled `ssh … --tail; sleep` loop. Passes the remote's JSONL through unmodified, reconnects with `--since <last ts>` so a dropped link replays its gap, emits `monitor_remote_blip` per drop and `monitor_remote_giveup` + exit 1 once the outage passes `[monitor] remote_failure_tolerance_s`, and kills the ssh process GROUP on exit so no orphan tree survives. New flags `--kind` and `--remote-config`.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
- The polling remote monitor (`RemoteSource`, `run_remote_command`, `MonitorRemoteError`, auto-stop over ssh) — it read remote paths on the local filesystem and was already failing loud since 0.2.4. Detection is on-host by design; remote observation is the relay above. `monitor.on_alert` lost its `host` parameter and `monitor_auto_stop_triggered` its `host` field; `monitor_auto_stop_failed` now reports a failed *local* stop (previously a failed ssh stop) instead of crashing the monitor loop.
|
|
29
|
+
|
|
30
|
+
See `docs/migrations/0.2.5.md`.
|
|
31
|
+
|
|
8
32
|
## [0.2.4] - 2026-07-27
|
|
9
33
|
|
|
10
34
|
### Added
|
|
@@ -648,6 +672,7 @@ No breaking changes. Plugin authors:
|
|
|
648
672
|
between rounds, opt them out:
|
|
649
673
|
```python
|
|
650
674
|
from agent_runner.vcs_state import register_plugin_owned_paths
|
|
675
|
+
|
|
651
676
|
register_plugin_owned_paths(["your-output-dir/", "logs/your-plugin/**/*"])
|
|
652
677
|
```
|
|
653
678
|
- If you followed the old `_register()` pattern from docs and noticed
|
|
@@ -938,7 +963,9 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
938
963
|
- Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
|
|
939
964
|
gated by a manual approval on the `pypi` GitHub environment.
|
|
940
965
|
|
|
941
|
-
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.
|
|
966
|
+
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.6...HEAD
|
|
967
|
+
[0.2.6]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.5...v0.2.6
|
|
968
|
+
[0.2.5]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.4...v0.2.5
|
|
942
969
|
[0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
|
|
943
970
|
[0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
|
|
944
971
|
[0.2.2]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.1...v0.2.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Restart-on-exit supervisor for autonomous CLI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
|
|
6
6
|
Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
|
|
@@ -28,7 +28,7 @@ Requires-Dist: psutil>=5.9
|
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
30
30
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
|
-
Requires-Dist: ruff
|
|
31
|
+
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
|
|
32
32
|
Requires-Dist: vulture>=2.13; extra == 'dev'
|
|
33
33
|
Provides-Extra: e2e
|
|
34
34
|
Requires-Dist: pytest>=8; extra == 'e2e'
|
|
@@ -93,7 +93,7 @@ Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
|
93
93
|
|
|
94
94
|
## Defenses (built in)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
13 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
97
97
|
Each carries the historical incident it codifies and the invariant test that
|
|
98
98
|
guards it. Highlights:
|
|
99
99
|
|
|
@@ -123,13 +123,24 @@ agent-runner monitor # 30s poll
|
|
|
123
123
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
Watch a remote host's event stream from your laptop — one command instead of a
|
|
127
|
+
hand-rolled `ssh … ; sleep` loop:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agent-runner monitor --host pi --mode events # managed ssh relay, JSONL stdout
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The relay reconnects with `--since <last ts>` so a dropped link replays its gap,
|
|
134
|
+
gives up (exit 1) once the outage passes `remote_failure_tolerance_s`, and kills
|
|
135
|
+
the ssh process group on exit so no orphan tree is left behind.
|
|
136
|
+
|
|
137
|
+
> **Detection stays on the host.** `--host` with `--mode anomaly | narrate |
|
|
138
|
+
> http` exits with an error: the detectors read the supervised host's logs and
|
|
139
|
+
> auto-stop its service, which must keep working with your laptop closed. Run
|
|
140
|
+
> the monitor there (`ssh <alias>`, then `agent-runner monitor`) and relay its
|
|
141
|
+
> events here. See [`docs/runbook.md`](docs/runbook.md) § "Remote event relay &
|
|
142
|
+
> SSH trust" — the SSH trust boundary applies to any agent-runner command you
|
|
143
|
+
> drive over ssh.
|
|
133
144
|
|
|
134
145
|
## Documentation
|
|
135
146
|
|
|
@@ -57,7 +57,7 @@ Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
|
57
57
|
|
|
58
58
|
## Defenses (built in)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
13 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
61
61
|
Each carries the historical incident it codifies and the invariant test that
|
|
62
62
|
guards it. Highlights:
|
|
63
63
|
|
|
@@ -87,13 +87,24 @@ agent-runner monitor # 30s poll
|
|
|
87
87
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
Watch a remote host's event stream from your laptop — one command instead of a
|
|
91
|
+
hand-rolled `ssh … ; sleep` loop:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
agent-runner monitor --host pi --mode events # managed ssh relay, JSONL stdout
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The relay reconnects with `--since <last ts>` so a dropped link replays its gap,
|
|
98
|
+
gives up (exit 1) once the outage passes `remote_failure_tolerance_s`, and kills
|
|
99
|
+
the ssh process group on exit so no orphan tree is left behind.
|
|
100
|
+
|
|
101
|
+
> **Detection stays on the host.** `--host` with `--mode anomaly | narrate |
|
|
102
|
+
> http` exits with an error: the detectors read the supervised host's logs and
|
|
103
|
+
> auto-stop its service, which must keep working with your laptop closed. Run
|
|
104
|
+
> the monitor there (`ssh <alias>`, then `agent-runner monitor`) and relay its
|
|
105
|
+
> events here. See [`docs/runbook.md`](docs/runbook.md) § "Remote event relay &
|
|
106
|
+
> SSH trust" — the SSH trust boundary applies to any agent-runner command you
|
|
107
|
+
> drive over ssh.
|
|
97
108
|
|
|
98
109
|
## Documentation
|
|
99
110
|
|
|
@@ -22,6 +22,7 @@ __all__ = [
|
|
|
22
22
|
"emit_rate_limit_stop",
|
|
23
23
|
"emit_round_grace_extended",
|
|
24
24
|
"emit_round_grace_kill",
|
|
25
|
+
"emit_round_logs_prune_deferred",
|
|
25
26
|
"emit_round_progress",
|
|
26
27
|
"emit_round_substrate_after",
|
|
27
28
|
"emit_round_substrate_before",
|
|
@@ -346,6 +347,42 @@ def emit_round_grace_extended(
|
|
|
346
347
|
)
|
|
347
348
|
|
|
348
349
|
|
|
350
|
+
def emit_round_logs_prune_deferred(
|
|
351
|
+
log_dir: Path,
|
|
352
|
+
*,
|
|
353
|
+
directory: str,
|
|
354
|
+
existing: int,
|
|
355
|
+
keep: int,
|
|
356
|
+
would_delete: int,
|
|
357
|
+
) -> None:
|
|
358
|
+
"""Emit when a round-log prune was deferred because it would be *bulk*
|
|
359
|
+
(it would delete more files than it keeps), so nothing was deleted.
|
|
360
|
+
|
|
361
|
+
``directory`` is the directory holding the family — ``{log_dir}/rounds``
|
|
362
|
+
for agent transcripts, ``{log_dir}`` for the serve-level ``round-<N>.log``
|
|
363
|
+
files. Re-emitted on every prune attempt while the condition holds: the
|
|
364
|
+
deferral is permanent until an operator acts, and a one-shot event would
|
|
365
|
+
be missed by anyone who started watching later.
|
|
366
|
+
|
|
367
|
+
The hint is composed here rather than at the call sites so both families
|
|
368
|
+
name the same knob with the same wording.
|
|
369
|
+
"""
|
|
370
|
+
from agent_runner.events import ROUND_LOGS_PRUNE_DEFERRED, emit
|
|
371
|
+
|
|
372
|
+
emit(
|
|
373
|
+
log_dir,
|
|
374
|
+
ROUND_LOGS_PRUNE_DEFERRED,
|
|
375
|
+
directory=directory,
|
|
376
|
+
existing=existing,
|
|
377
|
+
keep=keep,
|
|
378
|
+
would_delete=would_delete,
|
|
379
|
+
hint=(
|
|
380
|
+
f"nothing deleted; raise runtime.round_log_retention to >= {existing} "
|
|
381
|
+
f"to keep this backlog, or delete files in {directory} yourself"
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
349
386
|
def emit_anomaly_repetitive_tool(
|
|
350
387
|
log_dir: Path,
|
|
351
388
|
*,
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 6)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -16,9 +16,9 @@ import signal
|
|
|
16
16
|
import subprocess # noqa: TID251 — api uses systemctl + ssh, both subprocess
|
|
17
17
|
import sysconfig
|
|
18
18
|
import time
|
|
19
|
-
from collections.abc import Iterator
|
|
19
|
+
from collections.abc import Iterator, Sequence
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Any, Literal
|
|
21
|
+
from typing import Any, Literal, TextIO
|
|
22
22
|
|
|
23
23
|
from agent_runner import events, lifecycle
|
|
24
24
|
from agent_runner.api_types import (
|
|
@@ -383,8 +383,6 @@ from agent_runner import defenses, monitor # noqa: E402
|
|
|
383
383
|
from agent_runner.events import ( # noqa: E402
|
|
384
384
|
AGENT_NETWORK_BLIP,
|
|
385
385
|
HOOK_FAILED,
|
|
386
|
-
MONITOR_REMOTE_BLIP,
|
|
387
|
-
MONITOR_REMOTE_GIVEUP,
|
|
388
386
|
MONITOR_STARTED,
|
|
389
387
|
)
|
|
390
388
|
|
|
@@ -479,16 +477,13 @@ def peek(
|
|
|
479
477
|
return state if select is None else select_path(state, select)
|
|
480
478
|
|
|
481
479
|
|
|
482
|
-
def _poll_once(project: str | Path
|
|
480
|
+
def _poll_once(project: str | Path) -> list[monitor.Alert]:
|
|
483
481
|
work_dir = project if isinstance(project, Path) else Path.cwd()
|
|
484
482
|
cfg = load_config(work_dir / "agent-runner.toml")
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
# Dormant: monitor_loop rejects --host before any poll, so this branch
|
|
490
|
-
# is unreachable today. See monitor.MonitorRemoteUnsupportedError.
|
|
491
|
-
src = monitor.RemoteSource(host=host, project=_project_name(work_dir))
|
|
483
|
+
# Always local: detection runs on the supervised host by design. Remote
|
|
484
|
+
# observation is an event RELAY (``monitor --host X --mode events``), not a
|
|
485
|
+
# remote poll — see agent_runner/remote_relay.py.
|
|
486
|
+
src: monitor.StateSource = monitor.LocalSource(log_dir=cfg.runtime.log_dir)
|
|
492
487
|
events = monitor.parse_events_from_jsonl_files(src.events_files())
|
|
493
488
|
metrics = monitor.parse_events_from_jsonl_files(src.metrics_files())
|
|
494
489
|
log_tails = monitor.load_round_log_tails(src.rounds_dir())
|
|
@@ -512,11 +507,6 @@ def _poll_once(project: str | Path, *, host: str | None) -> list[monitor.Alert]:
|
|
|
512
507
|
return builtin + plugin
|
|
513
508
|
|
|
514
509
|
|
|
515
|
-
# Backoff schedule for remote-failure retries: each element is the sleep duration
|
|
516
|
-
# in seconds for that attempt index (capped at 30s per step).
|
|
517
|
-
_REMOTE_FAILURE_BACKOFF = (1, 2, 4, 8, 16, 30)
|
|
518
|
-
|
|
519
|
-
|
|
520
510
|
def monitor_loop(
|
|
521
511
|
project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
|
|
522
512
|
) -> Iterator[monitor.Alert]:
|
|
@@ -527,11 +517,11 @@ def monitor_loop(
|
|
|
527
517
|
to that kind as the canonical "supervision is up" signal (monitor is otherwise
|
|
528
518
|
silent during healthy operation by design).
|
|
529
519
|
|
|
530
|
-
``host``
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
520
|
+
``host`` raises ``MonitorRemoteUnsupportedError`` immediately: detection runs
|
|
521
|
+
on the supervised host by design, so there is no remote polling mode. The
|
|
522
|
+
check is eager — this wrapper validates before handing back the generator,
|
|
523
|
+
so the failure lands at startup rather than at the first ``next()``. For a
|
|
524
|
+
remote event stream, see ``relay_remote_events``.
|
|
535
525
|
"""
|
|
536
526
|
if host is not None:
|
|
537
527
|
raise monitor.MonitorRemoteUnsupportedError(host)
|
|
@@ -543,13 +533,9 @@ def _monitor_loop_iter(
|
|
|
543
533
|
) -> Iterator[monitor.Alert]:
|
|
544
534
|
"""Polling generator behind ``monitor_loop``.
|
|
545
535
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
crossing the cap emits one ``monitor_remote_giveup`` and propagates the error
|
|
550
|
-
(CLI exits 1; systemd restarts the process). Setting tolerance to 0 preserves
|
|
551
|
-
the 0.1.10 immediate-propagate behavior with no blip events emitted. That
|
|
552
|
-
path is dormant while remote mode is rejected at startup.
|
|
536
|
+
``host`` is None by construction (``monitor_loop`` rejects anything else);
|
|
537
|
+
it is carried into the ``monitor_started`` payload as an explicit record
|
|
538
|
+
that this monitor watches its own host.
|
|
553
539
|
"""
|
|
554
540
|
import json as _json
|
|
555
541
|
|
|
@@ -566,51 +552,8 @@ def _monitor_loop_iter(
|
|
|
566
552
|
mode="anomaly-only",
|
|
567
553
|
)
|
|
568
554
|
|
|
569
|
-
tolerance_s = cfg.monitor.remote_failure_tolerance_s
|
|
570
|
-
blip_start: float | None = None
|
|
571
|
-
attempt = 0
|
|
572
|
-
|
|
573
555
|
while True:
|
|
574
|
-
|
|
575
|
-
alerts = _poll_once(work_dir, host=host)
|
|
576
|
-
# Success: reset retry state.
|
|
577
|
-
blip_start = None
|
|
578
|
-
attempt = 0
|
|
579
|
-
except monitor.MonitorRemoteError as e:
|
|
580
|
-
if tolerance_s == 0:
|
|
581
|
-
raise # 0.1.10 behavior preserved
|
|
582
|
-
now = time.monotonic()
|
|
583
|
-
if blip_start is None:
|
|
584
|
-
blip_start = now
|
|
585
|
-
attempt += 1
|
|
586
|
-
elapsed = now - blip_start
|
|
587
|
-
if elapsed >= tolerance_s:
|
|
588
|
-
events.emit(
|
|
589
|
-
cfg.runtime.log_dir,
|
|
590
|
-
MONITOR_REMOTE_GIVEUP,
|
|
591
|
-
host=host,
|
|
592
|
-
total_attempts=attempt,
|
|
593
|
-
total_elapsed_s=elapsed,
|
|
594
|
-
cap_s=tolerance_s,
|
|
595
|
-
final_error=e.stderr,
|
|
596
|
-
)
|
|
597
|
-
raise
|
|
598
|
-
backoff_idx = min(attempt - 1, len(_REMOTE_FAILURE_BACKOFF) - 1)
|
|
599
|
-
next_sleep_s = min(_REMOTE_FAILURE_BACKOFF[backoff_idx], tolerance_s - elapsed)
|
|
600
|
-
events.emit(
|
|
601
|
-
cfg.runtime.log_dir,
|
|
602
|
-
MONITOR_REMOTE_BLIP,
|
|
603
|
-
host=host,
|
|
604
|
-
error=e.stderr,
|
|
605
|
-
attempt=attempt,
|
|
606
|
-
elapsed_s=elapsed,
|
|
607
|
-
cap_s=tolerance_s,
|
|
608
|
-
interval_s=interval_s,
|
|
609
|
-
next_sleep_s=next_sleep_s,
|
|
610
|
-
)
|
|
611
|
-
time.sleep(next_sleep_s)
|
|
612
|
-
continue
|
|
613
|
-
|
|
556
|
+
alerts = _poll_once(work_dir)
|
|
614
557
|
for alert in alerts:
|
|
615
558
|
key = f"{alert.detector}:{_json.dumps(alert.context, sort_keys=True)}"
|
|
616
559
|
if key in seen:
|
|
@@ -620,7 +563,6 @@ def _monitor_loop_iter(
|
|
|
620
563
|
monitor.on_alert(
|
|
621
564
|
alert,
|
|
622
565
|
project=_project_name(work_dir),
|
|
623
|
-
host=host,
|
|
624
566
|
log_dir=cfg.runtime.log_dir,
|
|
625
567
|
allowed_stop_names=cfg.monitor.auto_stop_on,
|
|
626
568
|
)
|
|
@@ -800,6 +742,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
800
742
|
emit_rate_limit_stop,
|
|
801
743
|
emit_round_grace_extended,
|
|
802
744
|
emit_round_grace_kill,
|
|
745
|
+
emit_round_logs_prune_deferred,
|
|
803
746
|
emit_round_progress,
|
|
804
747
|
emit_round_substrate_after,
|
|
805
748
|
emit_round_substrate_before,
|
|
@@ -849,6 +792,41 @@ def stream_events_jsonl(log_dir: Path, *, poll_interval_s: float = 0.1) -> Itera
|
|
|
849
792
|
yield from _tail_events_jsonl(log_dir, start_at_now=True, poll_interval_s=poll_interval_s)
|
|
850
793
|
|
|
851
794
|
|
|
795
|
+
def relay_remote_events(
|
|
796
|
+
host: str,
|
|
797
|
+
*,
|
|
798
|
+
log_dir: Path,
|
|
799
|
+
kinds: Sequence[str] | None = None,
|
|
800
|
+
remote_config: str | None = None,
|
|
801
|
+
failure_tolerance_s: float = 90.0,
|
|
802
|
+
out: TextIO | None = None,
|
|
803
|
+
) -> int:
|
|
804
|
+
"""Remote sibling of ``stream_events_jsonl``: relay ``host``'s event stream.
|
|
805
|
+
|
|
806
|
+
Spawns a managed ``ssh <host> -- agent-runner events --tail`` and passes its
|
|
807
|
+
JSONL through, reconnecting with ``--since`` so a dropped link replays its
|
|
808
|
+
gap. Returns a CLI exit code (0 on interrupt, 1 once the link stays down
|
|
809
|
+
past ``failure_tolerance_s``). Blocks until then.
|
|
810
|
+
|
|
811
|
+
``log_dir`` is this CLIENT's log dir: the ``monitor_remote_blip`` /
|
|
812
|
+
``monitor_remote_giveup`` events it writes describe the local machine's link
|
|
813
|
+
to ``host``, not the supervised project. Detection is deliberately NOT
|
|
814
|
+
relayed — the detectors run on ``host`` (see ``monitor_loop``).
|
|
815
|
+
"""
|
|
816
|
+
# Late import: pulls agent_runtime (psutil, threading) only for the callers
|
|
817
|
+
# that actually stream from a remote host.
|
|
818
|
+
from agent_runner.remote_relay import relay_remote_events as _relay
|
|
819
|
+
|
|
820
|
+
return _relay(
|
|
821
|
+
host,
|
|
822
|
+
log_dir=log_dir,
|
|
823
|
+
kinds=kinds,
|
|
824
|
+
remote_config=remote_config,
|
|
825
|
+
failure_tolerance_s=failure_tolerance_s,
|
|
826
|
+
out=out,
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
|
|
852
830
|
def _format_narrate_line(evt: dict[str, Any]) -> str:
|
|
853
831
|
"""Format an event dict as a one-line human-readable string.
|
|
854
832
|
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
One-shot (--window N) or streaming (--tail) query against events.jsonl.
|
|
4
4
|
JSON Lines output (one JSON object per line, no pretty-print).
|
|
5
5
|
|
|
6
|
-
Current-month scope only
|
|
6
|
+
Current-month scope only, except under ``--since <ISO ts>``, which replays every
|
|
7
|
+
matching event with ``ts >= since`` across month files (and then follows, under
|
|
8
|
+
``--tail``). That replay is at-least-once by contract: a client resumes by
|
|
9
|
+
passing the last ts it saw, so the boundary event may arrive twice — no event is
|
|
10
|
+
ever silently lost across a dropped connection.
|
|
11
|
+
|
|
12
|
+
Tail mode follows month rollover via per-poll glob.
|
|
7
13
|
"""
|
|
8
14
|
|
|
9
15
|
from __future__ import annotations
|
|
@@ -16,6 +22,8 @@ import time
|
|
|
16
22
|
from datetime import UTC, datetime
|
|
17
23
|
from pathlib import Path
|
|
18
24
|
|
|
25
|
+
from agent_runner.events import parse_iso_ms
|
|
26
|
+
|
|
19
27
|
# Sentinel for "user did not explicitly set --window" so we can detect
|
|
20
28
|
# --window + --tail combinations. argparse mutually-exclusive group would
|
|
21
29
|
# be cleaner but argparse doesn't support "exclusive only when X has value Y".
|
|
@@ -64,6 +72,18 @@ def add_parser(sub, parent) -> None:
|
|
|
64
72
|
action="store_true",
|
|
65
73
|
help=("Streaming mode: emit each new matching event as it fires (blocks until SIGINT)."),
|
|
66
74
|
)
|
|
75
|
+
p.add_argument(
|
|
76
|
+
"--since",
|
|
77
|
+
type=str,
|
|
78
|
+
default=None,
|
|
79
|
+
metavar="ISO-TS",
|
|
80
|
+
help=(
|
|
81
|
+
"Replay every matching event with ts >= this ISO-8601 timestamp "
|
|
82
|
+
"(e.g. 2026-07-27T10:00:00.000Z), across month files, then keep "
|
|
83
|
+
"streaming if --tail. At-least-once: resume by passing the last ts "
|
|
84
|
+
"you saw — that event may repeat, none is lost. Excludes --window."
|
|
85
|
+
),
|
|
86
|
+
)
|
|
67
87
|
p.set_defaults(func=cmd_events)
|
|
68
88
|
|
|
69
89
|
|
|
@@ -97,6 +117,27 @@ def cmd_events(args) -> int:
|
|
|
97
117
|
)
|
|
98
118
|
return 2
|
|
99
119
|
|
|
120
|
+
since_raw = getattr(args, "since", None)
|
|
121
|
+
since: datetime | None = None
|
|
122
|
+
if since_raw is not None:
|
|
123
|
+
if window_explicit:
|
|
124
|
+
print(
|
|
125
|
+
"Error: --window and --since are mutually exclusive",
|
|
126
|
+
file=sys.stderr,
|
|
127
|
+
)
|
|
128
|
+
return 2
|
|
129
|
+
try:
|
|
130
|
+
since = parse_iso_ms(since_raw)
|
|
131
|
+
except ValueError:
|
|
132
|
+
print(
|
|
133
|
+
f"Error: --since expects an ISO-8601 timestamp "
|
|
134
|
+
f"(e.g. 2026-07-27T10:00:00.000Z), got {since_raw!r}",
|
|
135
|
+
file=sys.stderr,
|
|
136
|
+
)
|
|
137
|
+
return 2
|
|
138
|
+
if since.tzinfo is None:
|
|
139
|
+
since = since.replace(tzinfo=UTC)
|
|
140
|
+
|
|
100
141
|
try:
|
|
101
142
|
log_dir = _resolve_log_dir(args)
|
|
102
143
|
except FileNotFoundError as e:
|
|
@@ -104,7 +145,15 @@ def cmd_events(args) -> int:
|
|
|
104
145
|
return 1
|
|
105
146
|
|
|
106
147
|
if args.tail:
|
|
107
|
-
return _tail_events(log_dir, kind_set)
|
|
148
|
+
return _tail_events(log_dir, kind_set, since=since)
|
|
149
|
+
|
|
150
|
+
if since is not None:
|
|
151
|
+
try:
|
|
152
|
+
_replay_since(log_dir, kind_set, since) # offset only matters to --tail
|
|
153
|
+
except OSError as e:
|
|
154
|
+
print(f"Error: events file unreadable: {e}", file=sys.stderr)
|
|
155
|
+
return 1
|
|
156
|
+
return 0
|
|
108
157
|
|
|
109
158
|
window = args.window if args.window != _WINDOW_DEFAULT_SENTINEL else 10
|
|
110
159
|
return _query_events(log_dir, kind_set, window)
|
|
@@ -115,6 +164,68 @@ def _current_month_events_file(log_dir: Path) -> Path:
|
|
|
115
164
|
return log_dir / f"events-{month}.jsonl"
|
|
116
165
|
|
|
117
166
|
|
|
167
|
+
def _month_of(events_file: Path) -> str:
|
|
168
|
+
"""The YYYY-MM embedded in an ``events-YYYY-MM.jsonl`` filename."""
|
|
169
|
+
return events_file.name[len("events-") : -len(".jsonl")]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _matches_since(line: str, kind_set: set[str], since: datetime) -> bool:
|
|
173
|
+
"""True if ``line`` is a matching kind whose ts is at or after ``since``.
|
|
174
|
+
|
|
175
|
+
Malformed lines and lines carrying no parseable ``ts`` are skipped silently,
|
|
176
|
+
matching the tolerance of the other parse loops in this module.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
evt = json.loads(line)
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
return False
|
|
182
|
+
if evt.get("event") not in kind_set:
|
|
183
|
+
return False
|
|
184
|
+
ts = evt.get("ts")
|
|
185
|
+
if not isinstance(ts, str):
|
|
186
|
+
return False
|
|
187
|
+
try:
|
|
188
|
+
parsed = parse_iso_ms(ts)
|
|
189
|
+
except ValueError:
|
|
190
|
+
return False
|
|
191
|
+
if parsed.tzinfo is None:
|
|
192
|
+
parsed = parsed.replace(tzinfo=UTC)
|
|
193
|
+
return parsed >= since
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _replay_since(log_dir: Path, kind_set: set[str], since: datetime) -> tuple[Path, int]:
|
|
197
|
+
"""Emit every matching event with ``ts >= since``, oldest month file first.
|
|
198
|
+
|
|
199
|
+
Not windowed: replay is the resume path, so it emits the whole backlog.
|
|
200
|
+
|
|
201
|
+
Returns ``(current-month file, bytes consumed of it)``. That offset is the
|
|
202
|
+
handoff point for ``--tail``: it is the true end-of-read position, so a line
|
|
203
|
+
appended while the replay was running is picked up by the poll loop instead
|
|
204
|
+
of being skipped, and no line is emitted in both phases.
|
|
205
|
+
"""
|
|
206
|
+
since_month = since.astimezone(UTC).strftime("%Y-%m")
|
|
207
|
+
current = _current_month_events_file(log_dir)
|
|
208
|
+
offset = 0
|
|
209
|
+
for path in sorted(log_dir.glob("events-*.jsonl")):
|
|
210
|
+
if _month_of(path) < since_month:
|
|
211
|
+
# Nothing to replay from a month that ended before `since`. The live
|
|
212
|
+
# file lands here only when `since` names a future month — seed the
|
|
213
|
+
# tail at its end rather than rewinding it to 0.
|
|
214
|
+
if path == current:
|
|
215
|
+
offset = path.stat().st_size
|
|
216
|
+
continue
|
|
217
|
+
with path.open("r", encoding="utf-8") as f:
|
|
218
|
+
for line in f:
|
|
219
|
+
line = line.strip()
|
|
220
|
+
if not line:
|
|
221
|
+
continue
|
|
222
|
+
if _matches_since(line, kind_set, since):
|
|
223
|
+
print(line, flush=True)
|
|
224
|
+
if path == current:
|
|
225
|
+
offset = f.tell()
|
|
226
|
+
return current, offset
|
|
227
|
+
|
|
228
|
+
|
|
118
229
|
def _query_events(log_dir: Path, kind_set: set[str], window: int) -> int:
|
|
119
230
|
"""One-shot: read current-month events.jsonl, filter, print last N."""
|
|
120
231
|
events_file = _current_month_events_file(log_dir)
|
|
@@ -143,10 +254,15 @@ def _query_events(log_dir: Path, kind_set: set[str], window: int) -> int:
|
|
|
143
254
|
return 0
|
|
144
255
|
|
|
145
256
|
|
|
146
|
-
def _tail_events(log_dir: Path, kind_set: set[str]) -> int:
|
|
257
|
+
def _tail_events(log_dir: Path, kind_set: set[str], since: datetime | None = None) -> int:
|
|
147
258
|
"""Streaming: poll current-month events.jsonl at 1s interval; emit each
|
|
148
259
|
new matching line as it fires. Blocks until SIGINT (KeyboardInterrupt).
|
|
149
260
|
Follows month rollover via per-poll glob.
|
|
261
|
+
|
|
262
|
+
With ``since``, the backlog (``ts >= since``, across month files) is replayed
|
|
263
|
+
first and the poll resumes at the exact byte the replay stopped on — no gap
|
|
264
|
+
and no duplicate across the handoff. At-least-once overall: the caller
|
|
265
|
+
resumes from the last ts it saw, so that one event may repeat.
|
|
150
266
|
"""
|
|
151
267
|
last_size = 0
|
|
152
268
|
current_file: Path | None = None
|
|
@@ -156,6 +272,13 @@ def _tail_events(log_dir: Path, kind_set: set[str]) -> int:
|
|
|
156
272
|
|
|
157
273
|
signal.signal(signal.SIGINT, _handle_sigint)
|
|
158
274
|
|
|
275
|
+
if since is not None:
|
|
276
|
+
try:
|
|
277
|
+
current_file, last_size = _replay_since(log_dir, kind_set, since)
|
|
278
|
+
except OSError as e:
|
|
279
|
+
print(f"Error: events file unreadable: {e}", file=sys.stderr)
|
|
280
|
+
return 1
|
|
281
|
+
|
|
159
282
|
try:
|
|
160
283
|
while True:
|
|
161
284
|
events_file = _current_month_events_file(log_dir)
|