cli-agent-runner 0.2.11__tar.gz → 0.2.13__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 (331) hide show
  1. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.vulture-whitelist.py +7 -1
  2. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/CHANGELOG.md +37 -0
  3. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_docgen.py +9 -4
  5. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_emit.py +31 -4
  6. cli_agent_runner-0.2.13/agent_runner/_resolve.py +120 -0
  7. cli_agent_runner-0.2.13/agent_runner/_serve_policy.py +189 -0
  8. cli_agent_runner-0.2.13/agent_runner/_throttle.py +623 -0
  9. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_version.py +2 -2
  10. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/agent_runtime.py +87 -50
  11. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/api.py +281 -155
  12. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/api_types.py +18 -6
  13. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/_constants.py +9 -4
  14. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/claude_rate_limit.py +68 -19
  15. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/codewhale.py +9 -9
  16. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/gemini.py +53 -20
  17. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/pi.py +3 -1
  18. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/__init__.py +24 -1
  19. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/common.py +29 -5
  20. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/events_cmd.py +24 -29
  21. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/install_cmd.py +15 -5
  22. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/migrate_cmd.py +3 -1
  23. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/monitor_cmd.py +3 -1
  24. cli_agent_runner-0.2.13/agent_runner/cli/round_cmd.py +86 -0
  25. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/serve_cmd.py +175 -36
  26. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/service_cmd.py +6 -5
  27. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/upgrade_cmd.py +83 -9
  28. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/clock.py +20 -0
  29. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/config.py +218 -34
  30. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/context_store.py +25 -7
  31. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/events.py +54 -1
  32. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/hooks.py +26 -15
  33. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/http_progress.py +14 -6
  34. cli_agent_runner-0.2.13/agent_runner/lifecycle.py +192 -0
  35. cli_agent_runner-0.2.13/agent_runner/migrations.py +629 -0
  36. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/monitor.py +241 -98
  37. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/phase_select.py +8 -1
  38. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/round_log.py +54 -6
  39. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/round_view.py +3 -1
  40. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/runner.py +85 -17
  41. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/scaffold.py +7 -3
  42. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/service_unit.py +53 -19
  43. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/startup_check.py +50 -9
  44. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/vcs_state.py +92 -15
  45. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/architecture.md +5 -0
  46. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/commands.md +36 -1
  47. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/configuration.md +4 -3
  48. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/migrations/0.2.md +98 -0
  49. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/plugins.md +8 -5
  50. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/quickstart.md +7 -8
  51. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/runbook.md +14 -9
  52. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/pyproject.toml +2 -1
  53. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/_test_helpers.py +27 -1
  54. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/contract/test_public_api_surface.py +137 -0
  55. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/conftest.py +1 -1
  56. cli_agent_runner-0.2.13/tests/integration/test_auto_stop_e2e.py +188 -0
  57. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_fresh_eyes_signal.py +10 -12
  58. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_plugin_real_flow.py +1 -1
  59. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_serve_loop.py +1 -1
  60. cli_agent_runner-0.2.13/tests/integration/test_spawn_round_wedged.py +92 -0
  61. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/_event_scan.py +64 -16
  62. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_architecture.py +10 -5
  63. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_authored_facts_declared.py +3 -0
  64. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_catalogs.py +3 -0
  65. cli_agent_runner-0.2.13/tests/invariants/test_config_error_consistency.py +135 -0
  66. cli_agent_runner-0.2.13/tests/invariants/test_dataclass_fields_have_producers.py +210 -0
  67. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_cli_claims.py +13 -3
  68. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_docs_index_complete.py +3 -1
  69. cli_agent_runner-0.2.13/tests/invariants/test_event_int_coercion.py +56 -0
  70. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_event_kind_registry.py +3 -0
  71. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_event_kinds_ssot.py +4 -1
  72. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_hook_contract_docs.py +3 -1
  73. cli_agent_runner-0.2.13/tests/invariants/test_migrations_registry_ssot.py +22 -0
  74. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_module_boundaries.py +1 -0
  75. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_module_sizes.py +3 -0
  76. cli_agent_runner-0.2.13/tests/invariants/test_monitor_self_exclusion.py +22 -0
  77. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_no_ai_signatures.py +9 -0
  78. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +3 -0
  79. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_no_raw_time.py +28 -1
  80. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_no_vacuous_invariants.py +18 -0
  81. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_repo_constants_patched_in_tests.py +3 -0
  82. cli_agent_runner-0.2.13/tests/invariants/test_round_log_decode_boundary.py +45 -0
  83. cli_agent_runner-0.2.13/tests/invariants/test_round_num_single_source.py +43 -0
  84. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_set_diff_for_auto_tool_classification.py +3 -1
  85. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_stash_uses_sha_not_index.py +25 -1
  86. cli_agent_runner-0.2.13/tests/invariants/test_timeout_budget_invariant.py +47 -0
  87. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/literate/parser.py +6 -3
  88. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/literate/test_parser.py +8 -0
  89. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/literate/test_quickstart.py +10 -0
  90. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_agent_runtime.py +27 -11
  91. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_agent_runtime_grace.py +57 -5
  92. cli_agent_runner-0.2.13/tests/unit/test_agent_runtime_marker_scan.py +84 -0
  93. cli_agent_runner-0.2.13/tests/unit/test_agent_runtime_reap.py +114 -0
  94. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_install.py +75 -0
  95. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_observation.py +42 -0
  96. cli_agent_runner-0.2.13/tests/unit/test_api_service.py +583 -0
  97. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_types.py +25 -0
  98. cli_agent_runner-0.2.13/tests/unit/test_auto_stop_gating.py +157 -0
  99. cli_agent_runner-0.2.13/tests/unit/test_claude_foreign_schema.py +133 -0
  100. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli.py +1 -1
  101. cli_agent_runner-0.2.13/tests/unit/test_cli_main_boundary.py +43 -0
  102. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli_service_peek_monitor.py +29 -0
  103. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli_upgrade.py +220 -60
  104. cli_agent_runner-0.2.13/tests/unit/test_codewhale_code_precedence.py +17 -0
  105. cli_agent_runner-0.2.13/tests/unit/test_coerce_int_poison.py +94 -0
  106. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config.py +187 -0
  107. cli_agent_runner-0.2.13/tests/unit/test_config_strictness.py +96 -0
  108. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_context_store.py +11 -0
  109. cli_agent_runner-0.2.13/tests/unit/test_control_char_config.py +129 -0
  110. cli_agent_runner-0.2.13/tests/unit/test_detect_hung_latch.py +69 -0
  111. cli_agent_runner-0.2.13/tests/unit/test_detector_isolation.py +25 -0
  112. cli_agent_runner-0.2.13/tests/unit/test_effective_throttle_view.py +68 -0
  113. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_events_cmd.py +47 -0
  114. cli_agent_runner-0.2.13/tests/unit/test_events_cmd_rollover.py +42 -0
  115. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_events_cmd_tail.py +38 -1
  116. cli_agent_runner-0.2.13/tests/unit/test_events_jsonl_decode.py +126 -0
  117. cli_agent_runner-0.2.13/tests/unit/test_exit_classifier.py +89 -0
  118. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_gemini_plugin.py +44 -0
  119. cli_agent_runner-0.2.13/tests/unit/test_git_timeout.py +139 -0
  120. cli_agent_runner-0.2.13/tests/unit/test_hook_redact_order.py +33 -0
  121. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_http_progress.py +48 -0
  122. cli_agent_runner-0.2.13/tests/unit/test_interruptible_sleep_should_stop.py +47 -0
  123. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_lifecycle.py +30 -0
  124. cli_agent_runner-0.2.13/tests/unit/test_migrate_cmd.py +186 -0
  125. cli_agent_runner-0.2.13/tests/unit/test_migrations.py +359 -0
  126. cli_agent_runner-0.2.13/tests/unit/test_monitor_cmd_severity.py +19 -0
  127. cli_agent_runner-0.2.13/tests/unit/test_monitor_dedup_identity.py +125 -0
  128. cli_agent_runner-0.2.13/tests/unit/test_monitor_dedup_rearm.py +42 -0
  129. cli_agent_runner-0.2.13/tests/unit/test_monitor_detect_rate_limit.py +87 -0
  130. cli_agent_runner-0.2.13/tests/unit/test_monitor_event_tail.py +20 -0
  131. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_remote.py +59 -0
  132. cli_agent_runner-0.2.13/tests/unit/test_open_round_log.py +24 -0
  133. cli_agent_runner-0.2.13/tests/unit/test_orphan_chain_single_pass.py +26 -0
  134. cli_agent_runner-0.2.13/tests/unit/test_outer_round_ceiling.py +43 -0
  135. cli_agent_runner-0.2.13/tests/unit/test_peek_project_isolation.py +38 -0
  136. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_phase_select.py +19 -0
  137. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_plugin_constants.py +20 -0
  138. cli_agent_runner-0.2.13/tests/unit/test_primary_prompt_file_home.py +23 -0
  139. cli_agent_runner-0.2.13/tests/unit/test_read_json_hardening.py +85 -0
  140. cli_agent_runner-0.2.13/tests/unit/test_resolve.py +125 -0
  141. cli_agent_runner-0.2.13/tests/unit/test_round_cmd.py +124 -0
  142. cli_agent_runner-0.2.13/tests/unit/test_round_cmd_sigterm.py +33 -0
  143. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_round_log_helpers.py +88 -0
  144. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_runner.py +61 -0
  145. cli_agent_runner-0.2.13/tests/unit/test_runner_sidecar_ordering.py +34 -0
  146. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_runner_throttle.py +193 -67
  147. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_scaffold.py +29 -0
  148. cli_agent_runner-0.2.13/tests/unit/test_serve_config_broken.py +75 -0
  149. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_crash_loop.py +34 -17
  150. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_phase_schedule.py +7 -8
  151. cli_agent_runner-0.2.13/tests/unit/test_serve_policy.py +29 -0
  152. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_round_log.py +15 -24
  153. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_schedule_gate.py +7 -4
  154. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_sentinel.py +30 -30
  155. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_startup_hooks.py +11 -12
  156. cli_agent_runner-0.2.13/tests/unit/test_service_mode.py +103 -0
  157. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_service_unit.py +88 -14
  158. cli_agent_runner-0.2.13/tests/unit/test_startup_battery_exit.py +92 -0
  159. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_startup_check.py +38 -0
  160. cli_agent_runner-0.2.13/tests/unit/test_supervisor_stale_self_blind.py +31 -0
  161. cli_agent_runner-0.2.13/tests/unit/test_throttle_back_off_ladder.py +95 -0
  162. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_upgrade_migrate.py +26 -0
  163. cli_agent_runner-0.2.11/agent_runner/_throttle.py +0 -372
  164. cli_agent_runner-0.2.11/agent_runner/cli/round_cmd.py +0 -30
  165. cli_agent_runner-0.2.11/agent_runner/lifecycle.py +0 -101
  166. cli_agent_runner-0.2.11/agent_runner/migrations.py +0 -115
  167. cli_agent_runner-0.2.11/tests/invariants/test_config_error_consistency.py +0 -74
  168. cli_agent_runner-0.2.11/tests/unit/test_api_service.py +0 -214
  169. cli_agent_runner-0.2.11/tests/unit/test_auto_stop_gating.py +0 -87
  170. cli_agent_runner-0.2.11/tests/unit/test_migrate_cmd.py +0 -85
  171. cli_agent_runner-0.2.11/tests/unit/test_migrations.py +0 -83
  172. cli_agent_runner-0.2.11/tests/unit/test_monitor_detect_rate_limit.py +0 -47
  173. cli_agent_runner-0.2.11/tests/unit/test_round_cmd.py +0 -52
  174. cli_agent_runner-0.2.11/tests/unit/test_serve_config_broken.py +0 -33
  175. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.codecov.yml +0 -0
  176. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  177. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  178. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  179. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  180. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/workflows/ci.yml +0 -0
  181. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.github/workflows/release.yml +0 -0
  182. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/.gitignore +0 -0
  183. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/BACKLOG.md +0 -0
  184. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/CODE_OF_CONDUCT.md +0 -0
  185. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/CONTRIBUTING.md +0 -0
  186. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/LICENSE +0 -0
  187. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/README.md +0 -0
  188. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/README.zh.md +0 -0
  189. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/SECURITY.md +0 -0
  190. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/__init__.py +0 -0
  191. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_redact.py +0 -0
  192. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_registry.py +0 -0
  193. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/_substrate.py +0 -0
  194. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/__init__.py +0 -0
  195. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  196. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/builtin_plugins/kimi.py +0 -0
  197. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/__main__.py +0 -0
  198. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/init_cmd.py +0 -0
  199. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/cli/peek_cmd.py +0 -0
  200. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/defenses.py +0 -0
  201. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/metrics.py +0 -0
  202. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/__init__.py +0 -0
  203. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/aider.toml +0 -0
  204. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/claude.toml +0 -0
  205. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/codewhale.toml +0 -0
  206. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/gemini.toml +0 -0
  207. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/kimi.toml +0 -0
  208. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/presets/pi.toml +0 -0
  209. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/prompt_loader.py +0 -0
  210. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/remote_relay.py +0 -0
  211. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/agent_runner/schedule.py +0 -0
  212. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/build.sh +0 -0
  213. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/README.md +0 -0
  214. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/events.md +0 -0
  215. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/long-running-agents.md +0 -0
  216. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/recipes/aider.md +0 -0
  217. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/recipes/codewhale.md +0 -0
  218. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/recipes/kimi.md +0 -0
  219. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/recipes/pi.md +0 -0
  220. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/docs/thesis.md +0 -0
  221. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/__init__.py +0 -0
  222. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/_clock.py +0 -0
  223. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/conftest.py +0 -0
  224. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/contract/__init__.py +0 -0
  225. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/__init__.py +0 -0
  226. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  227. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/test_e2e_install_systemd.py +0 -0
  228. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  229. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  230. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  231. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  232. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  233. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/generate_vulture_whitelist.py +0 -0
  234. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/__init__.py +0 -0
  235. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_bounded_run.py +0 -0
  236. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_context_enricher_namespacing.py +0 -0
  237. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_dirty_handler_seam.py +0 -0
  238. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_grace_kill_emission.py +0 -0
  239. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_install_dry_run.py +0 -0
  240. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_monitor_seeded.py +0 -0
  241. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_plugin_detector_loaded.py +0 -0
  242. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_plugin_owned_paths.py +0 -0
  243. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  244. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  245. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_scaffold_presets.py +0 -0
  246. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_substrate_fingerprint.py +0 -0
  247. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/integration/test_transient_error_backoff.py +0 -0
  248. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/__init__.py +0 -0
  249. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/_docs.py +0 -0
  250. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_atomic_write_enforced.py +0 -0
  251. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  252. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_classification_ssot.py +0 -0
  253. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_config_value_set_ssot.py +0 -0
  254. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  255. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  256. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_operator_surface.py +0 -0
  257. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  258. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  259. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  260. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_docs_generated.py +0 -0
  261. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_entry_points_resolve.py +0 -0
  262. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_events_doc_contract.py +0 -0
  263. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  264. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_layer_2_loop_size.py +0 -0
  265. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_peek_schema_version.py +0 -0
  266. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_phase_select_stateless.py +0 -0
  267. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_plugins_config_stable.py +0 -0
  268. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  269. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_round_result_stable.py +0 -0
  270. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_upstream_schema_canary.py +0 -0
  271. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  272. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/literate/__init__.py +0 -0
  273. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/__init__.py +0 -0
  274. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_agent_runtime_progress.py +0 -0
  275. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  276. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_assemble_prompt.py +0 -0
  277. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_events_stream.py +0 -0
  278. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_read_round_num.py +0 -0
  279. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_api_resolve_phase.py +0 -0
  280. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_catalogs.py +0 -0
  281. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_claude_error_detector.py +0 -0
  282. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli_common.py +0 -0
  283. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli_init_install.py +0 -0
  284. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_cli_monitor_http.py +0 -0
  285. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_codewhale_plugin.py +0 -0
  286. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_fresh_eyes.py +0 -0
  287. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_max_rounds.py +0 -0
  288. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_prompt_delivery.py +0 -0
  289. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_schedule.py +0 -0
  290. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_stop_file.py +0 -0
  291. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  292. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_config_transient_error_action.py +0 -0
  293. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_default_dirty_handler.py +0 -0
  294. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_defenses.py +0 -0
  295. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_detector_protocol.py +0 -0
  296. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_dirty_handlers.py +0 -0
  297. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_docgen.py +0 -0
  298. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_emit_config_migrated.py +0 -0
  299. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_emit_schedule.py +0 -0
  300. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  301. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_events.py +0 -0
  302. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_example_dirty_handler.py +0 -0
  303. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_example_plugin.py +0 -0
  304. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_exit_cause.py +0 -0
  305. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  306. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_hook_failure_isolation.py +0 -0
  307. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_hooks.py +0 -0
  308. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_init_entry_points.py +0 -0
  309. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_kimi_plugin.py +0 -0
  310. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_metrics.py +0 -0
  311. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_assembly.py +0 -0
  312. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  313. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  314. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_detectors.py +0 -0
  315. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_monitor_schedule_pause.py +0 -0
  316. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_network_blip_signal_guard.py +0 -0
  317. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_peek_argparse.py +0 -0
  318. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_peek_schedule.py +0 -0
  319. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_peek_select.py +0 -0
  320. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_phase_agent_launch.py +0 -0
  321. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_phase_profiles.py +0 -0
  322. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_pi_plugin.py +0 -0
  323. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_presets.py +0 -0
  324. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_prompt_loader.py +0 -0
  325. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_redact.py +0 -0
  326. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_round_view.py +0 -0
  327. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_schedule.py +0 -0
  328. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_serve_cmd_bounded.py +0 -0
  329. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_substrate.py +0 -0
  330. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_throttle_plugin_classification.py +0 -0
  331. {cli_agent_runner-0.2.11 → cli_agent_runner-0.2.13}/tests/unit/test_vcs_state.py +0 -0
