cli-agent-runner 0.2.4__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/workflows/release.yml +7 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.vulture-whitelist.py +0 -4
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/CHANGELOG.md +14 -1
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/PKG-INFO +20 -9
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/README.md +18 -7
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/api.py +51 -74
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/events_cmd.py +126 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/monitor_cmd.py +61 -14
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/config.py +3 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/monitor.py +40 -115
- cli_agent_runner-0.2.5/agent_runner/remote_relay.py +270 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/architecture.md +32 -16
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/commands.md +53 -9
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/configuration.md +5 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/events.md +3 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.28.md +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.30.md +3 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.35.md +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.2.0.md +2 -3
- cli_agent_runner-0.2.5/docs/migrations/0.2.5.md +99 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/plugins.md +28 -15
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/runbook.md +93 -21
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/thesis.md +4 -3
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/pyproject.toml +3 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_monitor_seeded.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_module_boundaries.py +1 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_observation.py +8 -187
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_service.py +2 -2
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_auto_stop_gating.py +1 -5
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli_service_peek_monitor.py +79 -56
- cli_agent_runner-0.2.5/tests/unit/test_events_cmd.py +343 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_events_cmd_tail.py +39 -2
- cli_agent_runner-0.2.5/tests/unit/test_monitor_remote.py +320 -0
- 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 → cli_agent_runner-0.2.5}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/.gitignore +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/LICENSE +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/README.zh.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_emit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/build.sh +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/README.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.1.42.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.2.1.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.2.2.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.2.3.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/migrations/0.2.4.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.4 → cli_agent_runner-0.2.5}/tests/unit/test_vcs_state.py +0 -0
|
@@ -9,9 +9,13 @@ permissions:
|
|
|
9
9
|
|
|
10
10
|
# Trusted Publishing: the `environment` name below MUST match the
|
|
11
11
|
# `Environment name` field in the PyPI pending publisher config.
|
|
12
|
-
# A mismatch fails publish with an opaque OIDC error.
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# A mismatch fails publish with an opaque OIDC error. Keep the environment
|
|
13
|
+
# declared even when it carries no protection rules — it is the OIDC claim,
|
|
14
|
+
# not just an approval gate; deleting it breaks publishing outright. Whether
|
|
15
|
+
# a tag publishes straight through or waits for a reviewer is configured in
|
|
16
|
+
# the repo's environment settings, not here.
|
|
17
|
+
# Note: TestPyPI stage intentionally omitted — the tag is the release signal;
|
|
18
|
+
# pre-tag verification is `./build.sh check` plus the CI matrix on main.
|
|
15
19
|
|
|
16
20
|
concurrency:
|
|
17
21
|
group: release-${{ github.ref }}
|
|
@@ -5,6 +5,17 @@ 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.5] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `events --since <ISO ts>` — replays every matching event with `ts >= since`, across month files, one-shot or as the backlog phase of `--tail`. At-least-once: a client whose stream dropped reconnects with the last ts it saw and loses nothing (that one event may repeat).
|
|
12
|
+
- `monitor --host <alias> --mode events` — a managed ssh event relay, replacing a hand-rolled `ssh … --tail; sleep` loop. Passes the remote's JSONL through unmodified, reconnects with `--since <last ts>` so a dropped link replays its gap, emits `monitor_remote_blip` per drop and `monitor_remote_giveup` + exit 1 once the outage passes `[monitor] remote_failure_tolerance_s`, and kills the ssh process GROUP on exit so no orphan tree survives. New flags `--kind` and `--remote-config`.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
- The polling remote monitor (`RemoteSource`, `run_remote_command`, `MonitorRemoteError`, auto-stop over ssh) — it read remote paths on the local filesystem and was already failing loud since 0.2.4. Detection is on-host by design; remote observation is the relay above. `monitor.on_alert` lost its `host` parameter and `monitor_auto_stop_triggered` its `host` field; `monitor_auto_stop_failed` now reports a failed *local* stop (previously a failed ssh stop) instead of crashing the monitor loop.
|
|
16
|
+
|
|
17
|
+
See `docs/migrations/0.2.5.md`.
|
|
18
|
+
|
|
8
19
|
## [0.2.4] - 2026-07-27
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -648,6 +659,7 @@ No breaking changes. Plugin authors:
|
|
|
648
659
|
between rounds, opt them out:
|
|
649
660
|
```python
|
|
650
661
|
from agent_runner.vcs_state import register_plugin_owned_paths
|
|
662
|
+
|
|
651
663
|
register_plugin_owned_paths(["your-output-dir/", "logs/your-plugin/**/*"])
|
|
652
664
|
```
|
|
653
665
|
- If you followed the old `_register()` pattern from docs and noticed
|
|
@@ -938,7 +950,8 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
938
950
|
- Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
|
|
939
951
|
gated by a manual approval on the `pypi` GitHub environment.
|
|
940
952
|
|
|
941
|
-
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.
|
|
953
|
+
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.5...HEAD
|
|
954
|
+
[0.2.5]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.4...v0.2.5
|
|
942
955
|
[0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
|
|
943
956
|
[0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
|
|
944
957
|
[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.5
|
|
4
4
|
Summary: Restart-on-exit supervisor for autonomous CLI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
|
|
6
6
|
Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
|
|
@@ -28,7 +28,7 @@ Requires-Dist: psutil>=5.9
|
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
30
30
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
|
-
Requires-Dist: ruff
|
|
31
|
+
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
|
|
32
32
|
Requires-Dist: vulture>=2.13; extra == 'dev'
|
|
33
33
|
Provides-Extra: e2e
|
|
34
34
|
Requires-Dist: pytest>=8; extra == 'e2e'
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 5)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -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
|
)
|
|
@@ -849,6 +791,41 @@ def stream_events_jsonl(log_dir: Path, *, poll_interval_s: float = 0.1) -> Itera
|
|
|
849
791
|
yield from _tail_events_jsonl(log_dir, start_at_now=True, poll_interval_s=poll_interval_s)
|
|
850
792
|
|
|
851
793
|
|
|
794
|
+
def relay_remote_events(
|
|
795
|
+
host: str,
|
|
796
|
+
*,
|
|
797
|
+
log_dir: Path,
|
|
798
|
+
kinds: Sequence[str] | None = None,
|
|
799
|
+
remote_config: str | None = None,
|
|
800
|
+
failure_tolerance_s: float = 90.0,
|
|
801
|
+
out: TextIO | None = None,
|
|
802
|
+
) -> int:
|
|
803
|
+
"""Remote sibling of ``stream_events_jsonl``: relay ``host``'s event stream.
|
|
804
|
+
|
|
805
|
+
Spawns a managed ``ssh <host> -- agent-runner events --tail`` and passes its
|
|
806
|
+
JSONL through, reconnecting with ``--since`` so a dropped link replays its
|
|
807
|
+
gap. Returns a CLI exit code (0 on interrupt, 1 once the link stays down
|
|
808
|
+
past ``failure_tolerance_s``). Blocks until then.
|
|
809
|
+
|
|
810
|
+
``log_dir`` is this CLIENT's log dir: the ``monitor_remote_blip`` /
|
|
811
|
+
``monitor_remote_giveup`` events it writes describe the local machine's link
|
|
812
|
+
to ``host``, not the supervised project. Detection is deliberately NOT
|
|
813
|
+
relayed — the detectors run on ``host`` (see ``monitor_loop``).
|
|
814
|
+
"""
|
|
815
|
+
# Late import: pulls agent_runtime (psutil, threading) only for the callers
|
|
816
|
+
# that actually stream from a remote host.
|
|
817
|
+
from agent_runner.remote_relay import relay_remote_events as _relay
|
|
818
|
+
|
|
819
|
+
return _relay(
|
|
820
|
+
host,
|
|
821
|
+
log_dir=log_dir,
|
|
822
|
+
kinds=kinds,
|
|
823
|
+
remote_config=remote_config,
|
|
824
|
+
failure_tolerance_s=failure_tolerance_s,
|
|
825
|
+
out=out,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
|
|
852
829
|
def _format_narrate_line(evt: dict[str, Any]) -> str:
|
|
853
830
|
"""Format an event dict as a one-line human-readable string.
|
|
854
831
|
|
|
@@ -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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""monitor subcommand — anomaly detection
|
|
1
|
+
"""monitor subcommand — anomaly detection, event stream (local or relayed), HTTP page."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -20,14 +20,29 @@ def add_parser(sub, parent) -> None:
|
|
|
20
20
|
type=str,
|
|
21
21
|
default=None,
|
|
22
22
|
metavar="SSH-ALIAS",
|
|
23
|
-
help=
|
|
23
|
+
help=(
|
|
24
|
+
"Remote ssh alias — supported with --mode events only: agent-runner "
|
|
25
|
+
"manages the ssh, resumes with --since after a drop, and kills the "
|
|
26
|
+
"ssh process group on exit. Detection modes run on the host itself."
|
|
27
|
+
),
|
|
24
28
|
)
|
|
25
29
|
p.add_argument(
|
|
26
30
|
"--interval",
|
|
27
31
|
type=int,
|
|
28
32
|
default=None,
|
|
29
33
|
metavar="SECONDS",
|
|
30
|
-
help="Poll interval (default 30s
|
|
34
|
+
help="Poll interval (default 30s)",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument(
|
|
37
|
+
"--kind",
|
|
38
|
+
type=str,
|
|
39
|
+
default=None,
|
|
40
|
+
metavar="K[,K2,...]",
|
|
41
|
+
help=(
|
|
42
|
+
"Event kinds to relay (--host --mode events only). Default: every "
|
|
43
|
+
"kind this client knows — built-ins plus locally installed plugin "
|
|
44
|
+
"kinds. A kind that exists only on the remote must be named here."
|
|
45
|
+
),
|
|
31
46
|
)
|
|
32
47
|
p.add_argument(
|
|
33
48
|
"--mode",
|
|
@@ -45,13 +60,32 @@ def add_parser(sub, parent) -> None:
|
|
|
45
60
|
metavar="PORT",
|
|
46
61
|
help="HTTP port for --mode http (default 8765, local-only)",
|
|
47
62
|
)
|
|
63
|
+
p.add_argument(
|
|
64
|
+
"--remote-config",
|
|
65
|
+
type=str,
|
|
66
|
+
default=None,
|
|
67
|
+
metavar="PATH",
|
|
68
|
+
help=(
|
|
69
|
+
"Config path ON THE REMOTE HOST for the relayed events command "
|
|
70
|
+
"(--host --mode events only). Default: omit --config entirely, so "
|
|
71
|
+
"the remote resolves ./agent-runner.toml in the ssh landing directory."
|
|
72
|
+
),
|
|
73
|
+
)
|
|
48
74
|
p.set_defaults(func=cmd)
|
|
49
75
|
|
|
50
76
|
|
|
51
77
|
def cmd(args) -> int:
|
|
52
78
|
mode = getattr(args, "mode", "anomaly")
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
host = getattr(args, "host", None)
|
|
80
|
+
remote_only = [
|
|
81
|
+
f"--{name}"
|
|
82
|
+
for name in ("kind", "remote-config")
|
|
83
|
+
if getattr(args, name.replace("-", "_"), None) is not None
|
|
84
|
+
]
|
|
85
|
+
if host is not None and mode != "events":
|
|
86
|
+
return fail(str(monitor.MonitorRemoteUnsupportedError(host, mode)))
|
|
87
|
+
if remote_only and (host is None or mode != "events"):
|
|
88
|
+
return fail(f"{', '.join(remote_only)} applies to --host --mode events only")
|
|
55
89
|
|
|
56
90
|
if mode == "narrate":
|
|
57
91
|
return _cmd_narrate(args)
|
|
@@ -63,11 +97,12 @@ def cmd(args) -> int:
|
|
|
63
97
|
|
|
64
98
|
|
|
65
99
|
def _cmd_anomaly(args) -> int:
|
|
66
|
-
interval = args.interval if args.interval is not None else
|
|
100
|
+
interval = args.interval if args.interval is not None else 30
|
|
67
101
|
json_mode = getattr(args, "json", False)
|
|
68
102
|
try:
|
|
69
103
|
work_dir = work_dir_from_args(args)
|
|
70
|
-
|
|
104
|
+
# No host: cmd() has already rejected --host for every detection mode.
|
|
105
|
+
for alert in api.monitor_loop(work_dir, interval_s=interval):
|
|
71
106
|
if json_mode:
|
|
72
107
|
print(json.dumps(_to_jsonable(alert)))
|
|
73
108
|
sys.stdout.flush()
|
|
@@ -77,21 +112,33 @@ def _cmd_anomaly(args) -> int:
|
|
|
77
112
|
sys.stdout.flush()
|
|
78
113
|
except KeyboardInterrupt:
|
|
79
114
|
return 0
|
|
80
|
-
except monitor.MonitorRemoteUnsupportedError as e:
|
|
81
|
-
return fail(str(e))
|
|
82
|
-
except monitor.MonitorRemoteError as e:
|
|
83
|
-
# Dormant alongside the rest of remote mode: ssh only runs under --host,
|
|
84
|
-
# which the MonitorRemoteUnsupportedError handler above already rejects.
|
|
85
|
-
return fail(f"cannot reach {e.host!r} via ssh: {e.stderr}")
|
|
86
115
|
return 0
|
|
87
116
|
|
|
88
117
|
|
|
89
118
|
def _cmd_events(args) -> int:
|
|
90
|
-
"""JSONL event stream —
|
|
119
|
+
"""JSONL event stream — a local tail, or a managed ssh relay under --host.
|
|
120
|
+
|
|
121
|
+
Both variants read the CLIENT's config: locally for the log dir to tail,
|
|
122
|
+
remotely for where the relay writes its own link telemetry (blip / give-up)
|
|
123
|
+
and for the reconnect deadline.
|
|
124
|
+
"""
|
|
91
125
|
from agent_runner.cli.common import cfg_from_args
|
|
92
126
|
|
|
93
127
|
cfg = cfg_from_args(args)
|
|
94
128
|
log_dir = cfg.runtime.log_dir
|
|
129
|
+
|
|
130
|
+
host = getattr(args, "host", None)
|
|
131
|
+
if host is not None:
|
|
132
|
+
raw_kinds = getattr(args, "kind", None)
|
|
133
|
+
kinds = [k.strip() for k in raw_kinds.split(",") if k.strip()] if raw_kinds else None
|
|
134
|
+
return api.relay_remote_events(
|
|
135
|
+
host,
|
|
136
|
+
log_dir=log_dir,
|
|
137
|
+
kinds=kinds,
|
|
138
|
+
remote_config=getattr(args, "remote_config", None),
|
|
139
|
+
failure_tolerance_s=cfg.monitor.remote_failure_tolerance_s,
|
|
140
|
+
)
|
|
141
|
+
|
|
95
142
|
try:
|
|
96
143
|
for evt in api.stream_events_jsonl(log_dir):
|
|
97
144
|
print(json.dumps(evt), flush=True)
|