cli-agent-runner 0.1.38__tar.gz → 0.1.40__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 (229) hide show
  1. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.gitignore +3 -0
  2. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CHANGELOG.md +25 -1
  3. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_docgen.py +0 -6
  5. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_emit.py +15 -2
  6. cli_agent_runner-0.1.40/agent_runner/_redact.py +102 -0
  7. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_version.py +2 -2
  8. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/agent_runtime.py +45 -17
  9. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/config.py +30 -0
  10. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/hooks.py +4 -3
  11. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/claude.toml +1 -0
  12. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/runner.py +7 -2
  13. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/commands.md +24 -4
  14. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/configuration.md +9 -3
  15. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/long-running-agents.md +1 -1
  16. cli_agent_runner-0.1.40/docs/migrations/0.1.39.md +74 -0
  17. cli_agent_runner-0.1.40/docs/migrations/0.1.40.md +42 -0
  18. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/plugins.md +4 -2
  19. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/runbook.md +23 -4
  20. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/thesis.md +4 -3
  21. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_grace_kill_emission.py +64 -1
  22. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_architecture.py +3 -16
  23. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_catalogs.py +0 -1
  24. cli_agent_runner-0.1.40/tests/invariants/test_doc_claims_match_ssot.py +102 -0
  25. cli_agent_runner-0.1.40/tests/unit/test_agent_runtime_grace.py +268 -0
  26. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_observation.py +3 -2
  27. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_claude_error_detector.py +17 -0
  28. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config.py +45 -0
  29. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_docgen.py +19 -13
  30. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_hooks.py +9 -0
  31. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_presets.py +20 -0
  32. cli_agent_runner-0.1.40/tests/unit/test_redact.py +136 -0
  33. cli_agent_runner-0.1.38/tests/unit/test_agent_runtime_grace.py +0 -159
  34. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.codecov.yml +0 -0
  35. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  36. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  37. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  38. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  39. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/workflows/ci.yml +0 -0
  40. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.github/workflows/release.yml +0 -0
  41. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/.vulture-whitelist.py +0 -0
  42. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CODE_OF_CONDUCT.md +0 -0
  43. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/CONTRIBUTING.md +0 -0
  44. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/LICENSE +0 -0
  45. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/README.md +0 -0
  46. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/README.zh.md +0 -0
  47. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/SECURITY.md +0 -0
  48. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/__init__.py +0 -0
  49. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_registry.py +0 -0
  50. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_substrate.py +0 -0
  51. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/_throttle.py +0 -0
  52. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/api.py +0 -0
  53. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/api_types.py +0 -0
  54. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/__init__.py +0 -0
  55. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/_constants.py +0 -0
  56. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  57. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/gemini.py +0 -0
  58. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/__init__.py +0 -0
  59. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/__main__.py +0 -0
  60. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/common.py +0 -0
  61. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/events_cmd.py +0 -0
  62. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/init_cmd.py +0 -0
  63. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/install_cmd.py +0 -0
  64. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/monitor_cmd.py +0 -0
  65. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/peek_cmd.py +0 -0
  66. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/round_cmd.py +0 -0
  67. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/serve_cmd.py +0 -0
  68. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/service_cmd.py +0 -0
  69. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/cli/upgrade_cmd.py +0 -0
  70. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/context_store.py +0 -0
  71. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/defenses.py +0 -0
  72. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/detector_helpers.py +0 -0
  73. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/events.py +0 -0
  74. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/http_progress.py +0 -0
  75. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/lifecycle.py +0 -0
  76. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/metrics.py +0 -0
  77. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/monitor.py +0 -0
  78. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/__init__.py +0 -0
  79. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/aider.toml +0 -0
  80. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/presets/gemini.toml +0 -0
  81. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/prompt_loader.py +0 -0
  82. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/round_log.py +0 -0
  83. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/round_view.py +0 -0
  84. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/scaffold.py +0 -0
  85. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/service_unit.py +0 -0
  86. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/startup_check.py +0 -0
  87. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/agent_runner/vcs_state.py +0 -0
  88. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/build.sh +0 -0
  89. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/example-agent-runner.toml +0 -0
  90. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/launchd.plist.tmpl +0 -0
  91. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/run-loop.sh +0 -0
  92. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/deploy/systemd.service.tmpl +0 -0
  93. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/README.md +0 -0
  94. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/architecture.md +0 -0
  95. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/events.md +0 -0
  96. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/marketing/README.md +0 -0
  97. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/marketing/promo-cn.html +0 -0
  98. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.16.md +0 -0
  99. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.17.md +0 -0
  100. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.19.md +0 -0
  101. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.20.md +0 -0
  102. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.21.md +0 -0
  103. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.22.md +0 -0
  104. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.23.md +0 -0
  105. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.24.md +0 -0
  106. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.25.md +0 -0
  107. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.26.md +0 -0
  108. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.27.md +0 -0
  109. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.28.md +0 -0
  110. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.29.md +0 -0
  111. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.30.md +0 -0
  112. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.31.md +0 -0
  113. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.32.md +0 -0
  114. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.33.md +0 -0
  115. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.34.md +0 -0
  116. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.35.md +0 -0
  117. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.36.md +0 -0
  118. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.37.md +0 -0
  119. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/migrations/0.1.38.md +0 -0
  120. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/quickstart.md +0 -0
  121. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/docs/recipes/aider.md +0 -0
  122. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/pyproject.toml +0 -0
  123. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/__init__.py +0 -0
  124. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/_test_helpers.py +0 -0
  125. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/conftest.py +0 -0
  126. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/contract/__init__.py +0 -0
  127. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/contract/test_public_api_surface.py +0 -0
  128. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/__init__.py +0 -0
  129. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/conftest.py +0 -0
  130. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  131. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_install_systemd.py +0 -0
  132. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  133. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  134. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  135. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  136. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  137. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/__init__.py +0 -0
  138. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_bounded_run.py +0 -0
  139. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_context_enricher_namespacing.py +0 -0
  140. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_fresh_eyes_signal.py +0 -0
  141. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_install_dry_run.py +0 -0
  142. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_monitor_seeded.py +0 -0
  143. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_detector_loaded.py +0 -0
  144. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_owned_paths.py +0 -0
  145. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_real_flow.py +0 -0
  146. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  147. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_scaffold_presets.py +0 -0
  148. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_serve_loop.py +0 -0
  149. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_substrate_fingerprint.py +0 -0
  150. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/integration/test_transient_error_backoff.py +0 -0
  151. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/__init__.py +0 -0
  152. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_atomic_write_enforced.py +0 -0
  153. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_classification_ssot.py +0 -0
  154. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_docs_generated.py +0 -0
  155. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_entry_points_resolve.py +0 -0
  156. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kind_registry.py +0 -0
  157. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kinds_ssot.py +0 -0
  158. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_events_doc_contract.py +0 -0
  159. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_layer_2_loop_size.py +0 -0
  160. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_module_boundaries.py +0 -0
  161. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_module_sizes.py +0 -0
  162. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_no_ai_signatures.py +0 -0
  163. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  164. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_peek_schema_version.py +0 -0
  165. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  166. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_round_result_stable.py +0 -0
  167. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  168. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/invariants/test_upstream_schema_canary.py +0 -0
  169. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/__init__.py +0 -0
  170. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/parser.py +0 -0
  171. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/test_parser.py +0 -0
  172. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/literate/test_quickstart.py +0 -0
  173. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/__init__.py +0 -0
  174. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime.py +0 -0
  175. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime_progress.py +0 -0
  176. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_assemble_prompt.py +0 -0
  177. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_events_stream.py +0 -0
  178. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_install.py +0 -0
  179. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_read_round_num.py +0 -0
  180. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_resolve_phase.py +0 -0
  181. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_service.py +0 -0
  182. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_api_types.py +0 -0
  183. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_auto_stop_gating.py +0 -0
  184. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli.py +0 -0
  185. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_common.py +0 -0
  186. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_init_install.py +0 -0
  187. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_monitor_http.py +0 -0
  188. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  189. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_cli_upgrade.py +0 -0
  190. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_fresh_eyes.py +0 -0
  191. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_max_rounds.py +0 -0
  192. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_stop_file.py +0 -0
  193. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  194. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_config_transient_error_action.py +0 -0
  195. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_context_store.py +0 -0
  196. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_defenses.py +0 -0
  197. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_detector_helpers.py +0 -0
  198. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_detector_protocol.py +0 -0
  199. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_events.py +0 -0
  200. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_events_cmd.py +0 -0
  201. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  202. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_gemini_plugin.py +0 -0
  203. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_hook_failure_isolation.py +0 -0
  204. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_http_progress.py +0 -0
  205. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_init_entry_points.py +0 -0
  206. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_lifecycle.py +0 -0
  207. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_metrics.py +0 -0
  208. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_assembly.py +0 -0
  209. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  210. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  211. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  212. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detectors.py +0 -0
  213. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_remote.py +0 -0
  214. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_peek_argparse.py +0 -0
  215. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_peek_select.py +0 -0
  216. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_prompt_loader.py +0 -0
  217. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_round_log_helpers.py +0 -0
  218. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_round_view.py +0 -0
  219. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_runner.py +0 -0
  220. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_runner_throttle.py +0 -0
  221. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_scaffold.py +0 -0
  222. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_cmd_bounded.py +0 -0
  223. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_round_log.py +0 -0
  224. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_sentinel.py +0 -0
  225. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_serve_startup_hooks.py +0 -0
  226. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_service_unit.py +0 -0
  227. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_startup_check.py +0 -0
  228. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_substrate.py +0 -0
  229. {cli_agent_runner-0.1.38 → cli_agent_runner-0.1.40}/tests/unit/test_vcs_state.py +0 -0
