cli-agent-runner 0.1.33__tar.gz → 0.1.35__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.
Files changed (221) hide show
  1. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/CHANGELOG.md +17 -0
  2. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/PKG-INFO +3 -3
  3. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/README.md +2 -2
  4. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/README.zh.md +5 -5
  5. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/__init__.py +0 -8
  6. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_version.py +2 -2
  7. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/api_types.py +1 -0
  8. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/builtin_plugins/claude_rate_limit.py +3 -4
  9. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/__init__.py +2 -0
  10. cli_agent_runner-0.1.35/agent_runner/cli/events_cmd.py +188 -0
  11. cli_agent_runner-0.1.35/agent_runner/cli/peek_cmd.py +85 -0
  12. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/metrics.py +28 -2
  13. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/monitor.py +1 -0
  14. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/runner.py +15 -2
  15. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/commands.md +21 -3
  16. cli_agent_runner-0.1.35/docs/migrations/0.1.34.md +110 -0
  17. cli_agent_runner-0.1.35/docs/migrations/0.1.35.md +97 -0
  18. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/plugins.md +120 -4
  19. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/runbook.md +4 -3
  20. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/pyproject.toml +4 -4
  21. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_architecture.py +2 -2
  22. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_entry_points_resolve.py +11 -6
  23. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_module_boundaries.py +1 -0
  24. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_types.py +38 -0
  25. cli_agent_runner-0.1.35/tests/unit/test_events_cmd.py +179 -0
  26. cli_agent_runner-0.1.35/tests/unit/test_peek_select.py +41 -0
  27. cli_agent_runner-0.1.33/agent_runner/cli/peek_cmd.py +0 -156
  28. cli_agent_runner-0.1.33/tests/unit/test_peek_select.py +0 -76
  29. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.codecov.yml +0 -0
  30. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  32. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  33. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  34. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/workflows/ci.yml +0 -0
  35. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.github/workflows/release.yml +0 -0
  36. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.gitignore +0 -0
  37. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/.vulture-whitelist.py +0 -0
  38. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/CODE_OF_CONDUCT.md +0 -0
  39. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/CONTRIBUTING.md +0 -0
  40. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/LICENSE +0 -0
  41. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/SECURITY.md +0 -0
  42. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_docgen.py +0 -0
  43. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_emit.py +0 -0
  44. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_registry.py +0 -0
  45. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_substrate.py +0 -0
  46. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/_throttle.py +0 -0
  47. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/agent_runtime.py +0 -0
  48. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/api.py +0 -0
  49. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/builtin_plugins/__init__.py +0 -0
  50. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/builtin_plugins/_constants.py +0 -0
  51. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/builtin_plugins/gemini.py +0 -0
  52. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/__main__.py +0 -0
  53. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/common.py +0 -0
  54. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/init_cmd.py +0 -0
  55. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/install_cmd.py +0 -0
  56. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/monitor_cmd.py +0 -0
  57. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/round_cmd.py +0 -0
  58. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/serve_cmd.py +0 -0
  59. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/service_cmd.py +0 -0
  60. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/cli/upgrade_cmd.py +0 -0
  61. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/config.py +0 -0
  62. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/context_store.py +0 -0
  63. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/defenses.py +0 -0
  64. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/detector_helpers.py +0 -0
  65. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/events.py +0 -0
  66. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/hooks.py +0 -0
  67. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/http_progress.py +0 -0
  68. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/lifecycle.py +0 -0
  69. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/presets/__init__.py +0 -0
  70. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/presets/aider.toml +0 -0
  71. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/presets/claude.toml +0 -0
  72. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/presets/gemini.toml +0 -0
  73. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/prompt_loader.py +0 -0
  74. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/round_log.py +0 -0
  75. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/round_view.py +0 -0
  76. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/scaffold.py +0 -0
  77. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/service_unit.py +0 -0
  78. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/startup_check.py +0 -0
  79. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/agent_runner/vcs_state.py +0 -0
  80. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/build.sh +0 -0
  81. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/deploy/example-agent-runner.toml +0 -0
  82. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/deploy/launchd.plist.tmpl +0 -0
  83. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/deploy/run-loop.sh +0 -0
  84. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/deploy/systemd.service.tmpl +0 -0
  85. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/README.md +0 -0
  86. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/architecture.md +0 -0
  87. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/configuration.md +0 -0
  88. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/events.md +0 -0
  89. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/long-running-agents.md +0 -0
  90. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/marketing/README.md +0 -0
  91. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/marketing/promo-cn.html +0 -0
  92. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.16.md +0 -0
  93. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.17.md +0 -0
  94. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.19.md +0 -0
  95. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.20.md +0 -0
  96. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.21.md +0 -0
  97. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.22.md +0 -0
  98. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.23.md +0 -0
  99. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.24.md +0 -0
  100. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.25.md +0 -0
  101. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.26.md +0 -0
  102. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.27.md +0 -0
  103. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.28.md +0 -0
  104. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.29.md +0 -0
  105. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.30.md +0 -0
  106. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.31.md +0 -0
  107. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.32.md +0 -0
  108. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/migrations/0.1.33.md +0 -0
  109. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/quickstart.md +0 -0
  110. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/recipes/aider.md +0 -0
  111. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/docs/thesis.md +0 -0
  112. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/__init__.py +0 -0
  113. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/_test_helpers.py +0 -0
  114. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/conftest.py +0 -0
  115. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/contract/__init__.py +0 -0
  116. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/contract/test_public_api_surface.py +0 -0
  117. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/__init__.py +0 -0
  118. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/conftest.py +0 -0
  119. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  120. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/test_e2e_install_systemd.py +0 -0
  121. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  122. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  123. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  124. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  125. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  126. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/__init__.py +0 -0
  127. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_bounded_run.py +0 -0
  128. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_context_enricher_namespacing.py +0 -0
  129. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_fresh_eyes_signal.py +0 -0
  130. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_grace_kill_emission.py +0 -0
  131. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_install_dry_run.py +0 -0
  132. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_monitor_seeded.py +0 -0
  133. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_plugin_detector_loaded.py +0 -0
  134. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_plugin_owned_paths.py +0 -0
  135. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_plugin_real_flow.py +0 -0
  136. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  137. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_scaffold_presets.py +0 -0
  138. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_serve_loop.py +0 -0
  139. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_substrate_fingerprint.py +0 -0
  140. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/integration/test_transient_error_backoff.py +0 -0
  141. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/__init__.py +0 -0
  142. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_atomic_write_enforced.py +0 -0
  143. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_catalogs.py +0 -0
  144. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_classification_ssot.py +0 -0
  145. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_docs_generated.py +0 -0
  146. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_event_kind_registry.py +0 -0
  147. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_event_kinds_ssot.py +0 -0
  148. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_events_doc_contract.py +0 -0
  149. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_layer_2_loop_size.py +0 -0
  150. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_module_sizes.py +0 -0
  151. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_no_ai_signatures.py +0 -0
  152. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  153. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_peek_schema_version.py +0 -0
  154. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  155. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_round_result_stable.py +0 -0
  156. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  157. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/invariants/test_upstream_schema_canary.py +0 -0
  158. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/literate/__init__.py +0 -0
  159. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/literate/parser.py +0 -0
  160. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/literate/test_parser.py +0 -0
  161. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/literate/test_quickstart.py +0 -0
  162. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/__init__.py +0 -0
  163. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_agent_runtime.py +0 -0
  164. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_agent_runtime_grace.py +0 -0
  165. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_agent_runtime_progress.py +0 -0
  166. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_assemble_prompt.py +0 -0
  167. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_events_stream.py +0 -0
  168. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_install.py +0 -0
  169. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_observation.py +0 -0
  170. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_read_round_num.py +0 -0
  171. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_resolve_phase.py +0 -0
  172. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_api_service.py +0 -0
  173. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_auto_stop_gating.py +0 -0
  174. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_claude_error_detector.py +0 -0
  175. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli.py +0 -0
  176. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli_common.py +0 -0
  177. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli_init_install.py +0 -0
  178. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli_monitor_http.py +0 -0
  179. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  180. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_cli_upgrade.py +0 -0
  181. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config.py +0 -0
  182. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config_fresh_eyes.py +0 -0
  183. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config_max_rounds.py +0 -0
  184. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config_stop_file.py +0 -0
  185. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  186. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_config_transient_error_action.py +0 -0
  187. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_context_store.py +0 -0
  188. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_defenses.py +0 -0
  189. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_detector_helpers.py +0 -0
  190. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_detector_protocol.py +0 -0
  191. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_docgen.py +0 -0
  192. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_events.py +0 -0
  193. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  194. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_gemini_plugin.py +0 -0
  195. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_hook_failure_isolation.py +0 -0
  196. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_hooks.py +0 -0
  197. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_http_progress.py +0 -0
  198. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_init_entry_points.py +0 -0
  199. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_lifecycle.py +0 -0
  200. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_metrics.py +0 -0
  201. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_monitor_assembly.py +0 -0
  202. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  203. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  204. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_monitor_detectors.py +0 -0
  205. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_monitor_remote.py +0 -0
  206. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_peek_argparse.py +0 -0
  207. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_presets.py +0 -0
  208. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_prompt_loader.py +0 -0
  209. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_round_log_helpers.py +0 -0
  210. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_round_view.py +0 -0
  211. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_runner.py +0 -0
  212. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_runner_throttle.py +0 -0
  213. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_scaffold.py +0 -0
  214. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_serve_cmd_bounded.py +0 -0
  215. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_serve_round_log.py +0 -0
  216. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_serve_sentinel.py +0 -0
  217. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_serve_startup_hooks.py +0 -0
  218. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_service_unit.py +0 -0
  219. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_startup_check.py +0 -0
  220. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_substrate.py +0 -0
  221. {cli_agent_runner-0.1.33 → cli_agent_runner-0.1.35}/tests/unit/test_vcs_state.py +0 -0
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.35] - 2026-05-20
11
+
12
+ ### Removed
13
+ - `claude_rate_limit_detector` plugin alias (0.1.20-era back-compat layer after the 0.1.23 rename to `claude_error_detector`). Hard-cut at both entry-point and config-mapping layers. See `docs/migrations/0.1.35.md` for the 1-line TOML migration.
14
+
15
+ ## [0.1.34] - 2026-05-20
16
+
17
+ ### Added
18
+ - New verb `agent-runner events --kind K[,K2,...] [--window N] [--tail]` for event-stream observation. One-shot or streaming mode (JSON Lines output). Verbs: 13 → 14.
19
+ - `SystemMetrics.agent_process_count: int` — `pgrep -xc` of agent binary basename, host-wide. Surfaces orphan agent processes in peek output.
20
+ - `docs/plugins.md` worked example for custom monitor detector with plugin-emitted exempt flag — pattern for project-specific detectors that exclude exempt-by-design rounds.
21
+
22
+ ### Removed
23
+ - `peek --select events.<kind>` selector (mis-placed in state-snapshot verb). Use `events --kind <kind>` instead. 1-line `s///` migration.
24
+
25
+ See `docs/migrations/0.1.34.md`.
26
+
10
27
  ## [0.1.33] - 2026-05-19
