cli-agent-runner 0.2.3__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.
Files changed (274) hide show
  1. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/workflows/release.yml +7 -3
  2. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.vulture-whitelist.py +0 -4
  3. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CHANGELOG.md +34 -3
  4. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/PKG-INFO +22 -11
  5. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/README.md +20 -9
  6. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_emit.py +37 -1
  7. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_version.py +2 -2
  8. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/agent_runtime.py +34 -7
  9. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/api.py +63 -66
  10. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/api_types.py +9 -0
  11. cli_agent_runner-0.2.5/agent_runner/builtin_plugins/_constants.py +125 -0
  12. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/claude_rate_limit.py +11 -27
  13. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/codewhale.py +16 -36
  14. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/gemini.py +6 -28
  15. cli_agent_runner-0.2.5/agent_runner/builtin_plugins/kimi.py +93 -0
  16. cli_agent_runner-0.2.5/agent_runner/builtin_plugins/pi.py +279 -0
  17. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/events_cmd.py +126 -3
  18. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/monitor_cmd.py +61 -10
  19. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/config.py +10 -3
  20. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/events.py +1 -0
  21. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/hooks.py +5 -5
  22. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/monitor.py +98 -102
  23. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/pi.toml +2 -3
  24. cli_agent_runner-0.2.5/agent_runner/remote_relay.py +270 -0
  25. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/round_log.py +30 -0
  26. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/runner.py +6 -2
  27. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/service_unit.py +7 -1
  28. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/startup_check.py +19 -20
  29. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/architecture.md +33 -13
  30. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/commands.md +56 -11
  31. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/configuration.md +7 -0
  32. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/events.md +3 -3
  33. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.28.md +2 -2
  34. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.30.md +3 -3
  35. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.35.md +1 -0
  36. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.0.md +2 -3
  37. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.3.md +5 -8
  38. cli_agent_runner-0.2.5/docs/migrations/0.2.4.md +66 -0
  39. cli_agent_runner-0.2.5/docs/migrations/0.2.5.md +99 -0
  40. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/plugins.md +106 -20
  41. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/aider.md +5 -3
  42. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/pi.md +14 -7
  43. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/runbook.md +141 -28
  44. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/thesis.md +4 -2
  45. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/pyproject.toml +5 -2
  46. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/_test_helpers.py +26 -5
  47. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_monitor_seeded.py +2 -2
  48. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_run_one_round_with_fake_agent.py +2 -0
  49. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_claims_match_ssot.py +2 -1
  50. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_module_boundaries.py +1 -0
  51. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime.py +21 -6
  52. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_observation.py +34 -172
  53. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_service.py +2 -2
  54. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_auto_stop_gating.py +1 -5
  55. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_claude_error_detector.py +31 -30
  56. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_service_peek_monitor.py +89 -39
  57. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_codewhale_plugin.py +14 -12
  58. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config.py +59 -0
  59. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_events.py +39 -0
  60. cli_agent_runner-0.2.5/tests/unit/test_events_cmd.py +343 -0
  61. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_events_cmd_tail.py +39 -2
  62. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_gemini_plugin.py +12 -12
  63. cli_agent_runner-0.2.5/tests/unit/test_kimi_plugin.py +147 -0
  64. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detectors.py +57 -0
  65. cli_agent_runner-0.2.5/tests/unit/test_monitor_remote.py +320 -0
  66. cli_agent_runner-0.2.5/tests/unit/test_pi_plugin.py +449 -0
  67. cli_agent_runner-0.2.5/tests/unit/test_plugin_constants.py +61 -0
  68. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_presets.py +1 -6
  69. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_round_log_helpers.py +60 -0
  70. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_runner.py +0 -39
  71. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_service_unit.py +9 -0
  72. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_startup_check.py +1 -1
  73. cli_agent_runner-0.2.3/agent_runner/builtin_plugins/_constants.py +0 -61
  74. cli_agent_runner-0.2.3/tests/unit/test_events_cmd.py +0 -179
  75. cli_agent_runner-0.2.3/tests/unit/test_monitor_remote.py +0 -167
  76. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.codecov.yml +0 -0
  77. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  78. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  79. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  80. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  81. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.github/workflows/ci.yml +0 -0
  82. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/.gitignore +0 -0
  83. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/BACKLOG.md +0 -0
  84. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CODE_OF_CONDUCT.md +0 -0
  85. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/CONTRIBUTING.md +0 -0
  86. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/LICENSE +0 -0
  87. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/README.zh.md +0 -0
  88. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/SECURITY.md +0 -0
  89. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/__init__.py +0 -0
  90. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_docgen.py +0 -0
  91. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_redact.py +0 -0
  92. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_registry.py +0 -0
  93. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_substrate.py +0 -0
  94. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/_throttle.py +0 -0
  95. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/__init__.py +0 -0
  96. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  97. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/__init__.py +0 -0
  98. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/__main__.py +0 -0
  99. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/common.py +0 -0
  100. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/init_cmd.py +0 -0
  101. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/install_cmd.py +0 -0
  102. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/peek_cmd.py +0 -0
  103. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/round_cmd.py +0 -0
  104. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/serve_cmd.py +0 -0
  105. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/service_cmd.py +0 -0
  106. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/cli/upgrade_cmd.py +0 -0
  107. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/context_store.py +0 -0
  108. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/defenses.py +0 -0
  109. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/http_progress.py +0 -0
  110. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/lifecycle.py +0 -0
  111. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/metrics.py +0 -0
  112. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/__init__.py +0 -0
  113. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/aider.toml +0 -0
  114. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/claude.toml +0 -0
  115. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/codewhale.toml +0 -0
  116. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/gemini.toml +0 -0
  117. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/presets/kimi.toml +0 -0
  118. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/prompt_loader.py +0 -0
  119. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/round_view.py +0 -0
  120. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/scaffold.py +0 -0
  121. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/agent_runner/vcs_state.py +0 -0
  122. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/build.sh +0 -0
  123. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/README.md +0 -0
  124. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/long-running-agents.md +0 -0
  125. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.16.md +0 -0
  126. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.17.md +0 -0
  127. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.19.md +0 -0
  128. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.20.md +0 -0
  129. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.21.md +0 -0
  130. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.22.md +0 -0
  131. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.23.md +0 -0
  132. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.24.md +0 -0
  133. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.25.md +0 -0
  134. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.26.md +0 -0
  135. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.27.md +0 -0
  136. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.29.md +0 -0
  137. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.31.md +0 -0
  138. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.32.md +0 -0
  139. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.33.md +0 -0
  140. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.34.md +0 -0
  141. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.36.md +0 -0
  142. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.37.md +0 -0
  143. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.38.md +0 -0
  144. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.39.md +0 -0
  145. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.40.md +0 -0
  146. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.1.42.md +0 -0
  147. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.1.md +0 -0
  148. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/migrations/0.2.2.md +0 -0
  149. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/quickstart.md +0 -0
  150. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/codewhale.md +0 -0
  151. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/docs/recipes/kimi.md +0 -0
  152. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/__init__.py +0 -0
  153. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/conftest.py +0 -0
  154. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/contract/__init__.py +0 -0
  155. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/contract/test_public_api_surface.py +0 -0
  156. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/__init__.py +0 -0
  157. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/conftest.py +0 -0
  158. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  159. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_install_systemd.py +0 -0
  160. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  161. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  162. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  163. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  164. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  165. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/generate_vulture_whitelist.py +0 -0
  166. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/__init__.py +0 -0
  167. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_bounded_run.py +0 -0
  168. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_context_enricher_namespacing.py +0 -0
  169. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_dirty_handler_seam.py +0 -0
  170. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_fresh_eyes_signal.py +0 -0
  171. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_grace_kill_emission.py +0 -0
  172. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_install_dry_run.py +0 -0
  173. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_detector_loaded.py +0 -0
  174. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_owned_paths.py +0 -0
  175. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_plugin_real_flow.py +0 -0
  176. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  177. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_scaffold_presets.py +0 -0
  178. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_serve_loop.py +0 -0
  179. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_substrate_fingerprint.py +0 -0
  180. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/integration/test_transient_error_backoff.py +0 -0
  181. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/__init__.py +0 -0
  182. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/_event_scan.py +0 -0
  183. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_architecture.py +0 -0
  184. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_atomic_write_enforced.py +0 -0
  185. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  186. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_catalogs.py +0 -0
  187. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_classification_ssot.py +0 -0
  188. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_config_error_consistency.py +0 -0
  189. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_config_value_set_ssot.py +0 -0
  190. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  191. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_cli_claims.py +0 -0
  192. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_operator_surface.py +0 -0
  193. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  194. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_generated.py +0 -0
  195. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_docs_index_complete.py +0 -0
  196. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_entry_points_resolve.py +0 -0
  197. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kind_registry.py +0 -0
  198. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_event_kinds_ssot.py +0 -0
  199. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_events_doc_contract.py +0 -0
  200. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_hook_contract_docs.py +0 -0
  201. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_layer_2_loop_size.py +0 -0
  202. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_module_sizes.py +0 -0
  203. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_no_ai_signatures.py +0 -0
  204. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  205. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_peek_schema_version.py +0 -0
  206. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_plugins_config_stable.py +0 -0
  207. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  208. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  209. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_round_result_stable.py +0 -0
  210. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  211. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  212. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_upstream_schema_canary.py +0 -0
  213. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  214. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/__init__.py +0 -0
  215. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/parser.py +0 -0
  216. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/test_parser.py +0 -0
  217. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/literate/test_quickstart.py +0 -0
  218. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/__init__.py +0 -0
  219. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_grace.py +0 -0
  220. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_progress.py +0 -0
  221. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  222. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_assemble_prompt.py +0 -0
  223. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_events_stream.py +0 -0
  224. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_install.py +0 -0
  225. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_read_round_num.py +0 -0
  226. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_resolve_phase.py +0 -0
  227. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_api_types.py +0 -0
  228. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_catalogs.py +0 -0
  229. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli.py +0 -0
  230. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_common.py +0 -0
  231. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_init_install.py +0 -0
  232. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_monitor_http.py +0 -0
  233. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_cli_upgrade.py +0 -0
  234. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_fresh_eyes.py +0 -0
  235. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_max_rounds.py +0 -0
  236. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_prompt_delivery.py +0 -0
  237. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_stop_file.py +0 -0
  238. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  239. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_config_transient_error_action.py +0 -0
  240. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_context_store.py +0 -0
  241. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_default_dirty_handler.py +0 -0
  242. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_defenses.py +0 -0
  243. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_detector_protocol.py +0 -0
  244. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_dirty_handlers.py +0 -0
  245. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_docgen.py +0 -0
  246. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_exit_cause.py +0 -0
  247. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  248. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_hook_failure_isolation.py +0 -0
  249. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_hooks.py +0 -0
  250. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_http_progress.py +0 -0
  251. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_init_entry_points.py +0 -0
  252. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_lifecycle.py +0 -0
  253. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_metrics.py +0 -0
  254. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_assembly.py +0 -0
  255. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  256. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  257. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  258. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_network_blip_signal_guard.py +0 -0
  259. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_peek_argparse.py +0 -0
  260. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_peek_select.py +0 -0
  261. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_prompt_loader.py +0 -0
  262. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_redact.py +0 -0
  263. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_round_view.py +0 -0
  264. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_runner_throttle.py +0 -0
  265. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_scaffold.py +0 -0
  266. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_cmd_bounded.py +0 -0
  267. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_config_broken.py +0 -0
  268. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_crash_loop.py +0 -0
  269. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_round_log.py +0 -0
  270. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_sentinel.py +0 -0
  271. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_serve_startup_hooks.py +0 -0
  272. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_substrate.py +0 -0
  273. {cli_agent_runner-0.2.3 → cli_agent_runner-0.2.5}/tests/unit/test_throttle_plugin_classification.py +0 -0
  274. {cli_agent_runner-0.2.3 → 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
- # Note: TestPyPI stage intentionally omittedfirst release verification
14
- # happens via the manual approval gate on the `pypi` environment.
12
+ # A mismatch fails publish with an opaque OIDC error. Keep the environment
13
+ # declared even when it carries no protection rules it is the OIDC claim,
14
+ # not just an approval gate; deleting it breaks publishing outright. Whether
15
+ # a tag publishes straight through or waits for a reviewer is configured in
16
+ # the repo's environment settings, not here.
17
+ # Note: TestPyPI stage intentionally omitted — the tag is the release signal;
18
+ # pre-tag verification is `./build.sh check` plus the CI matrix on main.
15
19
 
16
20
  concurrency:
17
21
  group: release-${{ github.ref }}
@@ -228,10 +228,6 @@ path
228
228
  # agent_runner.monitor.LocalSource
229
229
  log_dir
230
230
 
231
- # agent_runner.monitor.RemoteSource
232
- host
233
- project
234
-
235
231
  # agent_runner.startup_check.CheckResult
236
232
  name
237
233
  ok
@@ -5,9 +5,35 @@ 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.3] - 2026-07-26
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`.
9
18
 
10
- Details: [docs/migrations/0.2.3.md](docs/migrations/0.2.3.md)
19
+ ## [0.2.4] - 2026-07-27
20
+
21
+ ### Added
22
+ - `kimi_error_detector` plugin — classifies transient errors from Kimi Code CLI rounds (`turn.step.retrying` records) so a rate-limited round backs off instead of hot-restarting. No usage events: the CLI's stream-json output carries no token counters.
23
+ - `agent_auth_error_detected` event — a plugin reports an auth failure named by the agent CLI's own structured output, and the `oauth_fail` detector counts those rounds directly. Makes pi's 401 loop visible (pi exits 0); the text-heuristic path keeps its nonzero-exit gate unchanged.
24
+ - `pi_error_detector` plugin — emits per-round token usage (summed across the round's assistant messages, since pi reports usage per message) and classifies transient errors from Pi Coding Agent rounds. pi exits 0 on provider failure, so the classifier reads the final message's `stopReason`/`errorMessage` rather than the exit code.
25
+
26
+ ### Fixed
27
+ - Round-log tail scanning hardened: plugin JSONL parsers filter non-JSON chatter *before* windowing (a stderr burst of any size can no longer evict the terminal event), monitor detectors share the same 200-line window (was 50 — the oauth/network text scans had the identical eviction risk), and each monitor poll reads only the newest 20 round logs instead of every log ever written.
28
+ - A relative `runtime.work_dir` (and every path derived from it) now resolves against the config file's directory instead of the caller's cwd, so `--config /abs/proj/agent-runner.toml` drives `/abs/proj` no matter where the supervisor was launched from.
29
+ - Remote monitor (`monitor --host <alias>`) was silently observing an empty world and reporting healthy — it listed remote filenames over ssh but read every path locally. It now fails loudly at startup (exit 1, with guidance to run the monitor on the host) until remote reads are implemented.
30
+ - Agent round logs (`{log_dir}/rounds/R*-*.log`) are now pruned under `runtime.round_log_retention` at the start of every round — previously that family grew unboundedly, since only the serve-level `round-<N>.log` family was pruned, and only at serve startup.
31
+ - Startup validation resolves the agent command exactly as the spawn does (child's PATH — `[agent.env]` may override it — and `work_dir` base); `PWD` now stays pinned to `work_dir` even if `[agent.env]` sets it. The stdout+stderr merge is now pinned by a behavioral test, not just prose.
32
+ - Generated serve systemd units now set `KillMode=mixed`: with systemd's default `control-group`, `systemctl stop` SIGTERMed the whole cgroup — agent child included — making the graceful round drain structurally ineffective. Existing installs: re-run `agent-runner install` (or add a drop-in) to pick this up.
33
+
34
+ See `docs/migrations/0.2.4.md`.
35
+
36
+ ## [0.2.3] - 2026-07-26
11
37
 
12
38
  ### Added
13
39
  - `--preset kimi` — a preset for [Kimi Code CLI](https://github.com/MoonshotAI/kimi-code) (`kimi -p --output-format stream-json`). `docs/recipes/kimi.md` also documents running Kimi K3 through the existing `claude` preset via Moonshot's Anthropic-compatible endpoint.
@@ -17,6 +43,8 @@ Details: [docs/migrations/0.2.3.md](docs/migrations/0.2.3.md)
17
43
  - The agent subprocess now runs in `runtime.work_dir` (`cwd=` on spawn). Previously it inherited the supervisor's cwd and only launch conventions (systemd `WorkingDirectory=`, relative `--config`) kept the two aligned — fatal for agent CLIs with no working-directory flag of their own (e.g. `pi`). The startup check now also validates a relative `agent.command[0]` against `work_dir`, matching where it executes.
18
44
  - Plugin round-log tail window widened 50 → 200 lines: a stderr burst after the agent's terminal JSONL event could evict it from the scan window, silently dropping usage/transient classification. The round log's merged stdout+stderr contract is now documented (`HookContext.agent_log_path`, `docs/long-running-agents.md`) — the merge is deliberate; auth/network detection reads stderr text from it.
19
45
 
46
+ See `docs/migrations/0.2.3.md`.
47
+
20
48
  ## [0.2.2] - 2026-07-18
21
49
 
22
50
  ### Removed
@@ -631,6 +659,7 @@ No breaking changes. Plugin authors:
631
659
  between rounds, opt them out:
632
660
  ```python
