cli-agent-runner 0.3.0__tar.gz → 0.3.2__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 (404) hide show
  1. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/workflows/ci.yml +6 -1
  2. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.vulture-whitelist.py +22 -0
  3. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CHANGELOG.md +15 -0
  4. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/PKG-INFO +4 -1
  5. cli_agent_runner-0.3.2/agent_runner/__init__.py +224 -0
  6. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/__init__.py +16 -0
  7. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/memory.py +30 -0
  8. cli_agent_runner-0.3.2/agent_runner/_emit/plugins.py +76 -0
  9. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_observe.py +16 -2
  10. cli_agent_runner-0.3.2/agent_runner/_plugin_checksum.py +62 -0
  11. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_plugin_manifest.py +48 -4
  12. cli_agent_runner-0.3.2/agent_runner/_plugin_sandbox.py +535 -0
  13. cli_agent_runner-0.3.2/agent_runner/_registry.py +69 -0
  14. cli_agent_runner-0.3.2/agent_runner/_sandbox_probe.py +326 -0
  15. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_version.py +2 -2
  16. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/agent_runtime.py +20 -0
  17. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/api.py +7 -0
  18. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/api_types.py +12 -0
  19. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_round.py +219 -6
  20. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/common.py +16 -13
  21. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/doctor_cmd.py +20 -0
  22. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/peek_cmd.py +7 -2
  23. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/serve_cmd.py +39 -4
  24. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/loader.py +6 -4
  25. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/models.py +9 -0
  26. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/parsers.py +62 -3
  27. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/validators.py +13 -0
  28. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/events.py +7 -0
  29. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/hooks.py +153 -10
  30. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/host_health.py +22 -0
  31. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/runner.py +3 -1
  32. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/architecture.md +7 -0
  33. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/configuration.md +6 -2
  34. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/memory-efficiency.md +11 -0
  35. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/plugins.md +15 -3
  36. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/thesis.md +1 -1
  37. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/pyproject.toml +11 -1
  38. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/_test_helpers.py +99 -0
  39. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/conftest.py +50 -0
  40. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/contract/test_public_api_surface.py +16 -0
  41. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_dirty_handler_seam.py +12 -1
  42. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_run_one_round_with_fake_agent.py +7 -0
  43. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_architecture.py +20 -3
  44. cli_agent_runner-0.3.2/tests/invariants/test_builtin_plugin_names_sync.py +21 -0
  45. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_import_footprint.py +17 -10
  46. cli_agent_runner-0.3.2/tests/invariants/test_kill_tests_not_skipped.py +20 -0
  47. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_module_boundaries.py +1 -0
  48. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_peek_schema_version.py +12 -1
  49. cli_agent_runner-0.3.2/tests/linux/test_plugin_sandbox_kill.py +149 -0
  50. cli_agent_runner-0.3.2/tests/unit/__init__.py +0 -0
  51. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_grace.py +63 -0
  52. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_observation.py +33 -0
  53. cli_agent_runner-0.3.2/tests/unit/test_builtin_provenance.py +31 -0
  54. cli_agent_runner-0.3.2/tests/unit/test_collapse_spawn_decisions.py +33 -0
  55. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config.py +38 -0
  56. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_dirty_handlers.py +9 -4
  57. cli_agent_runner-0.3.2/tests/unit/test_dispatch_dirty_owner_branch.py +261 -0
  58. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_doctor_cmd.py +61 -0
  59. cli_agent_runner-0.3.2/tests/unit/test_doctor_sandbox.py +75 -0
  60. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events.py +27 -0
  61. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_example_dirty_handler.py +4 -1
  62. cli_agent_runner-0.3.2/tests/unit/test_gate_serve_boot.py +90 -0
  63. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_host_health.py +53 -0
  64. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_init_entry_points.py +26 -22
  65. cli_agent_runner-0.3.2/tests/unit/test_load_fail_closed.py +165 -0
  66. cli_agent_runner-0.3.2/tests/unit/test_loader_split.py +61 -0
  67. cli_agent_runner-0.3.2/tests/unit/test_manifest_spawn_hooks.py +66 -0
  68. cli_agent_runner-0.3.2/tests/unit/test_peek_sandbox_block.py +128 -0
  69. cli_agent_runner-0.3.2/tests/unit/test_plugin_checksum.py +44 -0
  70. cli_agent_runner-0.3.2/tests/unit/test_plugin_name_mismatch.py +71 -0
  71. cli_agent_runner-0.3.2/tests/unit/test_plugin_sandbox_wire.py +313 -0
  72. cli_agent_runner-0.3.2/tests/unit/test_plugins_config_fields.py +59 -0
  73. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner.py +14 -0
  74. cli_agent_runner-0.3.2/tests/unit/test_sandbox_probe.py +74 -0
  75. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_cgroup_spine.py +168 -0
  76. cli_agent_runner-0.3.2/tests/unit/test_spawn_seam.py +397 -0
  77. cli_agent_runner-0.3.0/agent_runner/__init__.py +0 -102
  78. cli_agent_runner-0.3.0/agent_runner/_registry.py +0 -23
  79. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.codecov.yml +0 -0
  80. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  81. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  82. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  83. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  84. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/dependabot.yml +0 -0
  85. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.github/workflows/release.yml +0 -0
  86. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/.gitignore +0 -0
  87. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/BACKLOG.md +0 -0
  88. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  89. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/CONTRIBUTING.md +0 -0
  90. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/LICENSE +0 -0
  91. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/README.md +0 -0
  92. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/README.zh.md +0 -0
  93. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/SECURITY.md +0 -0
  94. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_docgen.py +0 -0
  95. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/agent.py +0 -0
  96. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/rounds.py +0 -0
  97. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_emit/serve.py +0 -0
  98. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_install.py +0 -0
  99. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_lifecycle.py +0 -0
  100. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_detectors.py +0 -0
  101. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_registry.py +0 -0
  102. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_monitor_state.py +0 -0
  103. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_plugin_scan.py +0 -0
  104. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_redact.py +0 -0
  105. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_resolve.py +0 -0
  106. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_round_outcome.py +0 -0
  107. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_round_support.py +0 -0
  108. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_serve_policy.py +0 -0
  109. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_substrate.py +0 -0
  110. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/_throttle.py +0 -0
  111. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/__init__.py +0 -0
  112. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/_constants.py +0 -0
  113. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  114. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/codewhale.py +0 -0
  115. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  116. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/gemini.py +0 -0
  117. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/kimi.py +0 -0
  118. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/pi.py +0 -0
  119. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/__init__.py +0 -0
  120. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/__main__.py +0 -0
  121. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_cgroup.py +0 -0
  122. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/events_cmd.py +0 -0
  123. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/init_cmd.py +0 -0
  124. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/install_cmd.py +0 -0
  125. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/migrate_cmd.py +0 -0
  126. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/monitor_cmd.py +0 -0
  127. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/round_cmd.py +0 -0
  128. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/service_cmd.py +0 -0
  129. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/cli/upgrade_cmd.py +0 -0
  130. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/clock.py +0 -0
  131. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/__init__.py +0 -0
  132. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/config/errors.py +0 -0
  133. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/context_store.py +0 -0
  134. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/defenses.py +0 -0
  135. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/http_progress.py +0 -0
  136. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/lifecycle.py +0 -0
  137. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/metrics.py +0 -0
  138. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/migrations.py +0 -0
  139. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/monitor.py +0 -0
  140. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/phase_select.py +0 -0
  141. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/__init__.py +0 -0
  142. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/aider.toml +0 -0
  143. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/claude.toml +0 -0
  144. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/codewhale.toml +0 -0
  145. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/gemini.toml +0 -0
  146. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/kimi.toml +0 -0
  147. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/presets/pi.toml +0 -0
  148. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/prompt_loader.py +0 -0
  149. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/remote_relay.py +0 -0
  150. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/round_log.py +0 -0
  151. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/round_view.py +0 -0
  152. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/scaffold.py +0 -0
  153. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/schedule.py +0 -0
  154. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/service_unit.py +0 -0
  155. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/startup_check.py +0 -0
  156. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/agent_runner/vcs_state.py +0 -0
  157. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/build.sh +0 -0
  158. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/deploy/Dockerfile +0 -0
  159. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/deploy/docker-compose.yml +0 -0
  160. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/README.md +0 -0
  161. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/commands.md +0 -0
  162. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/events.md +0 -0
  163. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/long-running-agents.md +0 -0
  164. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/migrations/0.2.md +0 -0
  165. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/migrations/0.3.md +0 -0
  166. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/quickstart.md +0 -0
  167. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/aider.md +0 -0
  168. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/codewhale.md +0 -0
  169. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/container-pi.md +0 -0
  170. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/deploy-all-in-one.md +0 -0
  171. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/kimi.md +0 -0
  172. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/recipes/pi.md +0 -0
  173. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/docs/runbook.md +0 -0
  174. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/__init__.py +0 -0
  175. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/_clock.py +0 -0
  176. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/contract/__init__.py +0 -0
  177. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/__init__.py +0 -0
  178. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/conftest.py +0 -0
  179. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  180. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_install_systemd.py +0 -0
  181. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  182. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  183. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  184. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  185. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  186. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/generate_vulture_whitelist.py +0 -0
  187. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/__init__.py +0 -0
  188. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_auto_stop_e2e.py +0 -0
  189. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_bounded_run.py +0 -0
  190. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_context_enricher_namespacing.py +0 -0
  191. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_fresh_eyes_signal.py +0 -0
  192. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_grace_kill_emission.py +0 -0
  193. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_install_dry_run.py +0 -0
  194. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_monitor_seeded.py +0 -0
  195. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_detector_loaded.py +0 -0
  196. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_owned_paths.py +0 -0
  197. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_real_flow.py +0 -0
  198. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  199. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_scaffold_presets.py +0 -0
  200. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_serve_loop.py +0 -0
  201. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  202. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_wedged.py +0 -0
  203. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_substrate_fingerprint.py +0 -0
  204. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/integration/test_transient_error_backoff.py +0 -0
  205. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/__init__.py +0 -0
  206. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/_docs.py +0 -0
  207. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/_event_scan.py +0 -0
  208. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_atomic_write_enforced.py +0 -0
  209. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_authored_facts_declared.py +0 -0
  210. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  211. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_bdd_test_conventions.py +0 -0
  212. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_catalogs.py +0 -0
  213. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_classification_ssot.py +0 -0
  214. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_config_error_consistency.py +0 -0
  215. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_config_value_set_ssot.py +0 -0
  216. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_container_stop_bound.py +0 -0
  217. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  218. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  219. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  220. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_cli_claims.py +0 -0
  221. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_operator_surface.py +0 -0
  222. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  223. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  224. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  225. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_generated.py +0 -0
  226. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_index_complete.py +0 -0
  227. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_entry_points_resolve.py +0 -0
  228. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_int_coercion.py +0 -0
  229. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kind_registry.py +0 -0
  230. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kinds_ssot.py +0 -0
  231. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_events_doc_contract.py +0 -0
  232. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  233. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_hook_contract_docs.py +0 -0
  234. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_layer_2_loop_size.py +0 -0
  235. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  236. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_module_sizes.py +0 -0
  237. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  238. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_ai_signatures.py +0 -0
  239. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_internal_refs.py +0 -0
  240. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_price_logic.py +0 -0
  241. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  242. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_raw_time.py +0 -0
  243. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  244. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_no_version_refs.py +0 -0
  245. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_phase_select_stateless.py +0 -0
  246. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_plugins_config_stable.py +0 -0
  247. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  248. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  249. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_alloc_growth.py +0 -0
  250. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  251. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_num_single_source.py +0 -0
  252. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_round_result_stable.py +0 -0
  253. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  254. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  255. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  256. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_upstream_schema_canary.py +0 -0
  257. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  258. {cli_agent_runner-0.3.0/tests/literate → cli_agent_runner-0.3.2/tests/linux}/__init__.py +0 -0
  259. {cli_agent_runner-0.3.0/tests/unit → cli_agent_runner-0.3.2/tests/literate}/__init__.py +0 -0
  260. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/parser.py +0 -0
  261. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/test_parser.py +0 -0
  262. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/literate/test_quickstart.py +0 -0
  263. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime.py +0 -0
  264. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_container.py +0 -0
  265. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  266. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_progress.py +0 -0
  267. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_reap.py +0 -0
  268. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  269. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_assemble_prompt.py +0 -0
  270. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_events_stream.py +0 -0
  271. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_install.py +0 -0
  272. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_read_round_num.py +0 -0
  273. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_resolve_phase.py +0 -0
  274. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_service.py +0 -0
  275. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_api_types.py +0 -0
  276. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_auto_stop_gating.py +0 -0
  277. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_catalogs.py +0 -0
  278. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cgroup_probe.py +0 -0
  279. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_claude_error_detector.py +0 -0
  280. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_claude_foreign_schema.py +0 -0
  281. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli.py +0 -0
  282. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_common.py +0 -0
  283. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_init_install.py +0 -0
  284. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_main_boundary.py +0 -0
  285. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_monitor_http.py +0 -0
  286. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  287. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_cli_upgrade.py +0 -0
  288. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_code_precedence.py +0 -0
  289. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_plugin.py +0 -0
  290. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_coerce_int_poison.py +0 -0
  291. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_fresh_eyes.py +0 -0
  292. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_max_rounds.py +0 -0
  293. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_nested_strictness.py +0 -0
  294. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_prompt_delivery.py +0 -0
  295. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_schedule.py +0 -0
  296. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_stop_file.py +0 -0
  297. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_strictness.py +0 -0
  298. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  299. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_config_transient_error_action.py +0 -0
  300. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_context_store.py +0 -0
  301. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_control_char_config.py +0 -0
  302. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_default_dirty_handler.py +0 -0
  303. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_defenses.py +0 -0
  304. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detect_hung_latch.py +0 -0
  305. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detector_isolation.py +0 -0
  306. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_detector_protocol.py +0 -0
  307. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_docgen.py +0 -0
  308. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_effective_throttle_view.py +0 -0
  309. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_config_migrated.py +0 -0
  310. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule.py +0 -0
  311. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  312. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd.py +0 -0
  313. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_rollover.py +0 -0
  314. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_tail.py +0 -0
  315. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_events_jsonl_decode.py +0 -0
  316. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_example_plugin.py +0 -0
  317. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exec_prefix.py +0 -0
  318. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exit_cause.py +0 -0
  319. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_exit_classifier.py +0 -0
  320. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  321. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_gemini_plugin.py +0 -0
  322. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_git_timeout.py +0 -0
  323. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hook_failure_isolation.py +0 -0
  324. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hook_redact_order.py +0 -0
  325. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_hooks.py +0 -0
  326. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress.py +0 -0
  327. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress_epoch.py +0 -0
  328. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  329. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_kimi_plugin.py +0 -0
  330. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_lifecycle.py +0 -0
  331. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics.py +0 -0
  332. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  333. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_swap.py +0 -0
  334. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_migrate_cmd.py +0 -0
  335. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_migrations.py +0 -0
  336. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_assembly.py +0 -0
  337. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_cmd_severity.py +0 -0
  338. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_identity.py +0 -0
  339. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  340. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  341. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  342. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  343. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detectors.py +0 -0
  344. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_event_tail.py +0 -0
  345. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_loop.py +0 -0
  346. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_on_alert.py +0 -0
  347. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_remote.py +0 -0
  348. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  349. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_schedule_pause.py +0 -0
  350. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_network_blip_signal_guard.py +0 -0
  351. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_open_round_log.py +0 -0
  352. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  353. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_outer_round_ceiling.py +0 -0
  354. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_argparse.py +0 -0
  355. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_project_isolation.py +0 -0
  356. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_schedule.py +0 -0
  357. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_peek_select.py +0 -0
  358. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_agent_launch.py +0 -0
  359. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_profiles.py +0 -0
  360. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_phase_select.py +0 -0
  361. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_pi_plugin.py +0 -0
  362. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_constants.py +0 -0
  363. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_manifest.py +0 -0
  364. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_scan_parity.py +0 -0
  365. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_presets.py +0 -0
  366. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_primary_prompt_file_home.py +0 -0
  367. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_prompt_loader.py +0 -0
  368. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_read_json_hardening.py +0 -0
  369. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_redact.py +0 -0
  370. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_resolve.py +0 -0
  371. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd.py +0 -0
  372. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd_sigterm.py +0 -0
  373. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_log_helpers.py +0 -0
  374. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome.py +0 -0
  375. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  376. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_round_view.py +0 -0
  377. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  378. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_runner_throttle.py +0 -0
  379. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_scaffold.py +0 -0
  380. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_schedule.py +0 -0
  381. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_cmd_bounded.py +0 -0
  382. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_config_broken.py +0 -0
  383. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_crash_loop.py +0 -0
  384. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_mem_gate.py +0 -0
  385. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_phase_schedule.py +0 -0
  386. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_policy.py +0 -0
  387. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round.py +0 -0
  388. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_log.py +0 -0
  389. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_oom_killed.py +0 -0
  390. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_terminate.py +0 -0
  391. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_schedule_gate.py +0 -0
  392. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_sentinel.py +0 -0
  393. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_serve_startup_hooks.py +0 -0
  394. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_service_mode.py +0 -0
  395. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_service_unit.py +0 -0
  396. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_startup_battery_exit.py +0 -0
  397. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_startup_check.py +0 -0
  398. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_substrate.py +0 -0
  399. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  400. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  401. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_epoch_bound.py +0 -0
  402. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_plugin_classification.py +0 -0
  403. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_upgrade_migrate.py +0 -0
  404. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.2}/tests/unit/test_vcs_state.py +0 -0