11
28
 
12
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-agent-runner
3
- Version: 0.1.33
3
+ Version: 0.1.35
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
@@ -80,14 +80,14 @@ agent-runner monitor # live anomaly detection
80
80
 
81
81
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
82
82
 
83
- ## 13 verbs
83
+ ## 16 verbs
84
84
 
85
85
  | Lifecycle | Observation |
86
86
  |---|---|
87
87
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
88
88
  | `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
89
89
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
90
- | `round` / `serve` | |
90
+ | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
91
91
 
92
92
  Verb reference: [`docs/commands.md`](docs/commands.md).
93
93
 
@@ -43,14 +43,14 @@ agent-runner monitor # live anomaly detection
43
43
 
44
44
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
45
45
 
46
- ## 13 verbs
46
+ ## 16 verbs
47
47
 
48
48
  | Lifecycle | Observation |
49
49
  |---|---|
50
50
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
51
51
  | `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
52
52
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
53
- | `round` / `serve` | |
53
+ | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
54
54
 
55
55
  Verb reference: [`docs/commands.md`](docs/commands.md).
56
56
 
@@ -20,9 +20,9 @@ supervisor 重启 —— 这是核心模式。中间穿插 11 条防御,避开
20
20
 
21
21
  ```
22
22
  ┌──────────────────────────────────────────┐
23
- │ Layer 3:Witness(monitor) │ 9 个检测器 + 自动停服
23
+ │ Layer 3:Witness(monitor) │ 11 个检测器 + 自动停服
24
24
  ├──────────────────────────────────────────┤
25
- │ Layer 2:Loop(serve,~60 LOC 薄壳) │ 捕获信号,循环拉起 round
25
+ │ Layer 2:Loop(serve,~120 LOC 薄壳) │ 捕获信号,循环拉起 round
26
26
  ├──────────────────────────────────────────┤
27
27
  │ Layer 1:Round(round) │ 跑一次 agent,跑完即退
28
28
  └──────────────────────────────────────────┘
@@ -57,14 +57,14 @@ agent-runner monitor # 实时异常检测,OAuth/磁盘 critical
57
57
 
58
58
  完整上手流程:[`docs/quickstart.md`](docs/quickstart.md)。
59
59
 
60
- ## 13 个动词
60
+ ## 16 个动词
61
61
 
62
62
  | 生命周期 | 观察 |
63
63
  |---|---|
64
64
  | `init` / `install` / `uninstall` | `peek` —— 项目状态快照 |
65
65
  | `start` / `stop` / `kill` / `cancel` | `watch` —— peek 在刷新循环里 |
66
- | `restart` / `status` | `monitor` —— 9 个检测器 + 告警 + 自动停服 |
67
- | `round` / `serve` | |
66
+ | `restart` / `status` | `monitor` —— 11 个检测器 + 告警 + 自动停服 |
67
+ | `round` / `serve` / `upgrade` | `events` —— 查询 / 流式订阅 events.jsonl |
68
68
 
69
69
  **停服三动词**有清晰的语义分层:
70
70
  - `stop` —— 优雅,等当前 round 跑完再退(最常用)
@@ -20,11 +20,6 @@ _HOOK_GROUPS = (
20
20
  # Surfaced via peek --json `plugins.disabled` for operator visibility.
21
21
  _DISABLED_PLUGIN_NAMES: list[str] = []
22
22
 
23
- # Plugin name aliases for back-compat: old entry-point name -> canonical name.
24
- _PLUGIN_NAME_ALIASES: dict[str, str] = {
25
- "claude_rate_limit_detector": "claude_error_detector", # 0.1.20 -> 0.1.23 rename
26
- }
27
-
28
23
 
29
24
  def _load_plugins_from_group(group: str) -> None:
30
25
  """Discover and load entry_points in ``group``, isolating per-plugin failures.
@@ -98,9 +93,6 @@ def apply_plugin_disable(names: list[str]) -> None:
98
93
  if not names:
99
94
  return
100
95
 
101
- # Translate aliases so old config names keep working
102
- names = [_PLUGIN_NAME_ALIASES.get(n, n) for n in names]
103
-
104
96
  global _DISABLED_PLUGIN_NAMES
105
97
  _DISABLED_PLUGIN_NAMES = list(names)
106
98
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.33'
22
- __version_tuple__ = version_tuple = (0, 1, 33)
21
+ __version__ = version = '0.1.35'
22
+ __version_tuple__ = version_tuple = (0, 1, 35)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -56,6 +56,7 @@ class SystemMetrics:
56
56
  disk_free_gb: float = 0.0
57
57
  load_1m: float | None = None
58
58
  cpu_pct: float | None = None
59
+ agent_process_count: int = 0 # 0.1.34 — pgrep -xc <agent_binary>, host-wide
59
60
 
60
61
 
61
62
  @dataclass(frozen=True)
@@ -7,10 +7,9 @@ with computed reset_at_epoch. Supervisor consumes the event.
7
7
  Also emits agent_usage_recorded per-round with token/cost data from the
8
8
  claude result event (0.1.24+).
9
9
 
10
- Naming history: was `claude_rate_limit_detector` in 0.1.20 (single-purpose
11
- rate-limit detector). Renamed + generalized to multi-classification in 0.1.23.
12
- Old plugin name `claude_rate_limit_detector` retained as entry-point alias
13
- via pyproject.toml.
10
+ Module name is historical: the original 0.1.20 single-purpose
11
+ rate-limit detector was generalized to multi-classification in 0.1.23
12
+ (class + entry-point renamed to `claude_error_detector`; module path kept).
14
13
  """
15
14
 
16
15
  from __future__ import annotations
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
 
18
18
  from agent_runner import __version__
19
19
  from agent_runner.cli import (
20
+ events_cmd,
20
21
  init_cmd,
21
22
  install_cmd,
22
23
  monitor_cmd,
@@ -63,6 +64,7 @@ def _build_parser() -> argparse.ArgumentParser:
63
64
  install_cmd.add_parser(sub, parent)
64
65
  service_cmd.add_parser(sub, parent)
65
66
  peek_cmd.add_parser(sub, parent)
67
+ events_cmd.add_parser(sub, parent)
66
68
  monitor_cmd.add_parser(sub, parent)
67
69
  serve_cmd.add_parser(sub, parent)
68
70
  round_cmd.add_parser(sub, parent)
@@ -0,0 +1,188 @@
1
+ """agent-runner events — event-stream observation verb (0.1.34+).
2
+
3
+ One-shot (--window N) or streaming (--tail) query against events.jsonl.
4
+ JSON Lines output (one JSON object per line, no pretty-print).
5
+
6
+ Current-month scope only. Tail mode follows month rollover via per-poll glob.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import signal
14
+ import sys
15
+ import time
16
+ from datetime import UTC, datetime
17
+ from pathlib import Path
18
+
19
+ # Sentinel for "user did not explicitly set --window" so we can detect
20
+ # --window + --tail combinations. argparse mutually-exclusive group would
21
+ # be cleaner but argparse doesn't support "exclusive only when X has value Y".
22
+ _WINDOW_DEFAULT_SENTINEL = -1
23
+
24
+
25
+ def _positive_int(s: str) -> int:
26
+ """Parse positive integer (duplicate of peek_cmd._positive_int; KISS)."""
27
+ try:
28
+ n = int(s)
29
+ except ValueError as e:
30
+ raise argparse.ArgumentTypeError(f"expects positive int, got {s!r}") from e
31
+ if n <= 0:
32
+ raise argparse.ArgumentTypeError(f"expects positive int (> 0), got {n}")
33
+ return n
34
+
35
+
36
+ def _parse_kinds(raw: str) -> set[str]:
37
+ """Parse comma-separated kinds; strip whitespace; reject empty."""
38
+ parts = [k.strip() for k in (raw or "").split(",") if k.strip()]
39
+ return set(parts)
40
+
41
+
42
+ def add_parser(sub, parent) -> None:
43
+ p = sub.add_parser(
44
+ "events",
45
+ parents=[parent],
46
+ help="Query / stream events from events.jsonl by kind",
47
+ )
48
+ p.add_argument(
49
+ "--kind",
50
+ type=str,
51
+ required=True,
52
+ metavar="K[,K2,...]",
53
+ help="Comma-separated event kinds (OR-filtered). At least one required.",
54
+ )
55
+ p.add_argument(
56
+ "--window",
57
+ type=_positive_int,
58
+ default=_WINDOW_DEFAULT_SENTINEL,
59
+ metavar="N",
60
+ help="One-shot mode: emit last N matching events (default 10).",
61
+ )
62
+ p.add_argument(
63
+ "--tail",
64
+ action="store_true",
65
+ help=("Streaming mode: emit each new matching event as it fires (blocks until SIGINT)."),
66
+ )
67
+ p.set_defaults(func=cmd_events)
68
+
69
+
70
+ def _resolve_log_dir(args) -> Path:
71
+ """Resolve log_dir from --config (used by both cmd_events and tests)."""
72
+ if getattr(args, "_log_dir_override", None) is not None:
73
+ return args._log_dir_override
74
+ from agent_runner.cli.common import work_dir_from_args
75
+ from agent_runner.config import load_config
76
+
77
+ cfg = load_config(work_dir_from_args(args) / "agent-runner.toml")
78
+ return cfg.runtime.log_dir
79
+
80
+
81
+ def cmd_events(args) -> int:
82
+ kind_set = _parse_kinds(args.kind)
83
+ if not kind_set:
84
+ print(
85
+ "Error: --kind requires at least one non-empty event kind",
86
+ file=sys.stderr,
87
+ )
88
+ return 2
89
+
90
+ window_explicit = getattr(args, "_window_explicit", False) or (
91
+ args.window != _WINDOW_DEFAULT_SENTINEL
92
+ )
93
+ if args.tail and window_explicit:
94
+ print(
95
+ "Error: --window and --tail are mutually exclusive",
96
+ file=sys.stderr,
97
+ )
98
+ return 2
99
+
100
+ try:
101
+ log_dir = _resolve_log_dir(args)
102
+ except FileNotFoundError as e:
103
+ print(f"Error: config not found: {e}", file=sys.stderr)
104
+ return 1
105
+
106
+ if args.tail:
107
+ return _tail_events(log_dir, kind_set)
108
+
109
+ window = args.window if args.window != _WINDOW_DEFAULT_SENTINEL else 10
110
+ return _query_events(log_dir, kind_set, window)
111
+
112
+
113
+ def _current_month_events_file(log_dir: Path) -> Path:
114
+ month = datetime.now(UTC).strftime("%Y-%m")
115
+ return log_dir / f"events-{month}.jsonl"
116
+
117
+
118
+ def _query_events(log_dir: Path, kind_set: set[str], window: int) -> int:
119
+ """One-shot: read current-month events.jsonl, filter, print last N."""
120
+ events_file = _current_month_events_file(log_dir)
121
+ if not events_file.exists():
122
+ return 0
123
+
124
+ matches: list[str] = []
125
+ try:
126
+ with events_file.open("r", encoding="utf-8") as f:
127
+ for line in f:
128
+ line = line.strip()
129
+ if not line:
130
+ continue
131
+ try:
132
+ evt = json.loads(line)
133
+ except json.JSONDecodeError:
134
+ continue
135
+ if evt.get("event") in kind_set:
136
+ matches.append(line)
137
+ except OSError as e:
138
+ print(f"Error: events file unreadable: {e}", file=sys.stderr)
139
+ return 1
140
+
141
+ for line in matches[-window:]:
142
+ print(line)
143
+ return 0
144
+
145
+
146
+ def _tail_events(log_dir: Path, kind_set: set[str]) -> int:
147
+ """Streaming: poll current-month events.jsonl at 1s interval; emit each
148
+ new matching line as it fires. Blocks until SIGINT (KeyboardInterrupt).
149
+ Follows month rollover via per-poll glob.
150
+ """
151
+ last_size = 0
152
+ current_file: Path | None = None
153
+
154
+ def _handle_sigint(_signum, _frame):
155
+ raise KeyboardInterrupt()
156
+
157
+ signal.signal(signal.SIGINT, _handle_sigint)
158
+
159
+ try:
160
+ while True:
161
+ events_file = _current_month_events_file(log_dir)
162
+ if events_file != current_file:
163
+ # Month rollover OR first iteration: reset offset
164
+ current_file = events_file
165
+ last_size = events_file.stat().st_size if events_file.exists() else 0
166
+
167
+ if events_file.exists():
168
+ size = events_file.stat().st_size
169
+ if size > last_size:
170
+ with events_file.open("r", encoding="utf-8") as f:
171
+ f.seek(last_size)
172
+ for line in f:
173
+ line = line.strip()
174
+ if not line:
175
+ continue
176
+ try:
177
+ evt = json.loads(line)
178
+ except json.JSONDecodeError:
179
+ continue
180
+ if evt.get("event") in kind_set:
181
+ print(line, flush=True)
182
+ last_size = size
183
+ elif size < last_size:
184
+ # File truncated / rotated underneath us; reset
185
+ last_size = 0
186
+ time.sleep(1.0)
187
+ except KeyboardInterrupt:
188
+ return 0
@@ -0,0 +1,85 @@
1
+ """peek and watch subcommands — snapshot + auto-refresh."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ import time
8
+
9
+ from agent_runner import api
10
+ from agent_runner.cli.common import emit, fail, work_dir_from_args
11
+
12
+
13
+ def _round_arg(s: str) -> int | str:
14
+ if s == "latest":
15
+ return s
16
+ try:
17
+ return int(s)
18
+ except ValueError as e:
19
+ raise argparse.ArgumentTypeError(f"--round expects int or 'latest', got {s!r}") from e
20
+
21
+
22
+ def add_parser(sub, parent) -> None:
23
+ for verb, fn in (("peek", cmd_peek), ("watch", cmd_watch)):
24
+ p = sub.add_parser(
25
+ verb, parents=[parent], help=f"{verb} project state with optional drill-down"
26
+ )
27
+ p.add_argument(
28
+ "--round",
29
+ type=_round_arg,
30
+ default=None,
31
+ metavar="N",
32
+ help="Drill into round N (int or 'latest')",
33
+ )
34
+ p.add_argument("--log", action="store_true", help="Include current round's log tail")
35
+ p.add_argument(
36
+ "--events", type=int, default=None, metavar="N", help="Include last N events"
37
+ )
38
+ p.add_argument(
39
+ "--select",
40
+ type=str,
41
+ default=None,
42
+ help=(
43
+ "Selector: dot-path (e.g. system.disk_used_pct) extracts a subtree from "
44
+ "peek state. For event-stream queries, use the dedicated `events` verb."
45
+ ),
46
+ )
47
+ if verb == "watch":
48
+ p.add_argument(
49
+ "--interval",
50
+ type=int,
51
+ default=2,
52
+ metavar="SECONDS",
53
+ help="Refresh interval (default 2)",
54
+ )
55
+ p.set_defaults(func=fn)
56
+
57
+
58
+ def cmd_peek(args) -> int:
59
+ select = args.select
60
+ try:
61
+ result = api.peek(
62
+ work_dir_from_args(args),
63
+ round=args.round,
64
+ log=args.log,
65
+ events=args.events,
66
+ select=select,
67
+ )
68
+ except KeyError as e:
69
+ return fail(str(e))
70
+ except FileNotFoundError as e:
71
+ return fail(f"config not found: {e}")
72
+ emit(result, json_mode=getattr(args, "json", False))
73
+ return 0
74
+
75
+
76
+ def cmd_watch(args) -> int:
77
+ while True:
78
+ sys.stdout.write("\x1b[2J\x1b[H")
79
+ rc = cmd_peek(args)
80
+ if rc != 0:
81
+ return rc
82
+ try:
83
+ time.sleep(args.interval)
84
+ except KeyboardInterrupt:
85
+ return 0
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  import json
9
9
  import os
10
+ import subprocess
10
11
  from datetime import UTC, datetime
11
12
  from pathlib import Path
12
13
  from typing import Any
@@ -16,7 +17,7 @@ import psutil
16
17
  from agent_runner.events import now_iso_ms
17
18
 
18
19
 
19
- def collect(disk_path: Path) -> dict[str, Any]:
20
+ def collect(disk_path: Path, *, agent_binary: str | None = None) -> dict[str, Any]:
20
21
  vm = psutil.virtual_memory()
21
22
  du = psutil.disk_usage(str(disk_path))
22
23
  out: dict[str, Any] = {
@@ -38,15 +39,40 @@ def collect(disk_path: Path) -> dict[str, Any]:
38
39
  out["cpu_pct"] = round(psutil.cpu_percent(interval=None), 1)
39
40
  except Exception:
40
41
  pass
42
+ if agent_binary:
43
+ out["agent_process_count"] = _count_agent_processes(agent_binary)
41
44
  return out
42
45
 
43
46
 
47
+ def _count_agent_processes(agent_binary: str) -> int:
48
+ """Run `pgrep -xc <agent_binary>`; return count or 0 on error.
49
+
50
+ Host-wide intentional — catches orphan agent processes not parented
51
+ by us, which is the diagnostic value of this metric.
52
+ """
53
+ try:
54
+ result = subprocess.run(
55
+ ["pgrep", "-xc", agent_binary],
56
+ capture_output=True,
57
+ text=True,
58
+ timeout=2,
59
+ )
60
+ # pgrep -c returns exit 1 with output "0" when no matches; exit 0
61
+ # with count otherwise. Both are valid; non-int output → 0.
62
+ if result.returncode in (0, 1):
63
+ return int(result.stdout.strip() or "0")
64
+ except (subprocess.SubprocessError, ValueError, FileNotFoundError, OSError):
65
+ pass
66
+ return 0
67
+
68
+
44
69
  def log_metrics(
45
70
  log_dir: Path,
46
71
  *,
47
72
  event: str = "periodic",
48
73
  round_num: int | None = None,
49
74
  phase: str | None = None,
75
+ agent_binary: str | None = None,
50
76
  ) -> None:
51
77
  """Append one metrics sample to metrics-YYYY-MM.jsonl (UTC).
52
78
 
@@ -59,7 +85,7 @@ def log_metrics(
59
85
  payload: dict[str, Any] = {
60
86
  "ts": now_iso_ms(),
61
87
  "event": event,
62
- **collect(log_dir),
88
+ **collect(log_dir, agent_binary=agent_binary),
63
89
  }
64
90
  if round_num is not None:
65
91
  payload["round_num"] = round_num
@@ -515,6 +515,7 @@ def assemble_project_state(source: StateSource, *, project: str) -> ProjectState
515
515
  disk_free_gb=float(latest.get("disk_free_gb", 0.0)),
516
516
  load_1m=latest.get("load_1m"),
517
517
  cpu_pct=latest.get("cpu_pct"),
518
+ agent_process_count=int(latest.get("agent_process_count", 0)),
518
519
  )
519
520
  return ProjectState(
520
521
  project=project,
@@ -441,7 +441,14 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
441
441
  context_store.atomic_write_json(log_dir / context_store.CONTEXT_FILE, enriched_ctx)
442
442
 
443
443
  events.emit(log_dir, "round_start", round_num=round_num, phase=phase)
444
- metrics.log_metrics(log_dir, event="round_start", round_num=round_num, phase=phase)
444
+ _agent_binary = Path(cfg.agent.command[0]).name if cfg.agent.command else None
445
+ metrics.log_metrics(
446
+ log_dir,
447
+ event="round_start",
448
+ round_num=round_num,
449
+ phase=phase,
450
+ agent_binary=_agent_binary,
451
+ )
445
452
 
446
453
  prompt = _api_assemble_prompt(cfg, phase=phase, context=enriched_ctx)
447
454
 
@@ -564,7 +571,13 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
564
571
  phase_index=phase_idx,
565
572
  ),
566
573
  )
567
- metrics.log_metrics(log_dir, event="round_end", round_num=round_num, phase=phase)
574
+ metrics.log_metrics(
575
+ log_dir,
576
+ event="round_end",
577
+ round_num=round_num,
578
+ phase=phase,
579
+ agent_binary=_agent_binary,
580
+ )
568
581
  events.emit(log_dir, "round_end", round_num=round_num)
569
582
 
570
583
  round_result = RoundResult(
@@ -20,6 +20,7 @@ are shared between `peek`, `watch`, and `monitor`.
20
20
  | `status` | Show current service state |
21
21
  | `peek` | peek project state with optional drill-down |
22
22
  | `watch` | watch project state with optional drill-down |
23
+ | `events` | Query / stream events from events.jsonl by kind |
23
24
  | `monitor` | Anomaly detection, narrate/events stream, or HTTP progress page |
24
25
  | `serve` | Long-running supervisor loop |
25
26
  | `round` | Run one round and exit |
@@ -87,12 +88,29 @@ agent-runner peek
87
88
  agent-runner peek --json
88
89
  agent-runner peek --select system.disk_used_pct
89
90
  agent-runner peek --select defenses
90
- agent-runner peek --select events.agent_usage_recorded --window 5 # 0.1.32+: native event-kind query
91
- agent-runner peek --select events.transient_error_detected --window 20
92
91
  agent-runner peek --round 42 --log # drill into round 42, include log tail
93
92
  agent-runner peek --events 50 # last 50 events
94
93
  ```
