cli-agent-runner 0.1.39__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 (228) hide show
  1. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.gitignore +3 -0
  2. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/CHANGELOG.md +11 -1
  3. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_emit.py +13 -7
  5. cli_agent_runner-0.1.40/agent_runner/_redact.py +102 -0
  6. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_version.py +2 -2
  7. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/agent_runtime.py +27 -19
  8. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/hooks.py +4 -3
  9. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/commands.md +3 -0
  10. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/configuration.md +4 -3
  11. cli_agent_runner-0.1.40/docs/migrations/0.1.40.md +42 -0
  12. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/runbook.md +12 -1
  13. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_grace_kill_emission.py +3 -3
  14. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime_grace.py +47 -5
  15. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_claude_error_detector.py +17 -0
  16. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_hooks.py +9 -0
  17. cli_agent_runner-0.1.40/tests/unit/test_redact.py +136 -0
  18. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.codecov.yml +0 -0
  19. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  20. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/workflows/ci.yml +0 -0
  24. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.github/workflows/release.yml +0 -0
  25. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/.vulture-whitelist.py +0 -0
  26. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/CODE_OF_CONDUCT.md +0 -0
  27. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/CONTRIBUTING.md +0 -0
  28. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/LICENSE +0 -0
  29. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/README.md +0 -0
  30. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/README.zh.md +0 -0
  31. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/SECURITY.md +0 -0
  32. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/__init__.py +0 -0
  33. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_docgen.py +0 -0
  34. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_registry.py +0 -0
  35. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_substrate.py +0 -0
  36. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/_throttle.py +0 -0
  37. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/api.py +0 -0
  38. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/api_types.py +0 -0
  39. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/__init__.py +0 -0
  40. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/_constants.py +0 -0
  41. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  42. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/builtin_plugins/gemini.py +0 -0
  43. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/__init__.py +0 -0
  44. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/__main__.py +0 -0
  45. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/common.py +0 -0
  46. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/events_cmd.py +0 -0
  47. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/init_cmd.py +0 -0
  48. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/install_cmd.py +0 -0
  49. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/monitor_cmd.py +0 -0
  50. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/peek_cmd.py +0 -0
  51. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/round_cmd.py +0 -0
  52. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/serve_cmd.py +0 -0
  53. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/service_cmd.py +0 -0
  54. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/cli/upgrade_cmd.py +0 -0
  55. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/config.py +0 -0
  56. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/context_store.py +0 -0
  57. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/defenses.py +0 -0
  58. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/detector_helpers.py +0 -0
  59. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/events.py +0 -0
  60. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/http_progress.py +0 -0
  61. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/lifecycle.py +0 -0
  62. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/metrics.py +0 -0
  63. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/monitor.py +0 -0
  64. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/presets/__init__.py +0 -0
  65. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/presets/aider.toml +0 -0
  66. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/presets/claude.toml +0 -0
  67. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/presets/gemini.toml +0 -0
  68. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/prompt_loader.py +0 -0
  69. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/round_log.py +0 -0
  70. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/round_view.py +0 -0
  71. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/runner.py +0 -0
  72. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/scaffold.py +0 -0
  73. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/service_unit.py +0 -0
  74. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/startup_check.py +0 -0
  75. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/agent_runner/vcs_state.py +0 -0
  76. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/build.sh +0 -0
  77. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/deploy/example-agent-runner.toml +0 -0
  78. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/deploy/launchd.plist.tmpl +0 -0
  79. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/deploy/run-loop.sh +0 -0
  80. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/deploy/systemd.service.tmpl +0 -0
  81. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/README.md +0 -0
  82. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/architecture.md +0 -0
  83. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/events.md +0 -0
  84. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/long-running-agents.md +0 -0
  85. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/marketing/README.md +0 -0
  86. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/marketing/promo-cn.html +0 -0
  87. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.16.md +0 -0
  88. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.17.md +0 -0
  89. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.19.md +0 -0
  90. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.20.md +0 -0
  91. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.21.md +0 -0
  92. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.22.md +0 -0
  93. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.23.md +0 -0
  94. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.24.md +0 -0
  95. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.25.md +0 -0
  96. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.26.md +0 -0
  97. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.27.md +0 -0
  98. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.28.md +0 -0
  99. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.29.md +0 -0
  100. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.30.md +0 -0
  101. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.31.md +0 -0
  102. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.32.md +0 -0
  103. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.33.md +0 -0
  104. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.34.md +0 -0
  105. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.35.md +0 -0
  106. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.36.md +0 -0
  107. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.37.md +0 -0
  108. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.38.md +0 -0
  109. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/migrations/0.1.39.md +0 -0
  110. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/plugins.md +0 -0
  111. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/quickstart.md +0 -0
  112. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/recipes/aider.md +0 -0
  113. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/docs/thesis.md +0 -0
  114. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/pyproject.toml +0 -0
  115. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/__init__.py +0 -0
  116. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/_test_helpers.py +0 -0
  117. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/conftest.py +0 -0
  118. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/contract/__init__.py +0 -0
  119. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/contract/test_public_api_surface.py +0 -0
  120. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/__init__.py +0 -0
  121. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/conftest.py +0 -0
  122. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  123. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_install_systemd.py +0 -0
  124. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  125. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  126. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  127. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  128. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  129. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/__init__.py +0 -0
  130. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_bounded_run.py +0 -0
  131. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_context_enricher_namespacing.py +0 -0
  132. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_fresh_eyes_signal.py +0 -0
  133. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_install_dry_run.py +0 -0
  134. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_monitor_seeded.py +0 -0
  135. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_detector_loaded.py +0 -0
  136. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_owned_paths.py +0 -0
  137. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_plugin_real_flow.py +0 -0
  138. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  139. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_scaffold_presets.py +0 -0
  140. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_serve_loop.py +0 -0
  141. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_substrate_fingerprint.py +0 -0
  142. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/integration/test_transient_error_backoff.py +0 -0
  143. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/__init__.py +0 -0
  144. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_architecture.py +0 -0
  145. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_atomic_write_enforced.py +0 -0
  146. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_catalogs.py +0 -0
  147. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_classification_ssot.py +0 -0
  148. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  149. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_docs_generated.py +0 -0
  150. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_entry_points_resolve.py +0 -0
  151. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kind_registry.py +0 -0
  152. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_event_kinds_ssot.py +0 -0
  153. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_events_doc_contract.py +0 -0
  154. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_layer_2_loop_size.py +0 -0
  155. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_module_boundaries.py +0 -0
  156. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_module_sizes.py +0 -0
  157. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_no_ai_signatures.py +0 -0
  158. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  159. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_peek_schema_version.py +0 -0
  160. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  161. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_round_result_stable.py +0 -0
  162. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  163. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/invariants/test_upstream_schema_canary.py +0 -0
  164. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/literate/__init__.py +0 -0
  165. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/literate/parser.py +0 -0
  166. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/literate/test_parser.py +0 -0
  167. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/literate/test_quickstart.py +0 -0
  168. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/__init__.py +0 -0
  169. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime.py +0 -0
  170. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_agent_runtime_progress.py +0 -0
  171. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_assemble_prompt.py +0 -0
  172. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_events_stream.py +0 -0
  173. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_install.py +0 -0
  174. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_observation.py +0 -0
  175. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_read_round_num.py +0 -0
  176. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_resolve_phase.py +0 -0
  177. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_service.py +0 -0
  178. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_api_types.py +0 -0
  179. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_auto_stop_gating.py +0 -0
  180. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli.py +0 -0
  181. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli_common.py +0 -0
  182. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli_init_install.py +0 -0
  183. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli_monitor_http.py +0 -0
  184. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  185. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_cli_upgrade.py +0 -0
  186. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config.py +0 -0
  187. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config_fresh_eyes.py +0 -0
  188. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config_max_rounds.py +0 -0
  189. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config_stop_file.py +0 -0
  190. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  191. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_config_transient_error_action.py +0 -0
  192. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_context_store.py +0 -0
  193. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_defenses.py +0 -0
  194. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_detector_helpers.py +0 -0
  195. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_detector_protocol.py +0 -0
  196. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_docgen.py +0 -0
  197. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_events.py +0 -0
  198. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_events_cmd.py +0 -0
  199. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  200. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_gemini_plugin.py +0 -0
  201. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_hook_failure_isolation.py +0 -0
  202. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_http_progress.py +0 -0
  203. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_init_entry_points.py +0 -0
  204. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_lifecycle.py +0 -0
  205. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_metrics.py +0 -0
  206. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_assembly.py +0 -0
  207. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  208. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  209. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  210. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_detectors.py +0 -0
  211. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_monitor_remote.py +0 -0
  212. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_peek_argparse.py +0 -0
  213. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_peek_select.py +0 -0
  214. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_presets.py +0 -0
  215. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_prompt_loader.py +0 -0
  216. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_round_log_helpers.py +0 -0
  217. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_round_view.py +0 -0
  218. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_runner.py +0 -0
  219. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_runner_throttle.py +0 -0
  220. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_scaffold.py +0 -0
  221. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_serve_cmd_bounded.py +0 -0
  222. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_serve_round_log.py +0 -0
  223. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_serve_sentinel.py +0 -0
  224. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_serve_startup_hooks.py +0 -0
  225. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_service_unit.py +0 -0
  226. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_startup_check.py +0 -0
  227. {cli_agent_runner-0.1.39 → cli_agent_runner-0.1.40}/tests/unit/test_substrate.py +0 -0
  228. {cli_agent_runner-0.1.39 → 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,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [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
9
19
 
10
20
  ### Fixed
11
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-agent-runner
3
- Version: 0.1.39
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
@@ -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,17 +262,18 @@ def emit_round_grace_extended(
257
262
  *,
258
263
  round_num: int,
259
264
  grace_s: int,
260
- live_children: list[str],
261
- ignored_children: list[str] | None = None,
265
+ live_children: list[dict],
266
+ ignored_children: list[dict] | None = None,
262
267
  ) -> None:
263
268
  """Emit when the grace-after-result timer expired but the agent still had
264
269
  live worker processes (e.g. a backgrounded build), so the round was NOT
265
270
  killed; it continues until it finishes or hits round_timeout_s.
266
271
 
267
- ignored_children: cmdlines that matched a grace_kill_ignore_patterns entry
268
- and were excluded from the liveness count — useful for verifying
269
- patterns are firing and for noticing when an upstream CLI changes
270
- its helper path.
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).
271
277
  """
272
278
  from agent_runner.events import ROUND_GRACE_EXTENDED, emit
273
279
 
@@ -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.39'
22
- __version_tuple__ = version_tuple = (0, 1, 39)
21
+ __version__ = version = '0.1.40'
22
+ __version_tuple__ = version_tuple = (0, 1, 40)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -31,7 +31,7 @@ class RunResult:
31
31
  timed_out: bool
32
32
  pid: int
33
33
  killed_for_grace: bool = False
34
- grace_kill_children: list[str] = field(default_factory=list)
34
+ grace_kill_children: list[dict] = field(default_factory=list)
35
35
 
36
36
 
37
37
  def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
@@ -63,35 +63,43 @@ def _live_children(
63
63
  *,
64
64
  ignore_patterns: list[re.Pattern[str]] | None = None,
65
65
  max_n: int = 5,
66
- max_len: int = 120,
67
- ) -> tuple[list[str], list[str]]:
68
- """Cmdlines of live (non-zombie) descendants of ``proc``, split into
69
- ``(live, ignored)``: ``live`` is what counts toward the grace-kill
70
- liveness check; ``ignored`` matched an ``ignore_patterns`` entry and is
71
- excluded (e.g. claude's persistent shell-snapshot helper). Both lists
72
- are bounded by ``max_n``/``max_len`` to keep events small. ``ignore_patterns
73
- is None`` no filtering, ``ignored`` is empty, ``live`` matches 0.1.38.
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.
74
75
  """
75
76
  try:
76
77
  parent = psutil.Process(proc.pid)
77
78
  except (psutil.NoSuchProcess, psutil.AccessDenied):
78
79
  return [], []
79
- live: list[str] = []
80
- ignored: list[str] = []
80
+ live: list[dict] = []
81
+ ignored: list[dict] = []
81
82
  for child in parent.children(recursive=True):
82
83
  try:
83
84
  if child.status() == psutil.STATUS_ZOMBIE:
84
85
  continue
85
- 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
86
90
  except (psutil.NoSuchProcess, psutil.AccessDenied):
87
91
  continue
88
- short = line[:max_len]
89
- if ignore_patterns and any(p.search(line) for p in ignore_patterns):
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:
90
99
  if len(ignored) < max_n:
91
- ignored.append(short)
92
- else:
93
- if len(live) < max_n:
94
- live.append(short)
100
+ ignored.append({"name": name, "pid": pid, "matched": matched})
101
+ elif len(live) < max_n:
102
+ live.append({"name": name, "pid": pid})
95
103
  if len(live) >= max_n and len(ignored) >= max_n:
96
104
  break
97
105
  return live, ignored
@@ -114,7 +122,7 @@ def run(
114
122
  max_grace_after_result_s: int = 0,
115
123
  progress_callback: Callable[[dict], None] | None = None,
116
124
  progress_interval_s: int = 0,
117
- on_grace_extended: Callable[[list[str], list[str]], None] | None = None,
125
+ on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
118
126
  grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
119
127
  ) -> RunResult:
120
128
  """Spawn the agent subprocess and wait for exit or timeout.
@@ -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
  }
@@ -101,6 +101,9 @@ back to package-only mode automatically.
101
101
  `--no-restart` forces package-only even on a systemd --user host (upgrade the
102
102
  package now, restart your service yourself).
103
103
 
104
+ Operator walkthrough (per-deployment decision table, rollback, failure modes,
105
+ postmortem trail): see `docs/runbook.md` § "Upgrading agent-runner".
106
+
104
107
  ## Observation
105
108
 
106
109
  ### `agent-runner peek [flags]`
@@ -212,9 +212,10 @@ round_progress_interval_s = 0 # 0 = disabled; set >0 to emit round_progress hea
212
212
  # supervisor_stale_threshold_s = 2700 # unset = round_timeout_s * 1.5; 0 = disable
213
213
 
214
214
  [monitor.host_health]
215
- mem_avail_min_mb = 200 # mem_pressure fires when mem_available_mb < this
216
- disk_warning_pct = 90.0 # disk_warning fires when disk_used_pct >= this
217
- 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.)
218
219
  ```
219
220
 
220
221
  Comment out individual entries to disable; e.g. `# auto_stop_on = []` disables
@@ -0,0 +1,42 @@
1
+ # Migrating to 0.1.40
2
+
3
+ ## TL;DR
4
+
5
+ ```bash
6
+ pip install --upgrade cli-agent-runner==0.1.40
7
+ ```
8
+
9
+ A security release. No config or behavior change for the supervisor. One
10
+ consumer-visible field-shape change (below).
11
+
12
+ ## Breaking: grace-kill child fields are now objects, not strings
13
+
14
+ `round_grace_extended` / `round_grace_kill` previously carried
15
+ `live_children` / `ignored_children` as lists of command-line **strings**.
16
+ They are now lists of **objects**:
17
+
18
+ - `live_children`: `[{"name": "<exe basename>", "pid": <int>}, ...]`
19
+ - `ignored_children`: `[{"name": ..., "pid": ..., "matched": "<pattern>"}, ...]`
20
+
21
+ If you parse these fields, update your consumer. The change exists because the
22
+ old strings were full process command lines, which could contain a secret
23
+ passed in a child's arguments (a DB password, an API key, a credential URL).
24
+ Storing only basename + pid removes that leak by construction. Ignore-pattern
25
+ matching is unchanged — it still runs against the full command line; only what
26
+ is **stored** is minimized.
27
+
28
+ ## Best-effort redaction of free-text excerpts
29
+
30
+ `transient_error_detected.raw`, `hook_failed.error_message`/`traceback`, and
31
+ `serve_startup_hook_failed.exc_msg` are now passed through a redactor that masks
32
+ auth headers, tokens, credential URLs, `KEY=value` secrets, known key-prefixes,
33
+ JWTs, and PEM blocks. This is **best-effort defense-in-depth**, not a guarantee:
34
+ do not pass secrets as command-line arguments to supervised agents, and treat
35
+ `events-*.jsonl` as sensitive.
36
+
37
+ ## One-time action (if applicable)
38
+
39
+ If pre-0.1.40 `events-*.jsonl` were shipped to a log aggregator, shared, or
40
+ fetched cross-host (`monitor --host`), review them for secret-bearing child
41
+ argv / error excerpts and rotate any exposed credential. Post-upgrade events
42
+ are protected.
@@ -149,7 +149,18 @@ RestartSec=5
149
149
 
150
150
  ## Upgrading agent-runner
151
151
 
152
- `upgrade` detects the deployment topology and takes the safe path for it:
152
+ `upgrade` detects the deployment topology and takes the safe path for it. Pick
153
+ by how your service runs:
154
+
155
+ | Your deployment | How to upgrade | What happens |
156
+ |---|---|---|
157
+ | systemd `--user` (installed via `agent-runner install`) | `agent-runner upgrade [--target X.Y.Z]` | Full auto: graceful stop → pip → smoke → start, auto-rollback on smoke failure |
158
+ | systemd **system** unit / self-managed supervisor | `agent-runner upgrade --no-restart` then restart yourself | Package-only: pip + smoke (no service touched); you run `sudo systemctl restart <unit>` |
159
+ | container / pipx / fully hand-managed | `pip install --upgrade cli-agent-runner` then restart | Manual: you own both the install and the restart |
160
+
161
+ Whichever path: a long-running supervisor only loads the new code **after it
162
+ restarts** — that's why every non-`--user` path ends in a restart you run. The
163
+ three paths are detailed below.
153
164
 
154
165
  ### Path 1 — systemd --user service (installed via `agent-runner install`)
155
166
 
@@ -126,7 +126,7 @@ def test_round_grace_extended_emitted_when_worker_alive(tmp_path: Path) -> None:
126
126
  assert len(extended_events) == 1
127
127
  assert extended_events[0]["round_num"] == 1
128
128
  assert extended_events[0]["grace_s"] == 1
129
- assert any("sleep" in c for c in extended_events[0]["live_children"])
129
+ assert any(c["name"] == "sleep" for c in extended_events[0]["live_children"])
130
130
 
131
131
  # round_grace_kill must NOT appear (round was busy, not idle)
132
132
  grace_kill_events = [e for e in events if e.get("event") == "round_grace_kill"]
@@ -165,9 +165,9 @@ def test_round_grace_extended_carries_ignored_children(tmp_path: Path) -> None:
165
165
  ev = extended_events[0]
166
166
 
167
167
  # The plain sleep goes to live_children (real worker)
168
- assert any("sleep" in c for c in ev["live_children"])
168
+ assert any(c["name"] == "sleep" for c in ev["live_children"])
169
169
  # The exec -a snapshot-bash-test process goes to ignored_children
170
- assert any("snapshot-bash-test" in c for c in ev["ignored_children"])
170
+ assert any(c["name"] == "snapshot-bash-test" for c in ev["ignored_children"])
171
171
 
172
172
  # round_grace_kill must NOT appear (real worker still alive)
173
173
  grace_kill_events = [e for e in events_list if e.get("event") == "round_grace_kill"]
@@ -97,7 +97,7 @@ def test_live_children_lists_backgrounded_child():
97
97
  try:
98
98
  time.sleep(0.5) # let the backgrounded child spawn
99
99
  live, ignored = _live_children(p)
100
- assert any("sleep" in k for k in live)
100
+ assert any(c["name"] == "sleep" for c in live)
101
101
  assert ignored == []
102
102
  finally:
103
103
  os.killpg(p.pid, signal.SIGKILL)
@@ -135,7 +135,7 @@ def test_grace_extended_when_result_but_child_running(tmp_path):
135
135
  assert result.timed_out is True # round_timeout_s backstop reaped it
136
136
  assert len(extended) == 1 # emitted once, not per-tick
137
137
  live, ignored = extended[0]
138
- assert any("sleep" in k for k in live)
138
+ assert any(c["name"] == "sleep" for c in live)
139
139
 
140
140
 
141
141
  def test_grace_kill_after_child_exits_then_idle(tmp_path):
@@ -175,8 +175,8 @@ def test_live_children_splits_on_ignore_pattern():
175
175
  time.sleep(0.5)
176
176
  live, ignored = _live_children(p, ignore_patterns=[re.compile(r"snapshot-bash-")])
177
177
  # One child should match the ignore pattern; the plain sleep goes to live.
178
- assert any("snapshot-bash-" in c or "sleep" in c for c in ignored)
179
- assert any("sleep" in c for c in live)
178
+ assert any(c["name"] in ("snapshot-bash-xyz", "sleep") for c in ignored)
179
+ assert any(c["name"] == "sleep" for c in live)
180
180
  finally:
181
181
  os.killpg(p.pid, signal.SIGKILL)
182
182
  p.wait()
@@ -191,7 +191,7 @@ def test_live_children_no_patterns_preserves_0138_behavior():
191
191
  time.sleep(0.5)
192
192
  live, ignored = _live_children(p) # default None
193
193
  assert ignored == []
194
- assert any("sleep" in c for c in live)
194
+ assert any(c["name"] == "sleep" for c in live)
195
195
  finally:
196
196
  os.killpg(p.pid, signal.SIGKILL)
197
197
  p.wait()
@@ -224,3 +224,45 @@ def test_grace_kill_fires_when_only_ignored_helper_alive(tmp_path):
224
224
  assert result.killed_for_grace is True
225
225
  assert result.duration_s < 4
226
226
  assert extended == [] # no extension emitted; reaped directly
227
+
228
+
229
+ def test_live_children_stores_no_argv_secret():
230
+ """A child with a secret in argv -> stored dict carries only name+pid; the
231
+ secret string never appears."""
232
+ from agent_runner.agent_runtime import _live_children
233
+
234
+ p = subprocess.Popen(
235
+ ["bash", "-c", "exec -a 'tool --api-key sk-ant-SECRET123 x' sleep 30"],
236
+ start_new_session=True,
237
+ )
238
+ try:
239
+ time.sleep(0.5)
240
+ live, ignored = _live_children(p)
241
+ blob = repr(live + ignored)
242
+ assert "sk-ant-SECRET123" not in blob and "--api-key" not in blob
243
+ assert all(set(c) <= {"name", "pid", "matched"} for c in live + ignored)
244
+ finally:
245
+ os.killpg(os.getpgid(p.pid), signal.SIGKILL)
246
+ p.wait()
247
+
248
+
249
+ def test_live_children_matched_records_pattern_not_argv():
250
+ """Ignore-pattern matches on full cmdline; the stored ignored entry records
251
+ the matched pattern string + basename, never the full argv."""
252
+ from agent_runner.agent_runtime import _live_children
253
+
254
+ # Background a subshell that exec-replaces itself with the secret in argv[0].
255
+ # _live_children sees the child process; matching fires on its full cmdline.
256
+ p = subprocess.Popen(
257
+ ["bash", "-c", "bash -c 'exec -a sk-MATCHME sleep 30' &\nwait"],
258
+ start_new_session=True,
259
+ )
260
+ try:
261
+ time.sleep(0.5)
262
+ live, ignored = _live_children(p, ignore_patterns=[re.compile(r"sk-MATCHME")])
263
+ assert ignored and ignored[0]["matched"] == "sk-MATCHME"
264
+ # Only name/pid/matched stored — not the raw cmdline
265
+ assert all(set(c) == {"name", "pid", "matched"} for c in ignored)
266
+ finally:
267
+ os.killpg(os.getpgid(p.pid), signal.SIGKILL)
268
+ p.wait()
@@ -629,3 +629,20 @@ def test_given_claude_log_with_529_overloaded_when_classified_then_api_transient
629
629
  )
630
630
  parsed = _parse_claude_log(log)
631
631
  assert parsed["transient_error"]["classification"] == "api_transient_5xx"
632
+
633
+
634
+ def test_emit_transient_raw_redacted(tmp_path):
635
+ import json
636
+
637
+ from agent_runner._emit import emit_transient_error_detected
638
+
639
+ emit_transient_error_detected(
640
+ tmp_path,
641
+ classification="api_transient_5xx",
642
+ agent="claude",
643
+ reset_at_epoch=0,
644
+ round_num=1,
645
+ raw="boom Bearer sk-ant-LEAKvalue000111 tail",
646
+ )
647
+ payload = json.loads(sorted(tmp_path.glob("events-*.jsonl"))[-1].read_text().splitlines()[-1])
648
+ assert "sk-ant-LEAKvalue000111" not in payload["raw"] and "<redacted>" in payload["raw"]
@@ -177,3 +177,12 @@ def test_dry_run_propagated_to_hook_context(tmp_path) -> None:
177
177
 
178
178
  assert captured, "post_round_hook was never called"
179
179
  assert captured[0].dry_run is True
180
+
181
+
182
+ def test_summarize_error_redacts_message_and_traceback():
183
+ from agent_runner.hooks import _summarize_error
184
+
185
+ exc = RuntimeError("connect failed: postgresql://svc:S3cr3tPw0rd@db:5432/app")
186
+ out = _summarize_error(exc, tb="trace https://x-token:ghp_aaaaaaaaaaaaaaaaaaaa@h line 1")
187
+ assert "S3cr3tPw0rd" not in out["error_message"]
188
+ assert "ghp_aaaaaaaaaaaaaaaaaaaa" not in out["traceback"]