@@ -82,7 +82,12 @@ jobs:
82
82
  python-version: ${{ matrix.python }}
83
83
  cache: pip
84
84
  - name: install
85
- run: pip install -e ".[dev]"
85
+ # [sandbox] pulls the pure-Python Landlock/seccomp bindings only on
86
+ # Linux (their sys_platform=='linux' markers make this a no-op on
87
+ # macOS), so the Linux legs exercise real seccomp enforcement in the
88
+ # kill-tests; test_kill_tests_not_skipped.py fails loudly if a
89
+ # capable runner ever skips them instead of asserting the denial.
90
+ run: pip install -e ".[dev,sandbox]"
86
91
  - name: ruff
87
92
  run: |
88
93
  ruff check .
@@ -20,6 +20,7 @@ context_enrichers
20
20
  post_round_hooks
21
21
  serve_startup_hooks
22
22
  dirty_handlers
23
+ spawn_hooks
23
24
  detectors
24
25
  event_kinds
25
26
 
@@ -34,6 +35,12 @@ usage_capable_by_agent
34
35
  newest_usage_ts_by_agent
35
36
  backoff_exponent_by_agent
36
37
 
38
+ # agent_runner._sandbox_probe.SandboxProbe
39
+ achieved_tier
40
+ landlock_abi
41
+ seccomp
42
+ unconfined_reason
43
+
37
44
  # agent_runner.agent_runtime.RunResult