95
94
 
95
+ ### `agent-runner events --kind K[,K2,...] [--window N] [--tail]`
96
+
97
+ Query or stream events.jsonl by kind. Output is always JSON Lines (one event
98
+ JSON per line). Current-month events.jsonl scope only.
99
+
100
+ ```bash
101
+ # One-shot: last 5 usage records
102
+ agent-runner events --kind agent_usage_recorded --window 5
103
+
104
+ # Multi-kind OR filter
105
+ agent-runner events --kind round_end,hook_failed --window 20
106
+
107
+ # Streaming: emit each new matching event as it fires; blocks until SIGINT
108
+ agent-runner events --kind transient_error_backoff_capped --tail
109
+ ```
110
+
111
+ `--window N` and `--tail` are mutually exclusive. Exit codes: 0 normal,
112
+ 2 invalid arguments, 1 unreadable events file.
113
+
96
114
  ### `agent-runner watch [--interval N] [peek-flags]`
97
115
 
98
116
  `peek` in a clear-and-refresh loop. Default 2s interval. Stop with Ctrl-C.
@@ -115,7 +133,7 @@ agent-runner monitor --json | jq -c # pipe alerts to a downstream consume
115
133
 
116
134
  ## 中文摘要
117
135
 
118
- 13 个动词:`init / install / uninstall / start / stop / kill / cancel / restart / status / round / serve / peek / watch / monitor`。
136
+ 16 个动词:`init / install / uninstall / start / stop / kill / cancel / restart / status / round / serve / upgrade / peek / watch / events / monitor`。
119
137
 
120
138
  观察类(peek/watch/monitor)三视角对称,全部共用 `--round / --log / --events / --select / --json` 下钻参数。
121
139