@@ -27,3 +27,6 @@ agent_runner/_version.py
27
27
  .coverage
28
28
  coverage.xml
29
29
  htmlcov/
30
+
31
+ # Personal /team-onboarding artifact — not project content
32
+ ONBOARDING.md
@@ -5,7 +5,31 @@ 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
- ## [Unreleased]
8
+ ## [0.1.40] - 2026-05-31
9
+
10
+ ### Security
11
+ - Grace-kill child-process fields (`live_children` / `ignored_children` in `round_grace_extended` / `round_grace_kill`) no longer store raw command lines — only the executable basename + pid (and, for ignored children, which ignore-pattern matched). This structurally prevents secrets passed in a child's arguments from reaching `events-*.jsonl`. The field shape changed from a list of strings to a list of objects.
12
+ - Free-text event excerpts that can carry agent output — `transient_error_detected.raw`, `hook_failed.error_message`/`traceback`, `serve_startup_hook_failed.exc_msg` — are now best-effort redacted (auth headers, tokens, credential URLs, `KEY=value` secrets, known key-prefixes, JWT, PEM).
13
+ - Pre-0.1.40 `events-*.jsonl` may contain unredacted argv/excerpts — see `docs/migrations/0.1.40.md`.
14
+
15
+ ### Changed
16
+ - Docs: `configuration.md` `[monitor.host_health]` example points to the generated schema table instead of restating default values.
17
+
18
+ ## [0.1.39] - 2026-05-29
19
+
20
+ ### Fixed
21
+ - Grace-kill (`max_grace_after_result_s`) is no longer defeated by long-lived helper subprocesses (e.g. claude's persistent Bash-tool shell-snapshot). `[runtime] grace_kill_ignore_patterns` lists regexes for cmdlines to exclude from the liveness count; the claude preset ships a matching default.
22
+
23
+ ### Added
24
+ - `[runtime] grace_kill_ignore_patterns: list[str]` — regex patterns; matching child cmdlines are excluded from the grace-kill liveness check.
25
+ - `round_grace_extended` event payload gains `ignored_children` — cmdlines filtered by `grace_kill_ignore_patterns`.
26
+
27
+ ### Changed
28
+ - Docs: `commands.md` documents `monitor --mode/--port` and `init --preset`; the Chinese verb list and `[monitor]` default values now point to the generated tables instead of restating them; runbook upgrade examples use a version placeholder.
29
+
30
+ ### Internal
31
+ - New invariant `test_doc_claims_match_ssot` gates documented counts (detectors / defenses / verbs) and config value-sets (`dirty_action` / `context_injection_mode` / transient classification) against their code SSOT — count/enum doc drift now fails CI at the introducing commit.
32
+ - Removed the unused `alert-kinds` docgen renderer; de-duplicated redundant defense-count and alert-kind guards to one canonical tripwire each.
9
33
 
10
34
  ## [0.1.38] - 2026-05-24
11
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-agent-runner
3
- Version: 0.1.38
3
+ Version: 0.1.40
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
@@ -110,11 +110,6 @@ def render_defenses_table() -> str:
110
110
  return "\n".join(lines)
111
111
 
112
112
 
113
- def render_alert_kinds_list() -> str:
114
- """Flat bullet list of all known alert kinds, alphabetised."""
115
- return "\n".join(f"- `{k}`" for k in sorted(KNOWN_ALERT_KINDS))
116
-
117
-
118
113
  def render_detector_list() -> str:
119
114
  """Bullet list of detectors; auto-stop kinds flagged inline."""
120
115
  lines: list[str] = []
@@ -155,7 +150,6 @@ def render_verb_table() -> str:
155
150
 
156
151
  RENDERERS: dict[str, Callable[[], str]] = {
157
152
  "defenses-table": render_defenses_table,
158
- "alert-kinds": render_alert_kinds_list,
159
153
  "detector-list": render_detector_list,
160
154
  "event-kinds": render_event_kinds_list,
161
155
  "config-schema": render_config_schema_table,
@@ -112,8 +112,10 @@ def emit_transient_error_detected(
112
112
  raw: str,
113
113
  ) -> None:
114
114
  """Emit detection of a transient agent error (rate limit / 5xx / timeout)."""
115
+ from agent_runner._redact import redact_secrets
115
116
  from agent_runner.events import TRANSIENT_ERROR_DETECTED, emit
116
117
 
118
+ raw = redact_secrets(raw)
117
119
  emit(
118
120
  log_dir,
119
121
  TRANSIENT_ERROR_DETECTED,
@@ -234,12 +236,15 @@ def emit_round_grace_kill(
234
236
  *,
235
237
  round_num: int,
236
238
  grace_s: int,
237
- live_children: list[str] | None = None,
239
+ live_children: list[dict] | None = None,
238
240
  ) -> None:
239
241
  """Emit when the subprocess was killed because the grace-after-result timer
240
242
  expired AND the agent's process group had no live worker processes left
241
243
  (a genuine hang). Distinct from round_grace_extended (grace elapsed but a
242
244
  worker was still running) and round_timeout_kill (wall-clock exceeded).
245
+
246
+ live_children: list of ``{"name": <exe basename>, "pid": <int>}`` dicts
247
+ (0.1.40+; previously list of cmdline strings).
243
248
  """
244
249
  from agent_runner.events import ROUND_GRACE_KILL, emit
245
250
 
@@ -257,11 +262,18 @@ def emit_round_grace_extended(
257
262
  *,
258
263
  round_num: int,
259
264
  grace_s: int,
260
- live_children: list[str],
265
+ live_children: list[dict],
266
+ ignored_children: list[dict] | None = None,
261
267
  ) -> None:
262
268
  """Emit when the grace-after-result timer expired but the agent still had
263
269
  live worker processes (e.g. a backgrounded build), so the round was NOT
264
270
  killed; it continues until it finishes or hits round_timeout_s.
271
+
272
+ live_children: list of ``{"name": <exe basename>, "pid": <int>}`` dicts
273
+ (0.1.40+; previously list of cmdline strings).
274
+ ignored_children: list of ``{"name": ..., "pid": ..., "matched": <pattern>}``
275
+ dicts for children that matched a grace_kill_ignore_patterns entry
276
+ and were excluded from the liveness count (0.1.40+; previously cmdline strings).
265
277
  """
266
278
  from agent_runner.events import ROUND_GRACE_EXTENDED, emit
267
279
 
@@ -271,6 +283,7 @@ def emit_round_grace_extended(
271
283
  round_num=round_num,
272
284
  grace_s=grace_s,
273
285
  live_children=live_children,
286
+ ignored_children=ignored_children or [],
274
287
  )
275
288
 
276
289
 
@@ -0,0 +1,102 @@
1
+ """Mask secret-bearing tokens in semi-trusted free text before it is persisted
2
+ to durable, default-readable event logs (events-*.jsonl).
3
+
4
+ Single source of truth for secret-shaping. Pure, dependency-free, idempotent.
5
+ BEST-EFFORT: applied only to free-text excerpts (transient error output, hook
6
+ exception messages/tracebacks), never as the sole control. The grace-kill child
7
+ fields avoid this entirely by storing basename+pid, not argv.
8
+
9
+ Patterns are length/charset-anchored so benign argv (sk-report.md, psql -h db,
10
+ 'Basic auth disabled') passes through unchanged.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+
17
+ _MASK = "<redacted>"
18
+
19
+ _LONG_FLAGS = [
20
+ "--token",
21
+ "--password",
22
+ "--passwd",
23
+ "--api-key",
24
+ "--apikey",
25
+ "--secret",
26
+ "--secret-access-key",
27
+ "--access-key",
28
+ "--auth",
29
+ "--authorization",
30
+ "--client-secret",
31
+ "--aws-session-token",
32
+ "--bearer",
33
+ "--auth-token",
34
+ "--private-key",
35
+ ]
36
+ # (prefix, min-tail-len) — anchored so short filenames don't match.
37
+ _PREFIX_RES = [
38
+ r"sk-ant-[A-Za-z0-9\-_]{16,}",
39
+ r"sk-[A-Za-z0-9]{16,}",
40
+ r"ghp_[A-Za-z0-9]{20,}",
41
+ r"gho_[A-Za-z0-9]{20,}",
42
+ r"ghs_[A-Za-z0-9]{20,}",
43
+ r"ghu_[A-Za-z0-9]{20,}",
44
+ r"github_pat_[A-Za-z0-9_]{20,}",
45
+ r"xox[bpars]-[A-Za-z0-9-]{10,}",
46
+ r"xapp-[A-Za-z0-9-]{10,}",
47
+ r"AKIA[0-9A-Z]{16}",
48
+ r"ASIA[0-9A-Z]{16}",
49
+ r"AIza[0-9A-Za-z_\-]{35}",
50
+ r"glpat-[A-Za-z0-9_\-]{20}",
51
+ r"ya29\.[A-Za-z0-9._\-]{20,}",
52
+ r"(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}",
53
+ r"npm_[A-Za-z0-9]{20,}",
54
+ r"hf_[A-Za-z0-9]{20,}",
55
+ ]
56
+
57
+ _FLAG_RE = re.compile(r"(?i)(" + "|".join(re.escape(f) for f in _LONG_FLAGS) + r")(\s+|=)(\S+)")
58
+ # Short HTTP-basic flag `-u user:pass` (curl/wget). Case-SENSITIVE so `-U`
59
+ # (psql/pg username) is left alone; a colon is required so `sort -u file` and
60
+ # bare `-u username` (no password) are not masked; and the value must NOT be a
61
+ # URL (`://`) — a `-u <url>` is left for _URL_USERINFO_RE, which masks only the
62
+ # userinfo and preserves the host (e.g. redis://<redacted>@cache:6379/0).
63
+ _SHORT_USER_RE = re.compile(r"(?<![\w-])-u(\s+|=)(?!\S*://)(\S+:\S+)")
64
+ _HEADER_NAME_RE = re.compile(
65
+ r"(?im)\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie|"
66
+ r"X-Api-Key|X-Auth-Token|X-Amz-Security-Token)(\s*:\s*)([^\r\n]+)"
67
+ )
68
+ _SCHEME_RE = re.compile(
69
+ r"\b(Bearer|Basic|Token|ApiKey|Digest|Negotiate|NTLM)(\s+)([A-Za-z0-9+/=._\-]{12,})"
70
+ )
71
+ _URL_USERINFO_RE = re.compile(r"(?i)([a-z][a-z0-9+.\-]*://)([^/\s@]+)@")
72
+ _URL_QUERY_RE = re.compile(
73
+ r"(?i)([?&#](?:access_token|api_?key|token|auth|secret|sig|signature|"
74
+ r"password|client_secret|x-amz-security-token|x-amz-signature)=)([^&#\s]+)"
75
+ )
76
+ _ENV_RE = re.compile(
77
+ r"(?i)((?:(?<=\s)|^)[A-Za-z_][A-Za-z0-9_]*"
78
+ r"(?:PASSWORD|PASSWD|PWD|TOKEN|SECRET|API[_-]?KEY|ACCESS[_-]?KEY|CREDENTIAL|PRIVATE[_-]?KEY)"
79
+ r"[A-Za-z0-9_]*)=(\S+)"
80
+ )
81
+ _PREFIX_RE = re.compile(r"(?<![A-Za-z0-9])(?:" + "|".join(_PREFIX_RES) + r")")
82
+ _JWT_RE = re.compile(r"\beyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+")
83
+ _PEM_RE = re.compile(
84
+ r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.DOTALL
85
+ )
86
+
87
+
88
+ def redact_secrets(text: str) -> str:
89
+ """Return *text* with secret-bearing tokens replaced by ``<redacted>``."""
90
+ if not text:
91
+ return text
92
+ out = _PEM_RE.sub(_MASK, text)
93
+ out = _ENV_RE.sub(rf"\1={_MASK}", out)
94
+ out = _FLAG_RE.sub(rf"\1\2{_MASK}", out)
95
+ out = _SHORT_USER_RE.sub(rf"-u\1{_MASK}", out)
96
+ out = _HEADER_NAME_RE.sub(rf"\1\2{_MASK}", out)
97
+ out = _SCHEME_RE.sub(rf"\1\2{_MASK}", out)
98
+ out = _URL_USERINFO_RE.sub(rf"\1{_MASK}@", out)
99
+ out = _URL_QUERY_RE.sub(rf"\1{_MASK}", out)
100
+ out = _JWT_RE.sub(_MASK, out)
101
+ out = _PREFIX_RE.sub(_MASK, out)
102
+ return out
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.38'
22
- __version_tuple__ = version_tuple = (0, 1, 38)
21
+ __version__ = version = '0.1.40'
22
+ __version_tuple__ = version_tuple = (0, 1, 40)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -11,6 +11,7 @@ Defenses encoded here:
11
11
  from __future__ import annotations
12
12
 
13
13
  import os
14
+ import re
14
15
  import signal
15
16
  import subprocess # noqa: TID251 — sanctioned subprocess caller
16
17
  import time
@@ -30,7 +31,7 @@ class RunResult:
30
31
  timed_out: bool
31
32
  pid: int
32
33
  killed_for_grace: bool = False
33
- grace_kill_children: list[str] = field(default_factory=list)
34
+ grace_kill_children: list[dict] = field(default_factory=list)
34
35
 
35
36
 
36
37
  def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
@@ -57,29 +58,51 @@ def _kill_pgroup(proc: subprocess.Popen) -> None:
57
58
  pass
58
59
 
59
60
 
60
- def _live_children(proc: subprocess.Popen, *, max_n: int = 5, max_len: int = 120) -> list[str]:
61
- """Cmdlines of live (non-zombie) descendant processes of ``proc``.
62
-
63
- Empty when ``proc`` has no live workers (a stuck agent that emitted
64
- type=result then hung). Non-empty when the round backgrounded work (e.g. a
65
- build) still running. Bounded so the resulting event stays small.
61
+ def _live_children(
62
+ proc: subprocess.Popen,
63
+ *,
64
+ ignore_patterns: list[re.Pattern[str]] | None = None,
65
+ max_n: int = 5,
66
+ ) -> tuple[list[dict], list[dict]]:
67
+ """Live (non-zombie) descendants of ``proc``, split into ``(live, ignored)``.
68
+
69
+ Each entry is ``{"name": <executable basename>, "pid": <int>}``; an ignored
70
+ entry also carries ``"matched": <pattern str>``. We store only basename+pid,
71
+ NOT argv — process arguments are where secrets leak (PGPASSWORD=…, --api-key
72
+ …, redis://:pass@…) and these lists are persisted to events-*.jsonl.
73
+ Ignore-pattern MATCHING runs against the full cmdline (detection unchanged);
74
+ only what we STORE is minimized.
66
75
  """
67
76
  try:
68
77
  parent = psutil.Process(proc.pid)
69
78
  except (psutil.NoSuchProcess, psutil.AccessDenied):
70
- return []
71
- out: list[str] = []
79
+ return [], []
80
+ live: list[dict] = []
81
+ ignored: list[dict] = []
72
82
  for child in parent.children(recursive=True):
73
83
  try:
74
84
  if child.status() == psutil.STATUS_ZOMBIE:
75
85
  continue
76
- line = " ".join(child.cmdline()) or child.name()
86
+ argv = child.cmdline()
87
+ full = " ".join(argv) or child.name() # MATCHING only
88
+ name = (Path(argv[0]).name if argv else "") or child.name()
89
+ pid = child.pid
77
90
  except (psutil.NoSuchProcess, psutil.AccessDenied):
78
91
  continue
79
- out.append(line[:max_len])
80
- if len(out) >= max_n:
92
+ matched = None
93
+ if ignore_patterns:
94
+ for p in ignore_patterns:
95
+ if p.search(full):
96
+ matched = p.pattern
97
+ break
98
+ if matched is not None:
99
+ if len(ignored) < max_n:
100
+ ignored.append({"name": name, "pid": pid, "matched": matched})
101
+ elif len(live) < max_n:
102
+ live.append({"name": name, "pid": pid})
103
+ if len(live) >= max_n and len(ignored) >= max_n:
81
104
  break
82
- return out
105
+ return live, ignored
83
106
 
84
107
 
85
108
  # Exact compact bytes — matches claude CLI's no-whitespace JSONL output.
@@ -99,7 +122,8 @@ def run(
99
122
  max_grace_after_result_s: int = 0,
100
123
  progress_callback: Callable[[dict], None] | None = None,
101
124
  progress_interval_s: int = 0,
102
- on_grace_extended: Callable[[list[str]], None] | None = None,
125
+ on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
126
+ grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
103
127
  ) -> RunResult:
104
128
  """Spawn the agent subprocess and wait for exit or timeout.
105
129
 
@@ -116,6 +140,10 @@ def run(
116
140
  progress_interval_s seconds with a dict of log stats (log_size_kb,
117
141
  last_write_age_s, wall_age_s). Keeps agent_runtime event-free; callers
118
142
  build the callback to emit events.
143
+
144
+ grace_kill_ignore_patterns: pre-compiled regex patterns; child cmdlines
145
+ matching any pattern (re.search) are excluded from the liveness count
146
+ (persistent helpers that aren't real workers). None = no filtering.
119
147
  """
120
148
  argv = _build_argv(command, prompt_arg_template, prompt)
121
149
  env = {**os.environ, **env_extra}
@@ -157,13 +185,13 @@ def run(
157
185
  except OSError:
158
186
  pass # log not flushed yet; check next tick
159
187
  if result_seen_at is not None and now - result_seen_at > max_grace_after_result_s:
160
- children = _live_children(proc)
161
- if children:
188
+ live, ignored = _live_children(proc, ignore_patterns=grace_kill_ignore_patterns)
189
+ if live:
162
190
  # Busy: a backgrounded worker is still running. Don't
163
191
  # reap — defer to the wall-clock ceiling. Signal once.
164
192
  if not grace_extended_emitted:
165
193
  if on_grace_extended is not None:
166
- on_grace_extended(children)
194
+ on_grace_extended(live, ignored)
167
195
  grace_extended_emitted = True
168
196
  else:
169
197
  _kill_pgroup(proc)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  import tomllib
6
7
  from dataclasses import dataclass, field
7
8
  from pathlib import Path
@@ -40,6 +41,12 @@ class RuntimeConfig:
40
41
  fresh_eyes_every_n: int | None = None # None = disabled
41
42
  dry_run: bool = False
42
43
  max_grace_after_result_s: int = 0 # 0 = disabled
44
+ grace_kill_ignore_patterns: list[str] = field(default_factory=list)
45
+ """Regex patterns (re.search) tested against each child process's joined
46
+ cmdline. Matching children are excluded from the grace-kill liveness
47
+ check — for persistent helper subprocesses (e.g. claude's shell-snapshot
48
+ bash) that would otherwise defeat max_grace_after_result_s. Empty list
49
+ = no filtering (0.1.38 behavior preserved)."""
43
50
 
44
51
 
45
52
  @dataclass(frozen=True)
@@ -221,6 +228,25 @@ def _validate_remote_failure_tolerance(value: Any) -> int:
221
228
  return v
222
229
 
223
230
 
231
+ def _validate_regex_list(value: Any, *, field: str) -> list[str]:
232
+ """Validate a list of regex pattern strings (each must compile). Returns the
233
+ raw strings unchanged; callers compile when they need ``re.Pattern`` objects."""
234
+ if not isinstance(value, list):
235
+ raise ValueError(f"{field}: expected a list of regex strings, got {type(value).__name__}")
236
+ out: list[str] = []
237
+ for p in value:
238
+ if not isinstance(p, str):
239
+ raise ValueError(
240
+ f"{field}: each pattern must be a string, got {type(p).__name__}: {p!r}"
241
+ )
242
+ try:
243
+ re.compile(p)
244
+ except re.error as e:
245
+ raise ValueError(f"{field}: invalid regex {p!r}: {e}") from e
246
+ out.append(p)
247
+ return out
248
+
249
+
224
250
  _PHASE_OVERRIDE_ALLOWED_FIELDS = frozenset(
225
251
  {
226
252
  "round_timeout_s",
@@ -392,6 +418,10 @@ def load_config(toml_path: Path) -> Config:
392
418
  runtime_d.get("max_grace_after_result_s", 0),
393
419
  field="runtime.max_grace_after_result_s",
394
420
  ),
421
+ grace_kill_ignore_patterns=_validate_regex_list(
422
+ runtime_d.get("grace_kill_ignore_patterns", []),
423
+ field="runtime.grace_kill_ignore_patterns",
424
+ ),
395
425
  )
396
426
  prompt_d = raw.get("prompt", {})
397
427
  mode = prompt_d.get("context_injection_mode", "prepend")
@@ -35,6 +35,7 @@ from pathlib import Path
35
35
  from typing import Any, Protocol, runtime_checkable
36
36
 
37
37
  from agent_runner import events
38
+ from agent_runner._redact import redact_secrets
38
39
  from agent_runner._registry import ensure_unique
39
40
 
40
41
  _HEAD_BYTES = 1024
@@ -201,7 +202,7 @@ def run_serve_startup_hooks(cfg: Any, log_dir: Path) -> bool:
201
202
  hook(cfg)
202
203
  except Exception as e: # noqa: BLE001 — hook is plugin contract; any failure aborts serve
203
204
  exc_type = type(e).__name__
204
- exc_msg = str(e)[:200]
205
+ exc_msg = redact_secrets(str(e))[:200]
205
206
  print(
206
207
  f"agent-runner: serve_startup_hook {hook.name} failed: {exc_type}: {exc_msg}",
207
208
  file=sys.stderr,
@@ -232,6 +233,6 @@ def _summarize_error(exc: BaseException, tb: str) -> dict[str, str]:
232
233
  trimmed = tb[:_HEAD_BYTES] + _TRUNC_MARKER + tb[-_TAIL_BYTES:]
233
234
  return {
234
235
  "error_type": type(exc).__name__,
235
- "error_message": str(exc),
236
- "traceback": trimmed,
236
+ "error_message": redact_secrets(str(exc)),
237
+ "traceback": redact_secrets(trimmed),
237
238
  }
@@ -16,6 +16,7 @@ work_dir = "."
16
16
  log_dir = "~/.agent-runner/{project}/logs"
17
17
  round_timeout_s = 1800
18
18
  restart_delay_s = 3
19
+ grace_kill_ignore_patterns = ['\.claude/shell-snapshots/snapshot-bash-']
19
20
 
20
21
  [prompt]
21
22
  file = "./prompts/main.md"
@@ -10,6 +10,7 @@ import hashlib
10
10
  import json
11
11
  import os
12
12
  import random
13
+ import re
13
14
  import sys
14
15
  import time
15
16
  import traceback as tb_mod
@@ -466,12 +467,15 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
466
467
  **stats,
467
468
  )
468
469
 
469
- def _grace_extended_emit(children: list[str]) -> None:
470
+ grace_kill_ignore_patterns = [re.compile(p) for p in cfg.runtime.grace_kill_ignore_patterns]
471
+
472
+ def _grace_extended_emit(live: list[str], ignored: list[str]) -> None:
470
473
  api.emit_round_grace_extended(
471
474
  log_dir,
472
475
  round_num=round_num,
473
476
  grace_s=cfg.runtime.max_grace_after_result_s,
474
- live_children=children,
477
+ live_children=live,
478
+ ignored_children=ignored,
475
479
  )
476
480
 
477
481
  result = agent_runtime.run(
@@ -485,6 +489,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
485
489
  progress_callback=_progress_emit,
486
490
  progress_interval_s=cfg.monitor.round_progress_interval_s,
487
491
  on_grace_extended=_grace_extended_emit,
492
+ grace_kill_ignore_patterns=grace_kill_ignore_patterns,
488
493
  )
489
494
  events.emit(
490
495
  log_dir,
@@ -34,8 +34,15 @@ are shared between `peek`, `watch`, and `monitor`.
34
34
  Scaffold a new project: writes `agent-runner.toml`, `prompts/main.md`, and
35
35
  appends `logs/` to `.gitignore`. By default also creates a git commit.
36
36
 
37
+ Flags:
38
+
39
+ - `--preset {claude,aider,gemini}` — agent CLI preset to scaffold (default: `claude`)
40
+ - `--force` — overwrite an existing `agent-runner.toml`
41
+ - `--no-commit` — skip the initial git commit
42
+
37
43
  ```bash
38
- agent-runner init # default: commit
44
+ agent-runner init # default: claude preset, commit
45
+ agent-runner init --preset aider # aider preset
39
46
  agent-runner init --no-commit # skip the commit
40
47
  agent-runner init --force # overwrite an existing toml
41
48
  ```
@@ -94,6 +101,9 @@ back to package-only mode automatically.
94
101
  `--no-restart` forces package-only even on a systemd --user host (upgrade the
95
102
  package now, restart your service yourself).
96
103
 
104
+ Operator walkthrough (per-deployment decision table, rollback, failure modes,
105
+ postmortem trail): see `docs/runbook.md` § "Upgrading agent-runner".
106
+
97
107
  ## Observation
98
108
 
99
109
  ### `agent-runner peek [flags]`
@@ -133,7 +143,7 @@ agent-runner events --kind transient_error_backoff_capped --tail
133
143
 
134
144
  `peek` in a clear-and-refresh loop. Default 2s interval. Stop with Ctrl-C.
135
145
 
136
- ### `agent-runner monitor [--host SSH-ALIAS] [--interval N] [--json]`
146
+ ### `agent-runner monitor [--host SSH-ALIAS] [--interval N] [--mode MODE] [--port PORT] [--json]`
137
147
 
138
148
  Anomaly-detection daemon. Runs the 12 detectors against the live state on every
139
149
  poll. Without `--host`, watches local logs at default 30s interval. With
@@ -143,15 +153,25 @@ When OAuth-fail or disk-critical detectors fire, monitor automatically issues a
143
153
  graceful stop (locally via `api.stop`; remotely via `ssh <host> 'agent-runner stop'`).
144
154
  Override with `[monitor]` config block (see configuration.md).
145
155
 
156
+ Flags:
157
+
158
+ - `--mode {anomaly,narrate,events,http}` — output mode (default: `anomaly`). `narrate`
159
+ streams a human-readable narrative; `events` streams raw event JSON; `http` serves
160
+ a local progress page.
161
+ - `--port PORT` — HTTP port for `--mode http` (default: `8765`, local-only).
162
+ - `--host SSH-ALIAS` — watch a remote agent-runner via ssh (anomaly mode only).
163
+
146
164
  ```bash
147
- agent-runner monitor # local
165
+ agent-runner monitor # local anomaly mode
148
166
  agent-runner monitor --host pi # remote
167
+ agent-runner monitor --mode narrate # streaming narrative
168
+ agent-runner monitor --mode http --port 9000 # HTTP progress page on port 9000
149
169
  agent-runner monitor --json | jq -c # pipe alerts to a downstream consumer
150
170
  ```
151
171
 
152
172
  ## 中文摘要
153
173
 
154
- 16 个动词:`init / install / uninstall / start / stop / kill / cancel / restart / status / round / serve / upgrade / peek / watch / events / monitor`。
174
+ 16 个动词,完整列表见上方动词表(自动生成)。
155
175
 
156
176
  观察类(peek/watch/monitor)三视角对称,全部共用 `--round / --log / --events / --select / --json` 下钻参数。
157
177
 
@@ -47,6 +47,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
47
47
  | `fresh_eyes_every_n` | `int | None` | None |
48
48
  | `dry_run` | `bool` | False |
49
49
  | `max_grace_after_result_s` | `int` | 0 |
50
+ | `grace_kill_ignore_patterns` | `list[str]` | [] |
50
51
 
51
52
  ### `[prompt]`
52
53
 
@@ -200,6 +201,10 @@ Unconfigured phases (and configs without `[phases]`) keep using the global
200
201
 
201
202
  ## `[monitor]` (optional, defaults shown)
202
203
 
204
+ > Authoritative field-level defaults are in the generated schema table above
205
+ > (`[monitor]` section). The snippet below shows only the fields most commonly
206
+ > customised, with operational notes.
207
+
203
208
  ```toml
204
209
  [monitor]
205
210
  auto_stop_on = ["oauth_fail", "disk_critical"]
@@ -207,9 +212,10 @@ round_progress_interval_s = 0 # 0 = disabled; set >0 to emit round_progress hea
207
212
  # supervisor_stale_threshold_s = 2700 # unset = round_timeout_s * 1.5; 0 = disable
208
213
 
209
214
  [monitor.host_health]
210
- mem_avail_min_mb = 200 # mem_pressure fires when mem_available_mb < this
211
- disk_warning_pct = 90.0 # disk_warning fires when disk_used_pct >= this
212
- disk_critical_pct = 95.0 # disk_critical fires when disk_used_pct >= this
215
+ # Thresholds for mem_pressure / disk_warning / disk_critical. Defaults are
216
+ # authoritative in the config-schema table above set a field here only to
217
+ # override. (mem_avail_min_mb: mem_pressure when mem_available_mb below it;
218
+ # disk_warning_pct / disk_critical_pct: fire when disk_used_pct at/above.)
213
219
  ```
214
220
 
215
221
  Comment out individual entries to disable; e.g. `# auto_stop_on = []` disables
@@ -140,7 +140,7 @@ token breakdown + cost (where the underlying CLI exposes it).
140
140
  ```
141
141
 
142
142
  Use as input to a cost-tracking detector or external billing reconciler.
143
- See `docs/migrations/0.1.28.md` for the current 12-field payload schema
143
+ See `docs/migrations/0.1.28.md` for the current payload schema
144
144
  (includes `cache_creation_tokens`, `tool_call_count`, `phase`, `success`)
145
145
  plus a consumer dispatcher sketch. Aggregation (rollups, budget warnings)
146
146
  is the consumer's responsibility — agent-runner emits raw per-round
@@ -0,0 +1,74 @@
1
+ # Migrating to 0.1.39
2
+
3
+ ## TL;DR
4
+
5
+ ```bash
6
+ pip install --upgrade cli-agent-runner==0.1.39
7
+ ```
8
+
9
+ **Claude users running 0.1.38**: add one line to `[runtime]` to unblock
10
+ grace-kill against claude's persistent shell-snapshot helper (see below).
11
+ New `agent-runner init --preset=claude` scaffolds get this automatically.
12
+
13
+ **Everyone else**: no action.
14
+
15
+ ## Persistent-helper exclusion (the live fix)
16
+
17
+ 0.1.38's grace-kill liveness check was correctly conservative — it refused to
18
+ reap a round with live worker children — but claude's `-p` mode keeps a
19
+ persistent Bash-tool shell-snapshot subprocess alive for the whole session.
20
+ That subprocess is not doing work; it's idle infrastructure. 0.1.38 saw it as
21
+ a live worker and deferred every post-result hang to `round_timeout_s` instead
22
+ of reaping at `max_grace_after_result_s`. This is the "persistent-helper
23
+ caveat" 0.1.38's migration doc flagged.
24
+
25
+ 0.1.39 adds `[runtime] grace_kill_ignore_patterns` — a list of regex patterns;
26
+ child cmdlines matching any pattern (via `re.search`) are excluded from the
27
+ liveness count. `presets/claude.toml` ships a default pattern matching
28
+ claude's shell-snapshot.
29
+
30
+ ### Existing claude operators — one line
31
+
32
+ Add to your `[runtime]` block:
33
+
34
+ ```toml
35
+ [runtime]
36
+ grace_kill_ignore_patterns = ['\.claude/shell-snapshots/snapshot-bash-']
37
+ ```
38
+
39
+ Or run `agent-runner init --preset=claude` in a scratch directory and diff
40
+ the generated `agent-runner.toml` against yours.
41
+
42
+ After the change, post-result hangs are reaped at `max_grace_after_result_s`.
43
+ Without it, they continue to defer to `round_timeout_s` (the 0.1.38 behavior).
44
+
45
+ ### Verifying the pattern is firing
46
+
47
+ The `round_grace_extended` event payload gains `ignored_children` listing
48
+ cmdlines that matched a pattern. Use it to:
49
+
50
+ - confirm the shell-snapshot is being filtered (`ignored_children` non-empty)
51
+ - catch the day claude renames its helper (`live_children` shows a new
52
+ unfiltered persistent process)
53
+
54
+ ### Other presets
55
+
56
+ `aider.toml` and `gemini.toml` ship no default patterns. Add operator-specific
57
+ patterns to your own `agent-runner.toml` if needed.
58
+
59
+ ## SSOT consistency hardening (also in 0.1.39)
60
+
61
+ A new invariant `test_doc_claims_match_ssot` gates documented counts
62
+ (detectors / defenses / verbs) and config value-sets against code SSOT.
63
+ `commands.md` documents `monitor --mode/--port` and `init --preset`.
64
+ Redundant count guards collapsed to one canonical tripwire each. The unused
65
+ `alert-kinds` docgen renderer was removed. No action required.
66
+
67
+ ## What did NOT change
68
+
69
+ - The 0.1.38 grace-kill liveness semantics (still process-group-based;
70
+ patterns are an exclusion filter on top).
71
+ - `round_grace_kill` (still fires only when the post-filter live set is empty).
72
+ - `round_timeout_s` (still the hard ceiling).
73
+ - `max_grace_after_result_s` (knob unchanged).
74
+ - For non-claude deployments: zero behavior change.