38
45
  exit_code
39
46
  duration_s
@@ -77,6 +84,7 @@ service
77
84
  recent_events
78
85
  recent_hook_failures
79
86
  recent_blips
87
+ recent_cgroup_growth_warnings
80
88
  schedule
81
89
 
82
90
  # agent_runner.api_types.RateLimitState
@@ -122,6 +130,11 @@ unit_file
122
130
  rate_limit
123
131
  system_managed
124
132
 
133
+ # agent_runner.api_types.SpawnDecision
134
+ action
135
+ defer_s
136
+ reason
137
+
125
138
  # agent_runner.api_types.SystemMetrics
126
139
  mem_total_mb
127
140
  mem_available_mb
@@ -142,6 +155,7 @@ phase
142
155
  checks
143
156
  overlaps
144
157
  plan
158
+ sandbox
145
159
 
146
160
  # agent_runner.config.models.AgentConfig
147
161
  command
@@ -192,6 +206,9 @@ phase_policy
192
206
 
193
207
  # agent_runner.config.models.PluginsConfig
194
208
  disable
209
+ spawn_override_allow
210
+ sandbox
211
+ pin
195
212
  raw
196
213
 
197
214
  # agent_runner.config.models.Profile
@@ -248,6 +265,7 @@ full_avg10_critical
248
265
  some_avg10_warning