633
661
  from agent_runner.vcs_state import register_plugin_owned_paths
662
+
634
663
  register_plugin_owned_paths(["your-output-dir/", "logs/your-plugin/**/*"])
635
664
  ```
636
665
  - If you followed the old `_register()` pattern from docs and noticed
@@ -921,7 +950,9 @@ Initial public release on PyPI as `cli-agent-runner`.
921
950
  - Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
922
951
  gated by a manual approval on the `pypi` GitHub environment.
923
952
 
924
- [Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...HEAD
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
955
+ [0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
925
956
  [0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
926
957
  [0.2.2]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.1...v0.2.2
927
958
  [0.2.1]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.0...v0.2.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-agent-runner
3
- Version: 0.2.3
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>=0.5; extra == 'dev'
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'
@@ -116,20 +116,31 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
116
116
  - `oauth_fail` — burning API quota on auth-rejected rounds
117
117
  - `disk_critical` — writing to a near-full disk risks corruption
118
118
 
119
- Runs locally or against a remote host via ssh:
119
+ Runs against the supervised project's local logs:
120
120
 
121
121
  ```bash
122
- agent-runner monitor # local, 30s poll
123
- agent-runner monitor --host pi # remote, 60s poll
122
+ agent-runner monitor # 30s poll
124
123
  agent-runner monitor --json | jq -c # pipe to downstream consumers