@@ -12,7 +12,6 @@ duration_s
12
12
  timed_out
13
13
  pid
14
14
  killed_for_grace
15
- grace_kill_children
16
15
 
17
16
  # agent_runner.api_types.Alert
18
17
  severity
@@ -248,15 +247,21 @@ path
248
247
  detect
249
248
  apply
250
249
  describe
250
+ advisory
251
251
 
252
252
  # agent_runner.migrations.MigrationResult
253
253
  new_text
254
254
  applied
255
255
  manual
256
+ advisory
256
257
 
257
258
  # agent_runner.monitor.LocalSource
258
259
  log_dir
259
260
 
261
+ # agent_runner.monitor._EventTail
262
+ offsets
263
+ buffer
264
+
260
265
  # agent_runner.phase_select.Selection
261
266
  phase
262
267
  paused
@@ -287,6 +292,7 @@ name
287
292
  ok
288
293
  reason
289
294
  how_to_fix
295
+ permanent
290
296
 
291
297
  # agent_runner.vcs_state.StashRef
292
298
  sha
@@ -5,6 +5,43 @@ 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.13] - 2026-09-02
9
+
10
+ ### Breaking
11
+ - Config validation now rejects unknown keys in base `[agent]`/`[runtime]`/`[vcs]`/`[monitor]`, a table given as a scalar, unknown per-phase `[phases.<name>.prompt]` keys, and an argv `prompt_arg_template` missing `{prompt}`. **Run `agent-runner migrate` before `upgrade`**, as in 0.2.12.
12
+ - systemd `serve` units grow `TimeoutStopSec` (now `round_timeout_s + 210`, up from `+60`) — re-render after upgrading.
13
+
14
+ ### Fixed
15
+ - Round-child exit codes are classified by permanence, not a type whitelist: a config break stays 78, a self-healing environmental failure (lock contention, a git-status timeout, an unwritable log dir) now reliably reaches retryable 76, and everything else still falls to 1 so the crash-loop breaker bounds a genuine bug; serve itself still never exits 76.
16
+ - `kill` reaps the whole round-and-agent process group; `stop`/`restart`/`uninstall` are drain-aware so a mid-round stop no longer leaves a systemd serve stopped or raises; service-mode detection checks systemd `is-active` instead of guessing; and `monitor_auto_stop_triggered` fires only after a confirmed stop.
17
+ - `install --system` rejects control characters in `work_dir`/`log_dir`/config path, closing a root-injection path through the rendered unit.
18
+ - `read_json`/orphan-state reads survive a non-UTF-8 byte or a directory instead of crashing serve; a git-status timeout mid-round no longer drops the round's own bookkeeping (`dirty_check_failed`).
19
+ - Throttle readers (serve's loop-top gate, the skip path, `peek`, monitor) converge on one reset source; the back-off sleep now wakes early on an NTP clock jump.
20
+
21
+ ### Notes
22
+ - New event kind: `dirty_check_failed`. `peek --json` schema is unchanged this release.
23
+
24
+ See `docs/migrations/0.2.md`.
25
+
26
+ ## [0.2.12] - 2026-08-31
27
+
28
+ ### Breaking
29
+ - Config validation now hard-rejects footguns that used to load silently: a scalar where a list is required (`phases.list`, `[agent] command`, `prompt_arg_template`, `[prompt] files`), an empty `[agent] command` / empty top-level `[prompt] files`, unknown keys in `[schedule]`/`[prompt]`, and `threshold > window`. **Run `agent-runner migrate` BEFORE `upgrade`** — the orchestrated upgrade runs the old binary's migrate, which cannot self-cross this break. Every new error names `agent-runner migrate`.
30
+ - systemd `serve` unit adds `StartLimitIntervalSec`/`StartLimitBurst`; re-render or hand-edit existing units.
31
+
32
+ ### Fixed
33
+ - The monitor no longer dies on a non-UTF-8 byte or an odd plugin severity; each builtin detector is isolated (`detector_error` event) and a hung round is TERM→grace→killpg'd (`round_supervisor_wedged`) instead of orphaning the agent or latching `hung`.
34
+ - Environmental startup failures (ENOSPC, mount hiccup, unclassified) exit 76 and keep serve retrying at a fixed back-off until they heal, instead of tripping the crash-loop breaker; deterministic config failures give up by name (exit 78).
35
+ - A git-commit timeout leaves no surviving `.git/index.lock` (self-caused only; `stale_index_lock_cleared`); a stop-file lands within one back-off chunk.
36
+ - `status`/`kill`/`restart` report and act on real service state: liveness falls back to `serve.pid` when `systemctl` is absent, and `restart` refuses on non-service modes before stopping.
37
+ - `upgrade` runs the new binary's `migrate` before the smoke check, restores the config on rollback, and emits `upgrade_start_failed` with a remedy when the new version installs but won't start.
38
+ - `json_tail` no longer lets a blank-line flood evict the terminal round-log record; `init` commits only its scaffold files.
39
+
40
+ ### Notes
41
+ - New event kinds: `detector_error`, `round_supervisor_wedged`, `stale_index_lock_cleared`, `upgrade_start_failed`. `RestartPreventExitStatus` stays `78 75` — environmental failures (round exit 76) are retried inside serve rather than stopping it.
42
+
43
+ See `docs/migrations/0.2.md`.
44
+
8
45
  ## [0.2.11] - 2026-08-29
9
46
 
10
47
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.2.11
3
+ Version: 0.2.13
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
@@ -157,10 +157,15 @@ def render_event_kinds_list() -> str:
157
157
 
158
158
 
159
159
  def _render_migrate_transforms() -> str:
160
- """Bullet list of the transforms `agent-runner migrate` applies, from the registry."""
161
- from agent_runner.migrations import MIGRATIONS
162
-
163
- return "\n".join(f"- {m.describe}" for m in MIGRATIONS)
160
+ """Bullet list of the transforms `agent-runner migrate` applies, from the registry.
161
+ A few entries carry a `parsed -> str` describe (so the manual report can name the
162
+ exact offending key found in a real config); rendered here with `{}`, which degrades
163
+ to the same generic instruction since there's no config in hand."""
164
+ from agent_runner.migrations import MIGRATIONS, _describe
165
+
166
+ # No config in hand here, so pass an empty parsed dict: a `parsed -> str`
167
+ # describe degrades to its generic instruction, same rule as the manual report.
168
+ return "\n".join(f"- {_describe(m, {})}" for m in MIGRATIONS)
164
169
 
165
170
 
166
171
  def _subparser_action() -> argparse.Action:
@@ -27,9 +27,11 @@ __all__ = [
27
27
  "emit_round_progress",
28
28
  "emit_round_substrate_after",
29
29
  "emit_round_substrate_before",
30
+ "emit_round_supervisor_wedged",
30
31
  "emit_schedule_paused",
31
32
  "emit_schedule_phase_skipped",
32
33
  "emit_schedule_resumed",
34
+ "emit_stale_index_lock_cleared",
33
35
  "emit_stop_file_detected",
34
36
  "emit_transient_error_backoff_capped",
35
37
  "emit_transient_error_detected",
@@ -52,7 +54,8 @@ def emit_max_rounds_reached(log_dir: Path, *, rounds_completed: int, max_rounds:
52
54
 
53
55
 
54
56
  def emit_config_broken(log_dir: Path, *, reason: str) -> None:
55
- """Emit config_broken (serve stopped on a permanent startup-battery failure)."""
57
+ """Emit config_broken (serve gave up on a permanent, non-self-healing failure
58
+ a startup-battery check, or any other ConfigError-classified round exit)."""
56
59
  from agent_runner.events import CONFIG_BROKEN, emit
57
60
 
58
61
  emit(log_dir, CONFIG_BROKEN, reason=reason)
@@ -68,7 +71,7 @@ def emit_crash_loop(log_dir: Path, *, consecutive: int, exit_code: int, log_path
68
71
  from agent_runner.events import CRASH_LOOP, emit
69
72
 
70
73
  try:
71
- reason = redact_secrets(log_path.read_text(errors="replace")[-2000:])
74
+ reason = redact_secrets(log_path.read_text(encoding="utf-8", errors="replace")[-2000:])
72
75
  except OSError:
73
76
  reason = ""
74
77
  emit(log_dir, CRASH_LOOP, consecutive=consecutive, exit_code=exit_code, reason=reason)
@@ -98,6 +101,15 @@ def emit_stop_file_detected(
98
101
  )
99
102
 
100
103
 
104
+ def emit_stale_index_lock_cleared(log_dir: Path, *, lock_path: str, round_num: int) -> None:
105
+ """Emit when serve removed a .git/index.lock that its own timed-out+killed git
106
+ call orphaned. Single-writer: serve holds the round lock, so any lock surviving
107
+ our kill is ours to clear."""
108
+ from agent_runner.events import STALE_INDEX_LOCK_CLEARED, emit
109
+
110
+ emit(log_dir, STALE_INDEX_LOCK_CLEARED, lock_path=lock_path, round_num=round_num)
111
+
112
+
101
113
  def emit_schedule_paused(
102
114
  log_dir: Path, *, active_window: str, resume_at: str, timezone: str, phase: str = ""
103
115
  ) -> None:
@@ -168,6 +180,18 @@ def emit_round_substrate_after(
168
180
  )
169
181
 
170
182
 
183
+ def emit_round_supervisor_wedged(
184
+ log_dir: Path, *, pid: int, timeout_s: int, log_path: Path
185
+ ) -> None:
186
+ """Emit when the round subprocess blew past the outer ceiling and serve had to
187
+ TERM/kill it (the round supervisor was wedged — not doing its own bounded
188
+ post-round cleanup). Distinct from round_timeout_kill (the AGENT hit the inner
189
+ wall inside a healthy round)."""
190
+ from agent_runner.events import ROUND_SUPERVISOR_WEDGED, emit
191
+
192
+ emit(log_dir, ROUND_SUPERVISOR_WEDGED, pid=pid, timeout_s=timeout_s, log_path=str(log_path))
193
+
194
+
171
195
  def emit_fresh_eyes_round_triggered(log_dir: Path, *, round_num: int, every_n: int) -> None:
172
196
  """Emit fresh-eyes signal trigger event (only on triggered rounds)."""
173
197
  from agent_runner.events import FRESH_EYES_ROUND_TRIGGERED, emit
@@ -292,8 +316,11 @@ def emit_agent_usage_recorded(
292
316
  - ``models_breakdown``: only populated when a round used multiple models
293
317
  (gemini multi-model rounds). None for claude (always single-model).
294
318
  - ``cache_creation_tokens``: claude only — ``usage.cache_creation_input_tokens``,
295
- independent count from ``cached_tokens`` (cache_read). Billed at ~25% premium
296
- over fresh input per Anthropic pricing. Gemini has no equivalent 0.
319
+ independent count from ``cached_tokens`` (cache_read). Priced differently
320
+ from fresh input by the provider; the framework records the count only and
321
+ never interprets cost (no price tables here — see ``cost_usd`` above for the
322
+ one cost figure the framework passes through verbatim). Gemini has no
323
+ equivalent → 0.
297
324
  - ``tool_call_count``: number of tool invocations the agent made in the round.
298
325
  Claude: count of ``tool_use`` content blocks across all assistant events.
299
326
  Gemini: ``stats.tool_calls``.
@@ -0,0 +1,120 @@
1
+ """Internal identity/location resolver (Group C, 0.2.13 "close the seams").
2
+
3
+ Project name, config path, unit filename and log_dir were each derived
4
+ independently at several call sites and could disagree when ``work_dir`` and
5
+ the TOML's own directory differ. This module is the single source for all
6
+ four; the divergent call sites route through it instead of re-deriving. It
7
+ also carries ``guard_against_clobber``, ``api.install``'s same-basename
8
+ sibling-unit guard — a natural fit since it works entirely off unit-file
9
+ identity (``WorkingDirectory=``) rather than any install-specific state.
10
+
11
+ INTERNAL ONLY -- not a public/documented contract. Exposing a resolver as a
12
+ public contract is deferred to 0.3.
13
+
14
+ Leaf module: imports only ``agent_runner.config`` and
15
+ ``agent_runner.service_unit`` (both leaves themselves), so anything in the
16
+ package can import this without risking a cycle.
17
+
18
+ Lenient/strict split (spec-review correction): validating the project
19
+ name on the serve/round/init path would newly reject any ``work_dir`` whose
20
+ basename has a space or CJK characters -- common on macOS dev boxes -- an
21
+ unflagged BREAK, since only lifecycle/observe verbs validate today. So
22
+ ``strict=True`` applies ONLY where the name is interpolated into a unit
23
+ filename / ssh / systemd identity (api.py's lifecycle verbs, install,
24
+ lifecycle.py, install_cmd.py); ``strict=False`` for descriptive uses
25
+ (``hook_ctx.project`` in runner.py, the scaffolded toml's ``{project}`` in
26
+ scaffold.py) that must keep accepting any basename.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import re
32
+ from pathlib import Path
33
+
34
+ from agent_runner.config import load_config
35
+ from agent_runner.service_unit import serve_unit_filename
36
+
37
+ _PROJECT_NAME_RE = re.compile(r"^[A-Za-z0-9._-]+$")
38
+
39
+
40
+ def project_name(work_dir: Path, *, strict: bool) -> str:
41
+ """Derive the project name from ``work_dir``'s basename.
42
+
43
+ strict=True rejects anything but ``[A-Za-z0-9._-]+`` -- required wherever
44
+ the name is interpolated into a unit filename / ssh / systemd identity.
45
+ strict=False accepts any non-empty basename (spaces, CJK, etc.) -- for
46
+ descriptive uses where the name is never shelled out or used as a
47
+ filesystem/systemd token by itself.
48
+ """
49
+ name = work_dir.resolve().name or "default"
50
+ if strict and not _PROJECT_NAME_RE.match(name):
51
+ raise ValueError(
52
+ f"invalid project name {name!r}: must match [A-Za-z0-9._-]+. "
53
+ "The project name is the basename of work_dir and is interpolated into "
54
+ "ssh remote commands and systemd unit filenames; shell metacharacters "
55
+ "and path separators are rejected."
56
+ )
57
+ return name
58
+
59
+
60
+ def config_path(args) -> Path:
61
+ """Resolve the config TOML path CLI ``args`` refer to (``--config``,
62
+ default ``./agent-runner.toml``).
63
+
64
+ Single source for "given argparse args, which agent-runner.toml" so every
65
+ CLI bootstrap step (which runs before a ``Config`` exists to read
66
+ ``runtime.work_dir`` back from) agrees on the same file, instead of each
67
+ reconstructing its own getattr/default dance.
68
+ """
69
+ cfg = getattr(args, "config", None)
70
+ return Path(cfg).resolve() if cfg is not None else Path("agent-runner.toml").resolve()
71
+
72
+
73
+ def unit_filename(project: str) -> str:
74
+ """Thin wrap of the existing ``service_unit.serve_unit_filename`` -- the
75
+ single name every unit-filename call site should go through."""
76
+ return serve_unit_filename(project)
77
+
78
+
79
+ def log_dir(work_dir: Path) -> Path:
80
+ """Return the configured log_dir from ``<work_dir>/agent-runner.toml``.
81
+
82
+ Falls back to the conventional ``~/.agent-runner/<project>/logs`` only
83
+ when the toml is missing there, keeping lifecycle/observe verbs aligned
84
+ with where ``serve_cmd.py`` actually writes ``serve.pid``.
85
+ """
86
+ cfg_path = work_dir / "agent-runner.toml"
87
+ if cfg_path.exists():
88
+ return load_config(cfg_path).runtime.log_dir
89
+ return Path.home() / ".agent-runner" / project_name(work_dir, strict=True) / "logs"
90
+
91
+
92
+ def _unit_owner(serve_path: Path) -> Path | None:
93
+ """Parse an existing unit's ``WorkingDirectory=`` line, or None if the file
94
+ doesn't exist / can't be read / has no such line."""
95
+ try:
96
+ text = serve_path.read_text()
97
+ except OSError:
98
+ return None
99
+ for line in text.splitlines():
100
+ if line.startswith("WorkingDirectory="):
101
+ return Path(line.removeprefix("WorkingDirectory="))
102
+ return None
103
+
104
+
105
+ def guard_against_clobber(serve_path: Path, work_dir: Path, *, force: bool) -> None:
106
+ """Refuse to overwrite a same-basename sibling project's unit.
107
+
108
+ The unit filename is derived from the project name (work_dir's basename)
109
+ alone, so two unrelated projects sharing a basename — or the same project
110
+ moved to a new path — would silently clobber each other's install.
111
+ force=True is the explicit override for the moved-repo case (``api.install``).
112
+ """
113
+ owner = _unit_owner(serve_path)
114
+ if owner is None or owner == work_dir or force:
115
+ return
116
+ raise FileExistsError(
117
+ f"{serve_path} already manages a different project at {owner} "
118
+ f"(this is {work_dir}); pass force=True to overwrite "
119
+ "(e.g. after moving/renaming that project's directory)."
120
+ )
@@ -0,0 +1,189 @@
1
+ """Pure serve restart policy — no I/O, no clock, no events read.
2
+
3
+ Extracted from api.py (0.2.12 Group G): serve_cmd imports the decision + exit
4
+ codes from here directly (see the architecture allowlist); api.py re-exports
5
+ them for external back-compat. Kept dependency-free so the serve loop stays a
6
+ thin dispatcher and the policy is unit-testable in isolation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Literal
12
+
13
+ from agent_runner.config import ConfigError
14
+
15
+ # Exit code for a permanent (no-retry) startup-battery failure. A broken config
16
+ # does not self-heal between rounds, so serve STOPS rather than respawning it
17
+ # forever. 78 = EX_CONFIG (sysexits) — avoids argparse's 2 and the generic 1.
18
+ PERMANENT_CONFIG_EXIT = 78
19
+
20
+ # Exit code serve returns when the crash-loop breaker trips (a give-up stop, like
21
+ # config_broken). Distinct from 78 so `systemctl status` / SuccessExitStatus can
22
+ # tell the two "needs intervention" stops apart. 75 = EX_TEMPFAIL (sysexits).
23
+ # The systemd unit lists both in RestartPreventExitStatus so a deliberate stop
24
+ # stays stopped (and visibly failed) while an unexpected supervisor crash restarts.
25
+ CRASH_LOOP_EXIT = 75
26
+
27
+ # Exit code for an ENVIRONMENTAL startup-battery failure (ENOSPC, mount hiccup,
28
+ # an unclassified check): recoverable, unlike a permanent config break. NOT in
29
+ # the unit's RestartPreventExitStatus (stays "78 75") so 76 restarts; treated
30
+ # exactly like an active throttle (post_round_decision), so serve keeps
31
+ # retrying at a fixed doubled delay with the crash-loop breaker disarmed,
32
+ # staying alive until the environment heals. StartLimit bounds only repeated
33
+ # serve-PROCESS death, not this round-level path. 76 = EX_PROTOCOL (sysexits).
34
+ ENV_BATTERY_EXIT = 76
35
+
36
+ # Crash-loop circuit breaker (b12). The serve loop escalates the restart delay
37
+ # on consecutive UNKNOWN short crashes (non-zero exit, short duration, no
38
+ # classified transient) and STOPS after CRASH_LOOP_THRESHOLD of them — the Run 6
39
+ # ~100-empty-rounds scar. Recoverable-slow failures (rate limit / 5h quota / 5xx
40
+ # / timeout) are already handled by the transient-error throttle and never reach
41
+ # this path. A clean (exit 0), long, or classified-transient round resets the run.
42
+ CRASH_LOOP_THRESHOLD = 5
43
+ CRASH_LOOP_SHORT_EXIT_S = 60 # mirrors monitor.SHORT_EXIT_THRESHOLD_S
44
+ CRASH_LOOP_MAX_DELAY_S = 1800 # cap the escalating restart delay (30 min)
45
+
46
+
47
+ class EnvironmentalError(Exception):
48
+ """Marks a round-child failure as ENVIRONMENTAL (recoverable) rather than a
49
+ permanent config break — classify_round_exit maps it to ENV_BATTERY_EXIT
50
+ (76), same treatment as an active throttle: serve retries at a flat back-off
51
+ with the crash-loop breaker disarmed.
52
+
53
+ A raise site elsewhere in the package (runner.LockHeldError,
54
+ vcs_state.GitTimeout) mixes this in alongside its own base class instead of
55
+ _serve_policy importing every module that raises one — this leaf module
56
+ sits BELOW runner.py/vcs_state.py in the import graph (both reach here via
57
+ api.py already), so importing upward would cycle. classify_round_exit
58
+ matches this marker, not the concrete class or its OTHER base.
59
+ """
60
+
61
+
62
+ # The action strings below are the restart-action enum, not events.py kinds:
63
+ # "continue" has no constant, and a constant cannot sit inside Literal[...].
64
+ def post_round_decision(
65
+ *,
66
+ returncode: int,
67
+ duration_s: float,
68
+ throttle_active: bool,
69
+ consecutive: int,
70
+ restart_delay_s: int,
71
+ ) -> tuple[Literal["config_broken", "crash_loop", "continue"], int, int]:
72
+ """Restart policy after one round — keeps the serve loop a thin dispatcher.
73
+
74
+ Returns ``(action, delay_s, consecutive)`` where action is:
75
+ - ``"config_broken"`` — permanent startup failure (b18): stop.
76
+ - ``"crash_loop"`` — CRASH_LOOP_THRESHOLD consecutive unknown short crashes
77
+ (b12): stop. An unknown short crash is a non-zero, fast exit with no
78
+ classified transient (rate-limit/5xx/timeout are handled by the throttle).
79
+ - ``"continue"`` — sleep ``delay_s`` then run the next round.
80
+
81
+ A clean (exit 0), long, or transient round resets ``consecutive`` to 0; an
82
+ unknown short crash escalates the delay (restart × 2ⁿ, capped) until the stop.
83
+ An ``ENV_BATTERY_EXIT`` (76, environmental startup-battery failure) is treated
84
+ exactly like an active throttle: it takes the flat ``restart_delay_s * 2``
85
+ delay below (never escalating) and resets ``consecutive`` to 0, so it never
86
+ counts toward the crash-loop breaker — a short environmental outage (ENOSPC,
87
+ mount hiccup) never trips ``crash_loop``. This always returns ``"continue"``,
88
+ so serve itself never exits 76 and systemd's StartLimit window never engages
89
+ for this path; a *persistent* environmental failure keeps serve alive
90
+ retrying at the doubled delay until the environment heals. StartLimit bounds
91
+ only repeated serve-PROCESS death, independent of this round-level path.
92
+ """
93
+ if returncode == PERMANENT_CONFIG_EXIT:
94
+ return ("config_broken", 0, consecutive)
95
+ throttle_active = throttle_active or returncode == ENV_BATTERY_EXIT
96
+ unknown_short_crash = (
97
+ returncode != 0 and duration_s < CRASH_LOOP_SHORT_EXIT_S and not throttle_active
98
+ )
99
+ if unknown_short_crash:
100
+ consecutive += 1
101
+ if consecutive >= CRASH_LOOP_THRESHOLD:
102
+ return ("crash_loop", 0, consecutive)
103
+ delay = min(restart_delay_s * 2**consecutive, CRASH_LOOP_MAX_DELAY_S)
104
+ return ("continue", delay, consecutive)
105
+ delay = restart_delay_s if returncode == 0 else restart_delay_s * 2
106
+ return ("continue", delay, 0)
107
+
108
+
109
+ def classify_round_exit(exc: BaseException) -> int:
110
+ """The round-child exit-code CLASSIFIER (Group A) — permanence, not exception
111
+ identity, decides serve's response. Replaces the old exception *whitelist*
112
+ (``cli/__init__.py`` mapped only ``ConfigError``->78, ``round_cmd.py`` only
113
+ ``KeyboardInterrupt``->130; ~10 other classes fell through to Python's own
114
+ uncaught-exception exit 1 — neither retry-76 nor stay-stopped-78/75).
115
+
116
+ - ``ConfigError`` (won't self-heal) -> ``PERMANENT_CONFIG_EXIT`` (78).
117
+ - ``EnvironmentalError``-marked (self-heals) -> ``ENV_BATTERY_EXIT`` (76).
118
+ - ``KeyboardInterrupt`` (SIGTERM/SIGINT) -> 130, the shell's convention.
119
+ - ``SystemExit`` passes its own code through unchanged (an int code; a
120
+ non-int/absent code — argparse-style ``sys.exit("msg")`` or a bare
121
+ ``sys.exit()`` — falls to 1, same as unclassified).
122
+ - Anything else, INCLUDING a totally unclassified traceback, falls to 1 so
123
+ the crash-loop breaker still bounds a genuine supervisor bug. Do NOT
124
+ default unclassified -> 76: ``post_round_decision`` resets its counter on
125
+ 76, so an arbitrary bug would loop forever at the doubled delay with no
126
+ breaker and no detector. The battery's own "unclassified -> environmental"
127
+ rule (``startup_check.py``) governs *authored probes*, not arbitrary round
128
+ tracebacks — a different trust boundary.
129
+
130
+ Never returns ``CRASH_LOOP_EXIT`` (75) — that verdict belongs exclusively to
131
+ serve's own ``post_round_decision``, counting consecutive unknown short
132
+ crashes; a round child never claims it directly.
133
+ """
134
+ if isinstance(exc, SystemExit):
135
+ return exc.code if isinstance(exc.code, int) else 1
136
+ if isinstance(exc, KeyboardInterrupt):
137
+ return 130
138
+ if isinstance(exc, ConfigError):
139
+ return PERMANENT_CONFIG_EXIT
140
+ if isinstance(exc, EnvironmentalError):
141
+ return ENV_BATTERY_EXIT
142
+ return 1
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Round-timeout budget (Group C, seam 3 — 0.2.13). TimeoutStopSec
147
+ # (service_unit.py, systemd's SIGKILL deadline after `systemctl stop`) and the
148
+ # outer round-wall ceiling (api.outer_round_ceiling_s, the in-process watchdog
149
+ # around the round subprocess) were derived independently and could drift —
150
+ # TimeoutStopSec only added a flat 60s to the inner round timeout, with no
151
+ # margin for the outer ceiling's own reap/git-commit/hook slack, let alone the
152
+ # SIGTERM-to-round-child grace on top of that. One function computes both so
153
+ # they cannot disagree.
154
+ #
155
+ # service_unit.py must not import api.py (cycle: api.py already imports
156
+ # service_unit.render_serve_unit), so this lives here — a dependency-free leaf
157
+ # both service_unit.py and api.py can import. Its margin constants are
158
+ # LITERAL mirrors of the real sources of truth (agent_runtime.REAP_GRACE_S,
159
+ # vcs_state.GIT_COMMIT_TIMEOUT_S, api._ROUND_TERM_GRACE_S) rather than
160
+ # imports, same as service_unit.py's literal "78 75" RestartPreventExitStatus
161
+ # mirroring PERMANENT_CONFIG_EXIT/CRASH_LOOP_EXIT — pinned by
162
+ # test_timeout_budget_invariant.py so a change to any of the three can't
163
+ # silently drift this budget out of sync.
164
+ _REAP_GRACE_S = 5
165
+ _GIT_COMMIT_TIMEOUT_S = 120
166
+ _HOOK_ALLOWANCE_S = 60 # slack for post-round hooks between the inner wall and the ceiling
167
+ _ROUND_TERM_GRACE_S = 15 # mirrors api._ROUND_TERM_GRACE_S / serve_cmd._ROUND_TERM_GRACE_S
168
+ _STOP_GRACE_MARGIN_S = 10 # pad above _ROUND_TERM_GRACE_S for systemd stop-request overhead
169
+
170
+
171
+ def timeout_budget(round_timeout_s: int) -> tuple[int, int]:
172
+ """Single source for the round-timeout safety budget.
173
+
174
+ Returns ``(timeout_stop_sec, outer_ceiling_s)``:
175
+
176
+ - ``outer_ceiling_s`` — the in-process outer wall-clock ceiling for the
177
+ round subprocess (``api.outer_round_ceiling_s``): ``round_timeout_s``
178
+ plus reap grace + git-commit ceiling + hook allowance, so it only trips
179
+ when the round supervisor itself is wedged, never during its own
180
+ bounded post-round cleanup.
181
+ - ``timeout_stop_sec`` — systemd's ``TimeoutStopSec``
182
+ (``service_unit.py``): must clear ``outer_ceiling_s`` by enough for a
183
+ SIGTERM to reach and drain the round (``_ROUND_TERM_GRACE_S``) plus a
184
+ stop-request overhead pad, so `systemctl stop` never SIGKILLs a round
185
+ that is draining normally.
186
+ """
187
+ outer_ceiling_s = round_timeout_s + _REAP_GRACE_S + _GIT_COMMIT_TIMEOUT_S + _HOOK_ALLOWANCE_S
188
+ timeout_stop_sec = outer_ceiling_s + _ROUND_TERM_GRACE_S + _STOP_GRACE_MARGIN_S
189
+ return timeout_stop_sec, outer_ceiling_s