249
266
  critical_consecutive_samples
250
267
  in_round_terminate
268
+ cgroup_growth_rate_warning_mb_per_min
251
269
 
252
270
  # agent_runner.context_store.OrphanState
253
271
  round_num
@@ -287,6 +305,10 @@ anomaly_repetitive_window
287
305
  anomaly_repetitive_threshold
288
306
  vcs
289
307
 
308
+ # agent_runner.hooks.SpawnView
309
+ argv
310
+ env
311
+
290
312
  # agent_runner.hooks.VcsHookView
291
313
  dirty_action
292
314
  stash_idempotency_s
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.2] - 2026-09-14
9
+
10
+ ### Added
11
+ - Opt-in confinement for third-party plugin code (the hooks the supervisor runs in its own process — NOT the agent): a per-call Landlock+seccomp trampoline around third-party spawn hooks and dirty handlers, behind the `[sandbox]` extra. `[plugins] sandbox = require|prefer|off` (default `prefer`: run unconfined but announce it; `require` refuses boot when the sandbox can't engage). Built-in plugins are trusted by module provenance, not by name.
12
+ - Checksum pinning: `[plugins.pin]` verifies a third-party plugin's sha256 before import and fails closed on a mismatch; `doctor` prints each third-party plugin's computed hash to paste in.
13
+ - A `SpawnHook` plugin family that can defer or skip a round's spawn, gated by `[plugins] spawn_override_allow` (empty by default — no plugin alters a spawn until named).
14
+ - `peek --json` (schema `2.2`) and `doctor` report the achieved sandbox tier, `libseccomp` presence, and per-plugin pin status, naming only the families actually confined.
15
+ - New events: `plugin_checksum_mismatch`, `plugin_sandbox_degraded`, `plugin_sandbox_kill`, `plugin_spawn_decision`, `plugin_spawn_override_ignored`, `plugin_builtin_name_squat`.
16
+
17
+ ## [0.3.1] - 2026-09-14
18
+
19
+ ### Added
20
+ - `monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min` (default 512.0): a mid-round memory-growth-rate detector gives an earlier growth signal than the existing PSI/MemFree floors catch, ahead of a runaway climb reaching them. Observability only — no round action.
21
+ - `cgroup_growth_rate_warning` event, surfaced in `peek --json` as `recent_cgroup_growth_warnings` (schema `2.1`).
22
+
8
23
  ## [0.3.0] - 2026-09-13
9
24
 
10
25
  **The one breaking release of the 0.3 line.** Run `agent-runner migrate` before upgrading — it rewrites everything below in one pass.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -34,6 +34,9 @@ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
34
34
  Requires-Dist: vulture>=2.13; extra == 'dev'
35
35
  Provides-Extra: e2e
36
36
  Requires-Dist: pytest>=8; extra == 'e2e'
37
+ Provides-Extra: sandbox
38
+ Requires-Dist: py-landlock==0.1.1; (sys_platform == 'linux') and extra == 'sandbox'
39
+ Requires-Dist: pyseccomp==0.1.2; (sys_platform == 'linux') and extra == 'sandbox'
37
40
  Description-Content-Type: text/markdown
38
41
 
39
42
  > **[中文](README.zh.md)** · English
@@ -0,0 +1,224 @@
1
+ """Agent Runner — restart-on-exit supervisor for autonomous CLI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from agent_runner._version import __version__
7
+ except ImportError: # editable install before hatch-vcs has generated _version.py
8
+ __version__ = "0.0.0+unknown"
9
+
10
+ _PLUGIN_GROUP = "agent_runner.plugins"
11
+
12
+ # Tracks the names passed to the most recent ``apply_plugin_disable`` call.
13
+ # Surfaced via peek --json `plugins.disabled` for operator visibility.
14
+ _DISABLED_PLUGIN_NAMES: list[str] = []
15
+
16
+
17
+ _DISCOVERED_PLUGIN_ENTRIES: list[tuple[str, str]] = [] # (name, "module:attr"), populated once
18
+
19
+
20
+ def _entry_point_module_path(value: str) -> str:
21
+ """The resolvable module-path portion of an entry-point ``value``
22
+ (``"module.path:attr [extras]"``), with any ``:attr`` suffix and trailing
23
+ extras marker stripped.
24
+
25
+ Shared by ``load_and_register_plugins``'s import resolution and
26
+ ``doctor``'s per-plugin checksum print (``cli/doctor_cmd.py``) — the
27
+ checksum print and the actual import MUST resolve the identical module,
28
+ or an operator's pinned digest would silently protect the wrong file.
29
+ """
30
+ return value.partition("[")[0].rstrip().partition(":")[0]
31
+
32
+
33
+ def _discover_plugin_manifests() -> None:
34
+ """Package-import-time step. Scans entry_points.txt ONLY (scan_entry_points) —
35
+ imports nothing, executes no plugin code.
36
+
37
+ Discovery goes through the ``entry_points.txt`` scanner (cheaper than a
38
+ fresh ``importlib.metadata.entry_points()`` scan per process; see
39
+ ``_plugin_scan``), with a hard fallback to ``importlib.metadata`` on any
40
+ parse failure.
41
+ """
42
+ import sys
43
+
44
+ from agent_runner._plugin_scan import scan_entry_points
45
+
46
+ global _DISCOVERED_PLUGIN_ENTRIES
47
+ _DISCOVERED_PLUGIN_ENTRIES = scan_entry_points(sys.path, _PLUGIN_GROUP)
48
+
49
+
50
+ _discover_plugin_manifests()
51
+
52
+
53
+ def load_and_register_plugins(plugins_cfg, log_dir=None) -> None:
54
+ """Called from config.loader.load_config once [plugins] is parsed — the
55
+ load-bearing fix: verification now has a Config to gate on. For each
56
+ discovered (name, value) NOT in plugins_cfg.disable and not already
57
+ registered, resolve module_path/attr_path, import + register_manifest. A
58
+ disabled name is never imported. A per-plugin failure isolates as a
59
+ UserWarning and skips that ONE plugin; it never aborts the whole load.
60
+
61
+ ``name`` here is always the entry-point name (the ``agent_runner.plugins``
62
+ pyproject.toml key) — it's the identifier known BEFORE import, so it's what
63
+ ``_admit_third_party`` gates on (pin lookup) and what ``doctor`` prints a
64
+ checksum against. It is NOT necessarily ``PluginManifest.name`` (only known
65
+ after import); for every builtin the two are pinned equal by
66
+ ``test_entry_point_names_should_match_manifest_name``, but a third-party
67
+ plugin has no such guarantee — which is fine, since admission never reads
68
+ the manifest name at all.
69
+
70
+ Builtin trust is decided by ``is_builtin_provenance(name, module_path)`` —
71
+ a reserved name AND a module genuinely under
72
+ ``agent_runner.builtin_plugins``. A reserved name alone is NOT enough: a
73
+ name-squatter (reserved name, foreign module) is treated as third-party,
74
+ flows through ``_admit_third_party``, and raises a loud
75
+ ``plugin_builtin_name_squat`` signal. Only genuine builtins skip the gate.
76
+
77
+ Every non-builtin entry passes through ``_admit_third_party`` BEFORE import
78
+ — a pin mismatch or an unpinned plugin under ``sandbox = "require"`` refuses
79
+ to import that ONE plugin (fail-closed; never loads unconfined).
80
+ """
81
+ import warnings
82
+
83
+ from agent_runner._plugin_manifest import loaded_manifest_names, register_manifest
84
+ from agent_runner._registry import (
85
+ BUILTIN_PLUGIN_NAMES,
86
+ is_builtin_provenance,
87
+ resolve_entry_target,
88
+ )
89
+
90
+ disable = set(plugins_cfg.disable)
91
+ already = set(loaded_manifest_names())
92
+ for name, value in _DISCOVERED_PLUGIN_ENTRIES:
93
+ if name in disable or name in already:
94
+ continue
95
+ # An entry-point value may carry a trailing extras marker
96
+ # (``module:attr [extra1,extra2]``); importlib.metadata.EntryPoint's
97
+ # own module/attr grammar excludes "[", so anything from the first
98
+ # "[" onward is always extras, never part of the path — strip it
99
+ # before resolving, or it glues onto attr_path and breaks getattr().
100
+ stripped = value.partition("[")[0].rstrip()
101
+ module_path, _, attr_path = stripped.partition(":")
102
+ # Builtin trust is keyed on PROVENANCE (module genuinely under
103
+ # agent_runner.builtin_plugins), never on the entry-point NAME alone —
104
+ # a reserved name is collidable, so a third-party package shipping an
105
+ # entry named e.g. "pi" is a name-squatter, not a builtin.
106
+ is_builtin = is_builtin_provenance(name, module_path)
107
+ if not is_builtin:
108
+ if name in BUILTIN_PLUGIN_NAMES:
109
+ _warn_builtin_name_squat(name, module_path, log_dir)
110
+ if not _admit_third_party(name, module_path, plugins_cfg, log_dir):
111
+ continue
112
+ try:
113
+ target = resolve_entry_target(module_path, attr_path)
114
+ register_manifest(
115
+ target, builtin=is_builtin, module_path=module_path, attr_path=attr_path
116
+ )
117
+ except Exception as e: # noqa: BLE001 — a broken plugin must never crash the supervisor
118
+ warnings.warn(f"failed to load {_PLUGIN_GROUP} plugin {name!r}: {e}", stacklevel=3)
119
+
120
+
121
+ def _warn_builtin_name_squat(name: str, module_path: str, log_dir) -> None:
122
+ """A discovered entry point claims a RESERVED builtin name while resolving
123
+ to a non-core module — a name-squatter. Builtin trust is denied (the entry
124
+ still flows through ``_admit_third_party``'s pin/sandbox gate); this makes
125
+ the claim LOUD (a warning always, plus an auditable event when a log_dir is
126
+ available) rather than a silent trust bypass."""
127
+ import warnings
128
+
129
+ warnings.warn(
130
+ f"{_PLUGIN_GROUP} entry {name!r} claims a reserved builtin name but resolves to "
131
+ f"non-core module {module_path!r}; treating as third-party (pin/sandbox gate applies)",
132
+ stacklevel=4,
133
+ )
134
+ if log_dir is not None:
135
+ from agent_runner.api import emit_plugin_builtin_name_squat
136
+
137
+ emit_plugin_builtin_name_squat(log_dir, name=name, module_path=module_path)
138
+
139
+
140
+ def _admit_third_party(name: str, module_path: str, plugins_cfg, log_dir) -> bool:
141
+ """Fail-closed pin/sandbox gate for a non-builtin plugin (any entry lacking
142
+ genuine builtin provenance, INCLUDING a reserved-name squatter). Returns
143
+ False to refuse (and emit) — skipping this ONE plugin; every other
144
+ discovered entry still loads.
145
+
146
+ Both ``name`` and ``pins``' keys are entry-point names — see
147
+ ``load_and_register_plugins``'s docstring. Runs BEFORE
148
+ ``importlib.import_module``, so no plugin code executes for a refused
149
+ entry.
150
+ """
151
+ from agent_runner._plugin_checksum import verify_pin
152
+ from agent_runner._sandbox_probe import probe_sandbox_capability
153
+ from agent_runner.api import emit_plugin_checksum_mismatch, emit_plugin_sandbox_degraded
154
+
155
+ try:
156
+ verdict, actual = verify_pin(name, module_path, plugins_cfg.pin)
157
+ except Exception: # noqa: BLE001 — unresolvable/unreadable module == refuse (fail closed)
158
+ verdict, actual = "mismatch", "sha256:unreadable"
159
+ if verdict == "mismatch":
160
+ if log_dir is not None:
161
+ emit_plugin_checksum_mismatch(
162
+ log_dir,
163
+ name=name,
164
+ expected=plugins_cfg.pin.get(name, ""),
165
+ actual=actual or "sha256:unreadable",
166
+ )
167
+ return False
168
+ if verdict == "unpinned" and plugins_cfg.sandbox == "require":
169
+ if log_dir is not None:
170
+ probe = probe_sandbox_capability()
171
+ emit_plugin_sandbox_degraded(
172
+ log_dir,
173
+ requested="require",
174
+ achieved_tier=probe.achieved_tier,
175
+ reason=f"{name}: unpinned third-party plugin under sandbox=require",
176
+ )
177
+ return False
178
+ return True
179
+
180
+
181
+ def apply_plugin_disable(names: list[str]) -> None:
182
+ """Remove plugins matching ``names`` (manifest names) from all in-memory
183
+ registries.
184
+
185
+ Called after config-load to honor ``[plugins] disable``. Idempotent for
186
+ already-removed names. Emits a UserWarning for names that match no
187
+ loaded manifest (typo catcher; tolerates cross-env config drift).
188
+
189
+ A disabled name is never imported by ``load_and_register_plugins`` in the
190
+ first place, so this is belt-and-suspenders cleanup for anything that
191
+ reached the registries another way (e.g. a manifest registered directly
192
+ via ``register_manifest``, not through entry-point discovery).
193
+
194
+ Known limitation: vcs_state._PLUGIN_OWNED_PATHS lacks per-plugin name
195
+ attribution today, so owned-paths are NOT filtered here. Disabled plugin's
196
+ paths remain registered (mostly inert).
197
+ """
198
+ import warnings
199
+
200
+ from agent_runner._plugin_manifest import unregister_by_name
201
+
202
+ if not names:
203
+ return
204
+
205
+ global _DISABLED_PLUGIN_NAMES
206
+ _DISABLED_PLUGIN_NAMES = list(names)
207
+
208
+ found = unregister_by_name(set(names))
209
+ unknown = set(names) - found
210
+ if unknown:
211
+ warnings.warn(
212
+ f"[plugins] disable references unknown plugin name(s): {sorted(unknown)}. "
213
+ f"(Names matched no loaded PluginManifest; check spelling or installed packages.)",
214
+ stacklevel=2,
215
+ )
216
+
217
+
218
+ def disabled_plugin_names() -> list[str]:
219
+ """Names passed to the most recent ``apply_plugin_disable`` call.
220
+
221
+ Used by peek --json to surface ``plugins.disabled`` for operator visibility.
222
+ Returns the LIST (not set) preserving configured order.
223
+ """
224
+ return list(_DISABLED_PLUGIN_NAMES)
@@ -29,6 +29,7 @@ from agent_runner._emit.agent import ( # noqa: F401 — public re-export
29
29
  emit_transient_error_recovered,
30
30
  )
31
31
  from agent_runner._emit.memory import ( # noqa: F401 — public re-export
32
+ emit_cgroup_growth_rate_warning,
32
33
  emit_host_cgroup_memory_limit,
33
34
  emit_mem_pressure_deferred_to_cgroup,
34
35
  emit_round_cgroup_memory,
@@ -38,6 +39,14 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
38
39
  emit_round_oom_killed,
39
40
  emit_round_resumed,
40
41
  )
42
+ from agent_runner._emit.plugins import ( # noqa: F401 — public re-export
43
+ emit_plugin_builtin_name_squat,
44
+ emit_plugin_checksum_mismatch,
45
+ emit_plugin_sandbox_degraded,
46
+ emit_plugin_sandbox_kill,
47
+ emit_plugin_spawn_decision,
48
+ emit_plugin_spawn_override_ignored,
49
+ )
41
50
  from agent_runner._emit.rounds import ( # noqa: F401 — public re-export
42
51
  emit_fresh_eyes_round_triggered,
43
52
  emit_round_container_orphan_risk,
@@ -69,6 +78,7 @@ __all__ = [
69
78
  "emit_agent_auth_error_detected",
70
79
  "emit_agent_usage_recorded",
71
80
  "emit_anomaly_repetitive_tool",
81
+ "emit_cgroup_growth_rate_warning",
72
82
  "emit_config_broken",
73
83
  "emit_config_migrated",
74
84
  "emit_crash_loop",
@@ -78,6 +88,12 @@ __all__ = [
78
88
  "emit_mem_loop",
79
89
  "emit_mem_loop_persistent",
80
90
  "emit_mem_pressure_deferred_to_cgroup",
91
+ "emit_plugin_builtin_name_squat",
92
+ "emit_plugin_checksum_mismatch",
93
+ "emit_plugin_sandbox_degraded",
94
+ "emit_plugin_sandbox_kill",
95
+ "emit_plugin_spawn_decision",
96
+ "emit_plugin_spawn_override_ignored",
81
97
  "emit_rate_limit_stop",
82
98
  "emit_round_cgroup_memory",
83
99
  "emit_round_container_orphan_risk",
@@ -238,3 +238,33 @@ def emit_mem_pressure_deferred_to_cgroup(
238
238
  from agent_runner.events import MEM_PRESSURE_DEFERRED_TO_CGROUP, emit
239
239
 
240
240
  emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
241
+
242
+
243
+ def emit_cgroup_growth_rate_warning(
244
+ log_dir: Path,
245
+ *,
246
+ round_num: int,
247
+ rate_mb_per_min: float,
248
+ threshold_mb_per_min: float,
249
+ source: str,
250
+ context: dict,
251
+ ) -> None:
252
+ """Emit on a False->True crossing of _spawn_round's mid-round growth-rate
253
+ derivative (host_health.cgroup_growth_rate_pressure) -- observability
254
+ only, no action path. ``source`` names which per-tick reading the
255
+ derivative used: "cgroup" (a finite cgroup memory.max bound exists,
256
+ memory.current is read) or "rss_sum" (no finite bound -- the psutil
257
+ children_rss_sum_bytes tree sum is used instead). Edge-triggered: fires
258
+ once per crossing episode, not once per tick, so a sustained climb does
259
+ not spam the event stream."""
260
+ from agent_runner.events import CGROUP_GROWTH_RATE_WARNING, emit
261
+
262
+ emit(
263
+ log_dir,
264
+ CGROUP_GROWTH_RATE_WARNING,
265
+ round_num=round_num,
266
+ rate_mb_per_min=rate_mb_per_min,
267
+ threshold_mb_per_min=threshold_mb_per_min,
268
+ source=source,
269
+ context=context,
270
+ )
@@ -0,0 +1,76 @@
1
+ """Event emission wrappers -- plugin-security domain (sandbox trampoline).
2
+ Re-exported from ``agent_runner._emit`` (the package facade) -- see its docstring."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+
8
+
9
+ def emit_plugin_sandbox_kill(
10
+ log_dir: Path, *, hook: str, signal: int, syscall: str | None = None
11
+ ) -> None:
12
+ """Emit when a trampoline child died by signal (seccomp KILL_PROCESS) -- a
13
+ denied syscall. Distinct from the caller's own hook_failed isolation, which
14
+ also fires: this event names the DENIAL specifically."""
15
+ from agent_runner.events import PLUGIN_SANDBOX_KILL, emit
16
+
17
+ emit(log_dir, PLUGIN_SANDBOX_KILL, hook=hook, signal=signal, syscall=syscall)
18
+
19
+
20
+ def emit_plugin_spawn_decision(
21
+ log_dir: Path, *, hook: str, action: str, defer_s: int, reason: str
22
+ ) -> None:
23
+ """Emit the collapsed pre-spawn verdict the serve admission gate acted on --
24
+ ``defer`` or ``skip`` (a ``proceed`` is the silent default, no event). ``hook``
25
+ is the winning hook by the collapse's own precedence (skip > max-defer)."""
26
+ from agent_runner.events import PLUGIN_SPAWN_DECISION, emit
27
+
28
+ emit(log_dir, PLUGIN_SPAWN_DECISION, hook=hook, action=action, defer_s=defer_s, reason=reason)
29
+
30
+
31
+ def emit_plugin_spawn_override_ignored(log_dir: Path, *, hook: str, action: str) -> None:
32
+ """Emit when a spawn hook returned a non-``proceed`` verdict but is NOT in
33
+ ``[plugins] spawn_override_allow`` -- its blocking power is denied (the
34
+ decision is downgraded to ``proceed`` before the collapse). Blocking a round
35
+ is an operator-granted capability, so an un-listed hook's ``defer``/``skip``
36
+ is audited here rather than honored silently."""
37
+ from agent_runner.events import PLUGIN_SPAWN_OVERRIDE_IGNORED, emit
38
+
39
+ emit(log_dir, PLUGIN_SPAWN_OVERRIDE_IGNORED, hook=hook, action=action)
40
+
41
+
42
+ def emit_plugin_builtin_name_squat(log_dir: Path, *, name: str, module_path: str) -> None:
43
+ """Emit when a discovered entry point claims a RESERVED builtin name while
44
+ resolving to a module OUTSIDE the core ``agent_runner.builtin_plugins``
45
+ namespace -- a name-squatter. Builtin trust is denied: the entry falls
46
+ through to the third-party pin/sandbox gate. This event makes the claim
47
+ auditable rather than silent."""
48
+ from agent_runner.events import PLUGIN_BUILTIN_NAME_SQUAT, emit
49
+
50
+ emit(log_dir, PLUGIN_BUILTIN_NAME_SQUAT, name=name, module_path=module_path)
51
+
52
+
53
+ def emit_plugin_checksum_mismatch(log_dir: Path, *, name: str, expected: str, actual: str) -> None:
54
+ """Emit when a third-party plugin's computed sha256 doesn't match its
55
+ ``[plugins.pin]`` entry (or the pin is unreadable) -- the loader refuses
56
+ to import that ONE plugin when this fires; it never loads unconfined."""
57
+ from agent_runner.events import PLUGIN_CHECKSUM_MISMATCH, emit
58
+
59
+ emit(log_dir, PLUGIN_CHECKSUM_MISMATCH, name=name, expected=expected, actual=actual)
60
+
61
+
62
+ def emit_plugin_sandbox_degraded(
63
+ log_dir: Path, *, requested: str, achieved_tier: str, reason: str
64
+ ) -> None:
65
+ """Emit when a plugin loads (or would run) at a WEAKER tier than
66
+ ``requested`` -- e.g. an unpinned third-party plugin refused under
67
+ ``sandbox = "require"``, or a ``"prefer"`` load proceeding unconfined."""
68
+ from agent_runner.events import PLUGIN_SANDBOX_DEGRADED, emit
69
+
70
+ emit(
71
+ log_dir,
72
+ PLUGIN_SANDBOX_DEGRADED,
73
+ requested=requested,
74
+ achieved_tier=achieved_tier,
75
+ reason=reason,
76
+ )
@@ -21,12 +21,14 @@ from agent_runner.clock import SYSTEM_CLOCK
21
21
  from agent_runner.config import load_config
22
22
  from agent_runner.events import (
23
23
  AGENT_NETWORK_BLIP,
24
+ CGROUP_GROWTH_RATE_WARNING,
24
25
  HOOK_FAILED,
25
26
  MONITOR_STARTED,
26
27
  )
27
28
 
28
29
  _RECENT_HOOK_FAILURES_LIMIT = 10
29
30
  _RECENT_BLIPS_LIMIT = 5
31
+ _RECENT_GROWTH_LIMIT = 5
30
32
  _MONITOR_SEEN_CAP = 512
31
33
  """Bound on `_monitor_loop_iter`'s dedup set — an unbounded set of alert-identity
32
34
  keys would grow forever across a long-lived monitor process; oldest-episode
@@ -59,12 +61,20 @@ def peek(
59
61
  log: bool = False,
60
62
  events: int | None = None,
61
63
  select: str | None = None,
64
+ cfg: Any = None,
62
65
  ) -> ProjectState | Any:
63
- """Build a ProjectState snapshot. With select, return that subtree."""
66
+ """Build a ProjectState snapshot. With select, return that subtree.
67
+
68
+ ``cfg`` (an already-loaded ``Config``) lets a caller that has loaded config
69
+ once — ``cli.peek_cmd.cmd_peek`` does, for the ``emit`` block — pass it in so
70
+ ``peek`` does not re-``load_config`` (which re-runs plugin load/checksum/probe)
71
+ a second time per invocation. ``None`` loads it here (the default, unchanged
72
+ for every other caller)."""
64
73
  from agent_runner import round_view
65
74
 
66
75
  work_dir = project if isinstance(project, Path) else Path.cwd()
67
- cfg = load_config(work_dir / "agent-runner.toml")
76
+ if cfg is None:
77
+ cfg = load_config(work_dir / "agent-runner.toml")
68
78
  log_dir = cfg.runtime.log_dir
69
79
  src = monitor.LocalSource(log_dir=log_dir)
70
80
  base_state = monitor.assemble_project_state(src, project=_project_name(work_dir))
@@ -80,6 +90,9 @@ def peek(
80
90
  parsed_events, HOOK_FAILED, _RECENT_HOOK_FAILURES_LIMIT
81
91
  )
82
92
  recent_blips = _recent_events_of_kind(parsed_events, AGENT_NETWORK_BLIP, _RECENT_BLIPS_LIMIT)
93
+ recent_cgroup_growth_warnings = _recent_events_of_kind(
94
+ parsed_events, CGROUP_GROWTH_RATE_WARNING, _RECENT_GROWTH_LIMIT
95
+ )
83
96
 
84
97
  from agent_runner._throttle import effective_throttle_view
85
98
 
@@ -121,6 +134,7 @@ def peek(
121
134
  recent_events=recent,
122
135
  recent_hook_failures=recent_hook_failures,
123
136
  recent_blips=recent_blips,
137
+ recent_cgroup_growth_warnings=recent_cgroup_growth_warnings,
124
138
  schedule=monitor.latest_schedule_state(parsed_events),
125
139
  )
126
140
  return state if select is None else select_path(state, select)
@@ -0,0 +1,62 @@
1
+ """sha256 pinning of a plugin's declared entry-point module FILE (coarse but
2
+ deterministic — one file, one read). Fail-closed: an unresolvable/unreadable
3
+ module raises, and the loader turns that into a refusal, same as a mismatch.
4
+
5
+ Keyed on the entry-point NAME (the ``agent_runner.plugins`` group's own
6
+ pyproject.toml key), not ``PluginManifest.name`` — a third-party plugin
7
+ hasn't been imported yet at pin-check time, so its manifest name isn't even
8
+ known; the entry-point name is the only stable, pre-import identifier an
9
+ operator can write into ``[plugins.pin]``. See
10
+ ``agent_runner.__init__._admit_third_party`` for the call site.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import importlib.util
17
+ from typing import Literal
18
+
19
+ PinVerdict = Literal["verified", "mismatch", "unpinned"]
20
+
21
+ _CHECKSUM_CACHE: dict[str, str] = {}
22
+ """Process-wide memo of ``module_path -> digest``, keyed post-resolution.
23
+ A ``doctor``/``peek --json`` invocation hashes each pinned third-party file
24
+ twice (once at load via ``verify_pin``, once for the snapshot print) — this
25
+ avoids the second read of the SAME file in the SAME process. Never
26
+ invalidated (a process's view of its own already-imported modules doesn't
27
+ change mid-run); the comparison logic below is untouched."""
28
+
29
+
30
+ def compute_plugin_checksum(module_path: str) -> str:
31
+ """sha256 of the resolved module's source FILE, as ``"sha256:<64hex>"``.
32
+
33
+ This exact recipe (sha256 of the raw bytes of ``find_spec(module_path)
34
+ .origin``) is also what ``doctor`` prints and what ``verify_pin`` checks
35
+ against — an operator copy-pastes doctor's printed digest straight into
36
+ ``[plugins.pin]``, so all three MUST compute the identical value.
37
+ """
38
+ cached = _CHECKSUM_CACHE.get(module_path)
39
+ if cached is not None:
40
+ return cached
41
+ spec = importlib.util.find_spec(module_path)
42
+ if spec is None or spec.origin is None:
43
+ raise ModuleNotFoundError(f"cannot resolve module file for {module_path!r}")
44
+ with open(spec.origin, "rb") as f:
45
+ digest = hashlib.sha256(f.read()).hexdigest()
46
+ checksum = f"sha256:{digest}"
47
+ _CHECKSUM_CACHE[module_path] = checksum
48
+ return checksum
49
+
50
+
51
+ def verify_pin(name: str, module_path: str, pins: dict[str, str]) -> tuple[PinVerdict, str | None]:
52
+ """Returns ``(verdict, actual)`` where ``actual`` is the computed
53
+ ``"sha256:<hex>"`` digest of the module file, or ``None`` when the name is
54
+ unpinned (no digest is computed in that case). The caller reuses ``actual``
55
+ for the mismatch event instead of hashing the file a second time.
56
+
57
+ ``name`` is the entry-point name (the ``[plugins.pin]`` key an operator
58
+ writes), NOT ``PluginManifest.name`` — see module docstring."""
59
+ if name not in pins:
60
+ return "unpinned", None
61
+ actual = compute_plugin_checksum(module_path)
62
+ return ("verified" if actual == pins[name] else "mismatch"), actual