125
124
  ```
126
125
 
127
- > **SSH trust boundary**: `monitor --host <alias>` shells out via plain SSH
128
- > using your local `~/.ssh/config` (aliases, `StrictHostKeyChecking` behavior).
129
- > A monitor with `auto_stop` enabled can issue `agent-runner stop` on the
130
- > remote — verify your SSH config before pointing monitor at a remote service.
131
- > See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH trust" for
132
- > recommended hygiene.
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
 
@@ -80,20 +80,31 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
80
80
  - `oauth_fail` — burning API quota on auth-rejected rounds
81
81
  - `disk_critical` — writing to a near-full disk risks corruption
82
82
 
83
- Runs locally or against a remote host via ssh:
83
+ Runs against the supervised project's local logs:
84
84
 
85
85
  ```bash
86
- agent-runner monitor # local, 30s poll
87
- agent-runner monitor --host pi # remote, 60s poll
86
+ agent-runner monitor # 30s poll
88
87
  agent-runner monitor --json | jq -c # pipe to downstream consumers
89
88
  ```
90
89
 
91
- > **SSH trust boundary**: `monitor --host <alias>` shells out via plain SSH
92
- > using your local `~/.ssh/config` (aliases, `StrictHostKeyChecking` behavior).
93
- > A monitor with `auto_stop` enabled can issue `agent-runner stop` on the
94
- > remote — verify your SSH config before pointing monitor at a remote service.
95
- > See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH trust" for
96
- > recommended hygiene.
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
 
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  from pathlib import Path
15
15
 
16
16
  __all__ = [
17
+ "emit_agent_auth_error_detected",
17
18
  "emit_agent_usage_recorded",
18
19
  "emit_anomaly_repetitive_tool",
19
20
  "emit_fresh_eyes_round_triggered",
@@ -150,6 +151,38 @@ def emit_transient_error_detected(
150
151
  )
151
152
 
152
153
 
154
+ def emit_agent_auth_error_detected(
155
+ log_dir: Path,
156
+ *,
157
+ round_num: int,
158
+ agent: str,
159
+ raw: str,
160
+ ) -> None:
161
+ """Emit an authentication/authorization failure the agent itself reported.
162
+
163
+ Contract: emitted by a per-CLI plugin only when the agent's OWN structured
164
+ output names the failure (e.g. an HTTP 401 in its JSON event stream). That
165
+ is certain evidence, unlike the monitor's ``oauth_fail`` text heuristic,
166
+ which scans free-text log tails and therefore needs a nonzero-exit shield
167
+ against prose that merely mentions "401". The monitor counts a round
168
+ carrying this event without that shield — which is what makes an auth loop
169
+ visible for a CLI that exits 0 on provider failure.
170
+
171
+ No back-off partner event: an auth failure is permanent until an operator
172
+ fixes the credential, so it is deliberately not a transient classification.
173
+ """
174
+ from agent_runner._redact import redact_secrets
175
+ from agent_runner.events import AGENT_AUTH_ERROR_DETECTED, emit
176
+
177
+ emit(
178
+ log_dir,
179
+ AGENT_AUTH_ERROR_DETECTED,
180
+ round_num=round_num,
181
+ agent=agent,
182
+ raw=redact_secrets(raw),
183
+ )
184
+
185
+
153
186
  def emit_transient_error_recovered(
154
187
  log_dir: Path,
155
188
  *,
@@ -205,7 +238,10 @@ def emit_agent_usage_recorded(
205
238
  Claude: count of ``tool_use`` content blocks across all assistant events.
206
239
  Gemini: ``stats.tool_calls``.
207
240
  - ``phase``: phase label from HookContext (e.g. "planning"); empty string when None.
208
- - ``success``: True when exit_code == 0 and not timed_out.
241
+ - ``success``: the supervisor's clean-exit predicate (``RoundResult.ok``).
242
+ A plugin MAY additionally fold in the agent's own terminal verdict when
243
+ its CLI's exit code is unreliable — pi does, because pi exits 0 on
244
+ provider failure.
209
245
  """
210
246
  from agent_runner.events import AGENT_USAGE_RECORDED, emit
211
247
 
@@ -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.3'
22
- __version_tuple__ = version_tuple = (0, 2, 3)
21
+ __version__ = version = '0.2.5'
22
+ __version_tuple__ = version_tuple = (0, 2, 5)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import os
13
13
  import re
14
+ import shutil
14
15
  import signal
15
16
  import subprocess # noqa: TID251 — sanctioned subprocess caller
16
17
  import threading
@@ -52,6 +53,15 @@ class RunResult:
52
53
  killed_for_grace: bool = False
53
54
  grace_kill_children: list[dict] = field(default_factory=list)
54
55
 
56
+ @property
57
+ def ok(self) -> bool:
58
+ """The supervisor's round-success predicate: clean exit, no timeout.
59
+
60
+ Single definition of "did this round fail" at the supervisor level. A
61
+ plugin MAY narrow it further when its CLI's exit code is unreliable.
62
+ """
63
+ return self.exit_code == 0 and not self.timed_out
64
+
55
65
 
56
66
  def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
57
67
  """Build full argv: command + prompt args (with {prompt} substituted)."""
@@ -130,6 +140,24 @@ def _live_children(
130
140
  _RESULT_MARKER = b'"type":"result"'
131
141
 
132
142
 
143
+ def resolve_exec_target(command0: str, work_dir: Path, env_path: str | None = None) -> str | None:
144
+ """Model of Popen's POSIX exec resolution for argv[0].
145
+
146
+ Kept beside the ``Popen`` in :func:`run` so validation and exec cannot
147
+ drift: a slash-containing argv[0] resolves against the child's cwd
148
+ (``work_dir``); a bare name is looked up on the CHILD's PATH
149
+ (``env_path`` — pass ``[agent.env]``'s PATH override if set; ``None``
150
+ falls back to the supervisor's, matching env inheritance). Returns the
151
+ resolved executable path, or ``None`` if it would not exec.
152
+ """
153
+ if "/" in command0:
154
+ candidate = Path(command0)
155
+ if not candidate.is_absolute():
156
+ candidate = work_dir / candidate
157
+ return shutil.which(str(candidate))
158
+ return shutil.which(command0, path=env_path)
159
+
160
+
133
161
  def run(
134
162
  *,
135
163
  command: list[str],
@@ -150,11 +178,9 @@ def run(
150
178
 
151
179
  Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → REAP_GRACE_S → SIGKILL.
152
180
 
153
- work_dir: the agent child's working directory (required). Before 0.2.3 the
154
- child inherited the supervisor's cwd and only launch conventions (systemd
155
- WorkingDirectory=, relative --config) kept the two aligned — fatal for CLIs
156
- with no --cwd flag of their own (e.g. pi). Callers pass the already-absolute
157
- cfg.runtime.work_dir.
181
+ work_dir: the agent child's working directory; callers pass the
182
+ already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
183
+ own (e.g. pi) depend on this.
158
184
 
159
185
  max_grace_after_result_s: when > 0, start a countdown after the first
160
186
  type=result event is detected in the log. After it elapses, reap the
@@ -182,8 +208,9 @@ def run(
182
208
  if stdin_mode
183
209
  else _build_argv(command, prompt_arg_template, prompt)
184
210
  )
185
- # PWD pinned alongside cwd= so shell-reported paths agree; env_extra wins.
186
- env = {**os.environ, "PWD": str(work_dir), **env_extra}
211
+ # PWD pinned last — it mirrors cwd= (a correctness pin, not a knob), so
212
+ # an [agent.env] PWD cannot silently diverge from where the child runs.
213
+ env = {**os.environ, **env_extra, "PWD": str(work_dir)}
187
214
  log_path.parent.mkdir(parents=True, exist_ok=True)
188
215
  log_file = log_path.open("w", encoding="utf-8")
189
216
  start = time.time()
@@ -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,14 +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, *, host: str | None) -> list[monitor.Alert]:
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
- src: monitor.StateSource
486
- if host is None:
487
- src = monitor.LocalSource(log_dir=cfg.runtime.log_dir)
488
- else:
489
- 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)
490
487
  events = monitor.parse_events_from_jsonl_files(src.events_files())
491
488
  metrics = monitor.parse_events_from_jsonl_files(src.metrics_files())
492
489
  log_tails = monitor.load_round_log_tails(src.rounds_dir())
@@ -510,11 +507,6 @@ def _poll_once(project: str | Path, *, host: str | None) -> list[monitor.Alert]:
510
507
  return builtin + plugin
511
508
 
512
509
 
513
- # Backoff schedule for remote-failure retries: each element is the sleep duration
514
- # in seconds for that attempt index (capped at 30s per step).
515
- _REMOTE_FAILURE_BACKOFF = (1, 2, 4, 8, 16, 30)
516
-
517
-
518
510
  def monitor_loop(
519
511
  project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
520
512
  ) -> Iterator[monitor.Alert]:
@@ -525,12 +517,25 @@ def monitor_loop(
525
517
  to that kind as the canonical "supervision is up" signal (monitor is otherwise
526
518
  silent during healthy operation by design).
527
519
 
528
- Tolerates transient ``MonitorRemoteError`` failures (from ``--host`` ssh)
529
- for up to ``cfg.monitor.remote_failure_tolerance_s`` seconds with exponential
530
- backoff (1s 2s 4s ... 30s cap). Each retry emits ``monitor_remote_blip``;
531
- crossing the cap emits one ``monitor_remote_giveup`` and propagates the error
532
- (CLI exits 1; systemd restarts the process). Setting tolerance to 0 preserves
533
- the 0.1.10 immediate-propagate behavior with no blip events emitted.
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``.
525
+ """
526
+ if host is not None:
527
+ raise monitor.MonitorRemoteUnsupportedError(host)
528
+ return _monitor_loop_iter(project, host=host, interval_s=interval_s)
529
+
530
+
531
+ def _monitor_loop_iter(
532
+ project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
533
+ ) -> Iterator[monitor.Alert]:
534
+ """Polling generator behind ``monitor_loop``.
535
+
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.
534
539
  """
535
540
  import json as _json
536
541
 
@@ -547,51 +552,8 @@ def monitor_loop(
547
552
  mode="anomaly-only",
548
553
  )
549
554
 
550
- tolerance_s = cfg.monitor.remote_failure_tolerance_s
551
- blip_start: float | None = None
552
- attempt = 0
553
-
554
555
  while True:
555
- try:
556
- alerts = _poll_once(work_dir, host=host)
557
- # Success: reset retry state.
558
- blip_start = None
559
- attempt = 0
560
- except monitor.MonitorRemoteError as e:
561
- if tolerance_s == 0:
562
- raise # 0.1.10 behavior preserved
563
- now = time.monotonic()
564
- if blip_start is None:
565
- blip_start = now
566
- attempt += 1
567
- elapsed = now - blip_start
568
- if elapsed >= tolerance_s:
569
- events.emit(
570
- cfg.runtime.log_dir,
571
- MONITOR_REMOTE_GIVEUP,
572
- host=host,
573
- total_attempts=attempt,
574
- total_elapsed_s=elapsed,
575
- cap_s=tolerance_s,
576
- final_error=e.stderr,
577
- )
578
- raise
579
- backoff_idx = min(attempt - 1, len(_REMOTE_FAILURE_BACKOFF) - 1)
580
- next_sleep_s = min(_REMOTE_FAILURE_BACKOFF[backoff_idx], tolerance_s - elapsed)
581
- events.emit(
582
- cfg.runtime.log_dir,
583
- MONITOR_REMOTE_BLIP,
584
- host=host,
585
- error=e.stderr,
586
- attempt=attempt,
587
- elapsed_s=elapsed,
588
- cap_s=tolerance_s,
589
- interval_s=interval_s,
590
- next_sleep_s=next_sleep_s,
591
- )
592
- time.sleep(next_sleep_s)
593
- continue
594
-
556
+ alerts = _poll_once(work_dir)
595
557
  for alert in alerts:
596
558
  key = f"{alert.detector}:{_json.dumps(alert.context, sort_keys=True)}"
597
559
  if key in seen:
@@ -601,7 +563,6 @@ def monitor_loop(
601
563
  monitor.on_alert(
602
564
  alert,
603
565
  project=_project_name(work_dir),
604
- host=host,
605
566
  log_dir=cfg.runtime.log_dir,
606
567
  allowed_stop_names=cfg.monitor.auto_stop_on,
607
568
  )
@@ -771,6 +732,7 @@ def check_self_terminated_sentinel(log_dir: Path) -> bool:
771
732
  # Re-export emit_* wrappers from _emit module (extracted for size hygiene).
772
733
  # Preserves the public import surface: `from agent_runner.api import emit_*` continues to work.
773
734
  from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-export
735
+ emit_agent_auth_error_detected,
774
736
  emit_agent_usage_recorded,
775
737
  emit_anomaly_repetitive_tool,
776
738
  emit_config_broken,
@@ -829,6 +791,41 @@ def stream_events_jsonl(log_dir: Path, *, poll_interval_s: float = 0.1) -> Itera
829
791
  yield from _tail_events_jsonl(log_dir, start_at_now=True, poll_interval_s=poll_interval_s)
830
792
 
831
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
+
832
829
  def _format_narrate_line(evt: dict[str, Any]) -> str:
833
830
  """Format an event dict as a one-line human-readable string.
834
831
 
@@ -173,6 +173,15 @@ class RoundResult:
173
173
  dirty_outcome: DirtyOutcome | None = None
174
174
  killed_for_grace: bool = False
175
175
 
176
+ @property
177
+ def ok(self) -> bool:
178
+ """The supervisor's round-success predicate: clean exit, no timeout.
179
+
180
+ Single definition of "did this round fail" at the supervisor level. A
181
+ plugin MAY narrow it further when its CLI's exit code is unreliable.
182
+ """
183
+ return self.exit_code == 0 and not self.timed_out
184
+
176
185
 
177
186
  @dataclass(frozen=True)
178
187
  class InitResult: