cli-agent-runner 0.3.1__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.1 → cli_agent_runner-0.3.2}/.github/workflows/ci.yml +6 -1
  2. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.vulture-whitelist.py +20 -0
  3. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/CHANGELOG.md +9 -0
  4. {cli_agent_runner-0.3.1 → 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.1 → cli_agent_runner-0.3.2}/agent_runner/_emit/__init__.py +14 -0
  7. cli_agent_runner-0.3.2/agent_runner/_emit/plugins.py +76 -0
  8. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_observe.py +10 -2
  9. cli_agent_runner-0.3.2/agent_runner/_plugin_checksum.py +62 -0
  10. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_plugin_manifest.py +48 -4
  11. cli_agent_runner-0.3.2/agent_runner/_plugin_sandbox.py +535 -0
  12. cli_agent_runner-0.3.2/agent_runner/_registry.py +69 -0
  13. cli_agent_runner-0.3.2/agent_runner/_sandbox_probe.py +326 -0
  14. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_version.py +2 -2
  15. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/api.py +6 -0
  16. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/api_types.py +11 -0
  17. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_round.py +158 -4
  18. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/common.py +16 -13
  19. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/doctor_cmd.py +20 -0
  20. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/peek_cmd.py +7 -2
  21. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/serve_cmd.py +39 -4
  22. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/loader.py +6 -4
  23. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/models.py +5 -0
  24. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/parsers.py +57 -3
  25. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/events.py +6 -0
  26. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/hooks.py +153 -10
  27. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/runner.py +3 -1
  28. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/architecture.md +6 -0
  29. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/configuration.md +3 -0
  30. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/plugins.md +15 -3
  31. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/thesis.md +1 -1
  32. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/pyproject.toml +11 -1
  33. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/_test_helpers.py +99 -0
  34. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/conftest.py +50 -0
  35. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/contract/test_public_api_surface.py +15 -0
  36. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_dirty_handler_seam.py +12 -1
  37. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_run_one_round_with_fake_agent.py +7 -0
  38. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_architecture.py +15 -2
  39. cli_agent_runner-0.3.2/tests/invariants/test_builtin_plugin_names_sync.py +21 -0
  40. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_import_footprint.py +17 -10
  41. cli_agent_runner-0.3.2/tests/invariants/test_kill_tests_not_skipped.py +20 -0
  42. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_module_boundaries.py +1 -0
  43. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_peek_schema_version.py +12 -1
  44. cli_agent_runner-0.3.2/tests/linux/test_plugin_sandbox_kill.py +149 -0
  45. cli_agent_runner-0.3.2/tests/unit/__init__.py +0 -0
  46. cli_agent_runner-0.3.2/tests/unit/test_builtin_provenance.py +31 -0
  47. cli_agent_runner-0.3.2/tests/unit/test_collapse_spawn_decisions.py +33 -0
  48. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_dirty_handlers.py +9 -4
  49. cli_agent_runner-0.3.2/tests/unit/test_dispatch_dirty_owner_branch.py +261 -0
  50. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_doctor_cmd.py +61 -0
  51. cli_agent_runner-0.3.2/tests/unit/test_doctor_sandbox.py +75 -0
  52. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_example_dirty_handler.py +4 -1
  53. cli_agent_runner-0.3.2/tests/unit/test_gate_serve_boot.py +90 -0
  54. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_init_entry_points.py +26 -22
  55. cli_agent_runner-0.3.2/tests/unit/test_load_fail_closed.py +165 -0
  56. cli_agent_runner-0.3.2/tests/unit/test_loader_split.py +61 -0
  57. cli_agent_runner-0.3.2/tests/unit/test_manifest_spawn_hooks.py +66 -0
  58. cli_agent_runner-0.3.2/tests/unit/test_peek_sandbox_block.py +128 -0
  59. cli_agent_runner-0.3.2/tests/unit/test_plugin_checksum.py +44 -0
  60. cli_agent_runner-0.3.2/tests/unit/test_plugin_name_mismatch.py +71 -0
  61. cli_agent_runner-0.3.2/tests/unit/test_plugin_sandbox_wire.py +313 -0
  62. cli_agent_runner-0.3.2/tests/unit/test_plugins_config_fields.py +59 -0
  63. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_runner.py +14 -0
  64. cli_agent_runner-0.3.2/tests/unit/test_sandbox_probe.py +74 -0
  65. cli_agent_runner-0.3.2/tests/unit/test_spawn_seam.py +397 -0
  66. cli_agent_runner-0.3.1/agent_runner/__init__.py +0 -102
  67. cli_agent_runner-0.3.1/agent_runner/_registry.py +0 -23
  68. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.codecov.yml +0 -0
  69. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  70. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  71. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  72. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  73. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/dependabot.yml +0 -0
  74. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.github/workflows/release.yml +0 -0
  75. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/.gitignore +0 -0
  76. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/BACKLOG.md +0 -0
  77. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  78. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/CONTRIBUTING.md +0 -0
  79. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/LICENSE +0 -0
  80. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/README.md +0 -0
  81. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/README.zh.md +0 -0
  82. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/SECURITY.md +0 -0
  83. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_docgen.py +0 -0
  84. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_emit/agent.py +0 -0
  85. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_emit/memory.py +0 -0
  86. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_emit/rounds.py +0 -0
  87. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_emit/serve.py +0 -0
  88. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_install.py +0 -0
  89. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_lifecycle.py +0 -0
  90. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_monitor_detectors.py +0 -0
  91. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_monitor_registry.py +0 -0
  92. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_monitor_state.py +0 -0
  93. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_plugin_scan.py +0 -0
  94. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_redact.py +0 -0
  95. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_resolve.py +0 -0
  96. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_round_outcome.py +0 -0
  97. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_round_support.py +0 -0
  98. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_serve_policy.py +0 -0
  99. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_substrate.py +0 -0
  100. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/_throttle.py +0 -0
  101. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/agent_runtime.py +0 -0
  102. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/__init__.py +0 -0
  103. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/_constants.py +0 -0
  104. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  105. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/codewhale.py +0 -0
  106. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  107. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/gemini.py +0 -0
  108. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/kimi.py +0 -0
  109. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/builtin_plugins/pi.py +0 -0
  110. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/__init__.py +0 -0
  111. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/__main__.py +0 -0
  112. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/_serve_cgroup.py +0 -0
  113. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/events_cmd.py +0 -0
  114. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/init_cmd.py +0 -0
  115. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/install_cmd.py +0 -0
  116. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/migrate_cmd.py +0 -0
  117. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/monitor_cmd.py +0 -0
  118. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/round_cmd.py +0 -0
  119. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/service_cmd.py +0 -0
  120. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/cli/upgrade_cmd.py +0 -0
  121. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/clock.py +0 -0
  122. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/__init__.py +0 -0
  123. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/errors.py +0 -0
  124. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/config/validators.py +0 -0
  125. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/context_store.py +0 -0
  126. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/defenses.py +0 -0
  127. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/host_health.py +0 -0
  128. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/http_progress.py +0 -0
  129. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/lifecycle.py +0 -0
  130. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/metrics.py +0 -0
  131. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/migrations.py +0 -0
  132. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/monitor.py +0 -0
  133. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/phase_select.py +0 -0
  134. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/__init__.py +0 -0
  135. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/aider.toml +0 -0
  136. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/claude.toml +0 -0
  137. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/codewhale.toml +0 -0
  138. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/gemini.toml +0 -0
  139. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/kimi.toml +0 -0
  140. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/presets/pi.toml +0 -0
  141. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/prompt_loader.py +0 -0
  142. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/remote_relay.py +0 -0
  143. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/round_log.py +0 -0
  144. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/round_view.py +0 -0
  145. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/scaffold.py +0 -0
  146. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/schedule.py +0 -0
  147. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/service_unit.py +0 -0
  148. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/startup_check.py +0 -0
  149. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/agent_runner/vcs_state.py +0 -0
  150. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/build.sh +0 -0
  151. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/deploy/Dockerfile +0 -0
  152. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/deploy/docker-compose.yml +0 -0
  153. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/README.md +0 -0
  154. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/commands.md +0 -0
  155. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/events.md +0 -0
  156. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/long-running-agents.md +0 -0
  157. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/memory-efficiency.md +0 -0
  158. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/migrations/0.2.md +0 -0
  159. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/migrations/0.3.md +0 -0
  160. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/quickstart.md +0 -0
  161. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/aider.md +0 -0
  162. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/codewhale.md +0 -0
  163. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/container-pi.md +0 -0
  164. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/deploy-all-in-one.md +0 -0
  165. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/kimi.md +0 -0
  166. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/recipes/pi.md +0 -0
  167. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/docs/runbook.md +0 -0
  168. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/__init__.py +0 -0
  169. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/_clock.py +0 -0
  170. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/contract/__init__.py +0 -0
  171. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/__init__.py +0 -0
  172. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/conftest.py +0 -0
  173. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  174. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_install_systemd.py +0 -0
  175. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  176. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  177. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  178. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  179. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  180. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/generate_vulture_whitelist.py +0 -0
  181. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/__init__.py +0 -0
  182. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_auto_stop_e2e.py +0 -0
  183. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_bounded_run.py +0 -0
  184. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_context_enricher_namespacing.py +0 -0
  185. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_fresh_eyes_signal.py +0 -0
  186. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_grace_kill_emission.py +0 -0
  187. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_install_dry_run.py +0 -0
  188. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_monitor_seeded.py +0 -0
  189. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_detector_loaded.py +0 -0
  190. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_owned_paths.py +0 -0
  191. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_plugin_real_flow.py +0 -0
  192. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  193. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_scaffold_presets.py +0 -0
  194. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_serve_loop.py +0 -0
  195. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  196. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_spawn_round_wedged.py +0 -0
  197. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_substrate_fingerprint.py +0 -0
  198. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/integration/test_transient_error_backoff.py +0 -0
  199. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/__init__.py +0 -0
  200. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/_docs.py +0 -0
  201. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/_event_scan.py +0 -0
  202. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_atomic_write_enforced.py +0 -0
  203. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_authored_facts_declared.py +0 -0
  204. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  205. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_bdd_test_conventions.py +0 -0
  206. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_catalogs.py +0 -0
  207. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_classification_ssot.py +0 -0
  208. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_config_error_consistency.py +0 -0
  209. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_config_value_set_ssot.py +0 -0
  210. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_container_stop_bound.py +0 -0
  211. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  212. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  213. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  214. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_cli_claims.py +0 -0
  215. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_operator_surface.py +0 -0
  216. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  217. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  218. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  219. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_generated.py +0 -0
  220. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_docs_index_complete.py +0 -0
  221. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_entry_points_resolve.py +0 -0
  222. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_event_int_coercion.py +0 -0
  223. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kind_registry.py +0 -0
  224. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_event_kinds_ssot.py +0 -0
  225. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_events_doc_contract.py +0 -0
  226. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  227. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_hook_contract_docs.py +0 -0
  228. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_layer_2_loop_size.py +0 -0
  229. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  230. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_module_sizes.py +0 -0
  231. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  232. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_ai_signatures.py +0 -0
  233. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_internal_refs.py +0 -0
  234. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_price_logic.py +0 -0
  235. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  236. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_raw_time.py +0 -0
  237. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  238. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_no_version_refs.py +0 -0
  239. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_phase_select_stateless.py +0 -0
  240. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_plugins_config_stable.py +0 -0
  241. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  242. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  243. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_round_alloc_growth.py +0 -0
  244. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  245. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_round_num_single_source.py +0 -0
  246. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_round_result_stable.py +0 -0
  247. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  248. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  249. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  250. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_upstream_schema_canary.py +0 -0
  251. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  252. {cli_agent_runner-0.3.1/tests/literate → cli_agent_runner-0.3.2/tests/linux}/__init__.py +0 -0
  253. {cli_agent_runner-0.3.1/tests/unit → cli_agent_runner-0.3.2/tests/literate}/__init__.py +0 -0
  254. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/literate/parser.py +0 -0
  255. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/literate/test_parser.py +0 -0
  256. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/literate/test_quickstart.py +0 -0
  257. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime.py +0 -0
  258. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_container.py +0 -0
  259. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_grace.py +0 -0
  260. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  261. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_progress.py +0 -0
  262. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_reap.py +0 -0
  263. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  264. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_assemble_prompt.py +0 -0
  265. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_events_stream.py +0 -0
  266. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_install.py +0 -0
  267. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_observation.py +0 -0
  268. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_read_round_num.py +0 -0
  269. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_resolve_phase.py +0 -0
  270. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_service.py +0 -0
  271. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_api_types.py +0 -0
  272. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_auto_stop_gating.py +0 -0
  273. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_catalogs.py +0 -0
  274. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cgroup_probe.py +0 -0
  275. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_claude_error_detector.py +0 -0
  276. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_claude_foreign_schema.py +0 -0
  277. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli.py +0 -0
  278. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_common.py +0 -0
  279. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_init_install.py +0 -0
  280. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_main_boundary.py +0 -0
  281. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_monitor_http.py +0 -0
  282. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  283. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_cli_upgrade.py +0 -0
  284. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_code_precedence.py +0 -0
  285. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_codewhale_plugin.py +0 -0
  286. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_coerce_int_poison.py +0 -0
  287. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config.py +0 -0
  288. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_fresh_eyes.py +0 -0
  289. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_max_rounds.py +0 -0
  290. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_nested_strictness.py +0 -0
  291. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_prompt_delivery.py +0 -0
  292. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_schedule.py +0 -0
  293. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_stop_file.py +0 -0
  294. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_strictness.py +0 -0
  295. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  296. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_config_transient_error_action.py +0 -0
  297. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_context_store.py +0 -0
  298. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_control_char_config.py +0 -0
  299. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_default_dirty_handler.py +0 -0
  300. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_defenses.py +0 -0
  301. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_detect_hung_latch.py +0 -0
  302. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_detector_isolation.py +0 -0
  303. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_detector_protocol.py +0 -0
  304. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_docgen.py +0 -0
  305. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_effective_throttle_view.py +0 -0
  306. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_emit_config_migrated.py +0 -0
  307. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule.py +0 -0
  308. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  309. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_events.py +0 -0
  310. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd.py +0 -0
  311. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_rollover.py +0 -0
  312. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_events_cmd_tail.py +0 -0
  313. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_events_jsonl_decode.py +0 -0
  314. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_example_plugin.py +0 -0
  315. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_exec_prefix.py +0 -0
  316. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_exit_cause.py +0 -0
  317. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_exit_classifier.py +0 -0
  318. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  319. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_gemini_plugin.py +0 -0
  320. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_git_timeout.py +0 -0
  321. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_hook_failure_isolation.py +0 -0
  322. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_hook_redact_order.py +0 -0
  323. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_hooks.py +0 -0
  324. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_host_health.py +0 -0
  325. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress.py +0 -0
  326. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_http_progress_epoch.py +0 -0
  327. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  328. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_kimi_plugin.py +0 -0
  329. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_lifecycle.py +0 -0
  330. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_metrics.py +0 -0
  331. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  332. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_metrics_swap.py +0 -0
  333. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_migrate_cmd.py +0 -0
  334. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_migrations.py +0 -0
  335. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_assembly.py +0 -0
  336. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_cmd_severity.py +0 -0
  337. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_identity.py +0 -0
  338. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  339. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  340. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  341. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  342. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_detectors.py +0 -0
  343. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_event_tail.py +0 -0
  344. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_loop.py +0 -0
  345. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_on_alert.py +0 -0
  346. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_remote.py +0 -0
  347. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  348. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_monitor_schedule_pause.py +0 -0
  349. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_network_blip_signal_guard.py +0 -0
  350. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_open_round_log.py +0 -0
  351. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  352. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_outer_round_ceiling.py +0 -0
  353. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_peek_argparse.py +0 -0
  354. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_peek_project_isolation.py +0 -0
  355. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_peek_schedule.py +0 -0
  356. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_peek_select.py +0 -0
  357. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_phase_agent_launch.py +0 -0
  358. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_phase_profiles.py +0 -0
  359. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_phase_select.py +0 -0
  360. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_pi_plugin.py +0 -0
  361. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_constants.py +0 -0
  362. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_manifest.py +0 -0
  363. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_plugin_scan_parity.py +0 -0
  364. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_presets.py +0 -0
  365. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_primary_prompt_file_home.py +0 -0
  366. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_prompt_loader.py +0 -0
  367. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_read_json_hardening.py +0 -0
  368. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_redact.py +0 -0
  369. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_resolve.py +0 -0
  370. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd.py +0 -0
  371. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_cmd_sigterm.py +0 -0
  372. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_log_helpers.py +0 -0
  373. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome.py +0 -0
  374. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  375. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_round_view.py +0 -0
  376. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  377. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_runner_throttle.py +0 -0
  378. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_scaffold.py +0 -0
  379. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_schedule.py +0 -0
  380. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_cmd_bounded.py +0 -0
  381. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_config_broken.py +0 -0
  382. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_crash_loop.py +0 -0
  383. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_mem_gate.py +0 -0
  384. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_phase_schedule.py +0 -0
  385. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_policy.py +0 -0
  386. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round.py +0 -0
  387. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  388. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_log.py +0 -0
  389. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_oom_killed.py +0 -0
  390. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_round_terminate.py +0 -0
  391. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_schedule_gate.py +0 -0
  392. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_sentinel.py +0 -0
  393. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_serve_startup_hooks.py +0 -0
  394. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_service_mode.py +0 -0
  395. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_service_unit.py +0 -0
  396. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_startup_battery_exit.py +0 -0
  397. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_startup_check.py +0 -0
  398. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_substrate.py +0 -0
  399. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  400. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  401. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_epoch_bound.py +0 -0
  402. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_throttle_plugin_classification.py +0 -0
  403. {cli_agent_runner-0.3.1 → cli_agent_runner-0.3.2}/tests/unit/test_upgrade_migrate.py +0 -0
  404. {cli_agent_runner-0.3.1 → 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
@@ -123,6 +130,11 @@ unit_file
123
130
  rate_limit
124
131
  system_managed
125
132
 
133
+ # agent_runner.api_types.SpawnDecision
134
+ action
135
+ defer_s
136
+ reason
137
+
126
138
  # agent_runner.api_types.SystemMetrics
127
139
  mem_total_mb
128
140
  mem_available_mb
@@ -143,6 +155,7 @@ phase
143
155
  checks
144
156
  overlaps
145
157
  plan
158
+ sandbox
146
159
 
147
160
  # agent_runner.config.models.AgentConfig
148
161
  command
@@ -193,6 +206,9 @@ phase_policy
193
206
 
194
207
  # agent_runner.config.models.PluginsConfig
195
208
  disable
209
+ spawn_override_allow
210
+ sandbox
211
+ pin
196
212
  raw
197
213
 
198
214
  # agent_runner.config.models.Profile
@@ -289,6 +305,10 @@ anomaly_repetitive_window
289
305
  anomaly_repetitive_threshold
290
306
  vcs
291
307
 
308
+ # agent_runner.hooks.SpawnView
309
+ argv
310
+ env
311
+
292
312
  # agent_runner.hooks.VcsHookView
293
313
  dirty_action
294
314
  stash_idempotency_s
@@ -5,6 +5,15 @@ 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
+
8
17
  ## [0.3.1] - 2026-09-14
9
18
 
10
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.1
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)
@@ -39,6 +39,14 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
39
39
  emit_round_oom_killed,
40
40
  emit_round_resumed,
41
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
+ )
42
50
  from agent_runner._emit.rounds import ( # noqa: F401 — public re-export
43
51
  emit_fresh_eyes_round_triggered,
44
52
  emit_round_container_orphan_risk,
@@ -80,6 +88,12 @@ __all__ = [
80
88
  "emit_mem_loop",
81
89
  "emit_mem_loop_persistent",
82
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",
83
97
  "emit_rate_limit_stop",
84
98
  "emit_round_cgroup_memory",
85
99
  "emit_round_container_orphan_risk",
@@ -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
+ )
@@ -61,12 +61,20 @@ def peek(
61
61
  log: bool = False,
62
62
  events: int | None = None,
63
63
  select: str | None = None,
64
+ cfg: Any = None,
64
65
  ) -> ProjectState | Any:
65
- """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)."""
66
73
  from agent_runner import round_view
67
74
 
68
75
  work_dir = project if isinstance(project, Path) else Path.cwd()
69
- cfg = load_config(work_dir / "agent-runner.toml")
76
+ if cfg is None:
77
+ cfg = load_config(work_dir / "agent-runner.toml")
70
78
  log_dir = cfg.runtime.log_dir
71
79
  src = monitor.LocalSource(log_dir=log_dir)
72
80
  base_state = monitor.assemble_project_state(src, project=_project_name(work_dir))
@@ -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
@@ -1,6 +1,6 @@
1
1
  """The plugin ABI: one typed, declared capability manifest per plugin, read
2
- by the loader (agent_runner/__init__.py::_load_plugin_manifests) instead of
3
- relying on import-time register_*() side effects. One entry-point group,
2
+ by the loader (agent_runner/__init__.py::load_and_register_plugins) instead
3
+ of relying on import-time register_*() side effects. One entry-point group,
4
4
  `agent_runner.plugins`, each entry resolving to a module-level
5
5
  `PLUGIN = PluginManifest(...)`.
6
6
  """
@@ -17,6 +17,7 @@ from agent_runner.hooks import (
17
17
  PostRoundHook,
18
18
  PreRoundHook,
19
19
  ServeStartupHook,
20
+ SpawnHook,
20
21
  )
21
22
 
22
23
 
@@ -32,6 +33,7 @@ class PluginManifest:
32
33
  post_round_hooks: tuple[PostRoundHook, ...] = ()
33
34
  serve_startup_hooks: tuple[ServeStartupHook, ...] = ()
34
35
  dirty_handlers: tuple[DirtyHandler, ...] = ()
36
+ spawn_hooks: tuple[SpawnHook, ...] = ()
35
37
  detectors: tuple[Detector, ...] = ()
36
38
  event_kinds: tuple[str, ...] = ()
37
39
 
@@ -39,11 +41,38 @@ class PluginManifest:
39
41
  _LOADED_MANIFESTS: list[PluginManifest] = []
40
42
 
41
43
 
42
- def register_manifest(manifest: PluginManifest) -> None:
44
+ def loaded_manifest_names() -> list[str]:
45
+ """Names of every currently-registered manifest (order-preserving).
46
+ Mirrors the ``list(_LOADED_MANIFESTS)`` pattern; used by the loader for
47
+ idempotent re-load skipping and by doctor for third-party hash listing."""
48
+ return [m.name for m in _LOADED_MANIFESTS]
49
+
50
+
51
+ def register_manifest(
52
+ manifest: PluginManifest,
53
+ *,
54
+ builtin: bool = False,
55
+ module_path: str = "",
56
+ attr_path: str = "",
57
+ ) -> None:
43
58
  """Register every capability a manifest declares into its own registry.
44
59
  No import-time side effects — the loader calls this explicitly after
45
60
  resolving a plugin's `PLUGIN` attribute.
46
61
 
62
+ ``builtin`` records whether this manifest is a GENUINE builtin (verified by
63
+ ``is_builtin_provenance`` at the load path) — it is threaded to
64
+ ``register_dirty_handler`` so ``dispatch_dirty`` can grant in-process trust
65
+ by handler-object identity rather than by the collidable manifest name.
66
+ Defaults to False (fail-closed): any manifest registered outside the
67
+ verified load path — a direct ``register_manifest`` call, a test — is
68
+ treated as third-party and sandboxed.
69
+
70
+ ``module_path``/``attr_path`` are the DISCOVERED entry point's resolvable
71
+ location (``module:attr``); the loader threads them here so the trampoline
72
+ re-imports a third-party dirty handler / spawn hook from that exact path,
73
+ never from the collidable ``manifest.name`` — an entry-point name != the
74
+ manifest name (legal for third-party plugins) then still resolves.
75
+
47
76
  Raises ``ValueError`` up front if ``manifest.name`` collides with an
48
77
  already-registered manifest's ``.name`` — this is checked HERE, not by
49
78
  the underlying registries' own ``ensure_unique`` calls below, which only
@@ -79,7 +108,13 @@ def register_manifest(manifest: PluginManifest) -> None:
79
108
  for h in manifest.serve_startup_hooks:
80
109
  hooks.register_serve_startup_hook(h)
81
110
  for d in manifest.dirty_handlers:
82
- hooks.register_dirty_handler(d)
111
+ hooks.register_dirty_handler(
112
+ d, owner=manifest.name, builtin=builtin, module_path=module_path, attr_path=attr_path
113
+ )
114
+ for s in manifest.spawn_hooks:
115
+ hooks.register_spawn_hook(
116
+ s, owner=manifest.name, builtin=builtin, module_path=module_path, attr_path=attr_path
117
+ )
83
118
  for det in manifest.detectors:
84
119
  monitor.register_detector(det)
85
120
  for kind in manifest.event_kinds:
@@ -107,6 +142,15 @@ def unregister_by_name(names: set[str]) -> set[str]:
107
142
  _remove_by_identity(hooks._POST_ROUND_HOOKS, manifest.post_round_hooks)
108
143
  _remove_by_identity(hooks._SERVE_STARTUP_HOOKS, manifest.serve_startup_hooks)
109
144
  _remove_by_identity(hooks._DIRTY_HANDLERS, manifest.dirty_handlers)
145
+ for h in manifest.dirty_handlers:
146
+ hooks._DIRTY_HANDLER_OWNER.pop(id(h), None)
147
+ hooks._DIRTY_HANDLER_BUILTIN.pop(id(h), None)
148
+ hooks._DIRTY_HANDLER_MODULE.pop(id(h), None)
149
+ _remove_by_identity(hooks._SPAWN_HOOKS, manifest.spawn_hooks)
150
+ for h in manifest.spawn_hooks:
151
+ hooks._SPAWN_HOOK_OWNER.pop(id(h), None)
152
+ hooks._SPAWN_HOOK_BUILTIN.pop(id(h), None)
153
+ hooks._SPAWN_HOOK_MODULE.pop(id(h), None)
110
154
  _remove_by_identity(monitor._PLUGIN_DETECTORS, manifest.detectors)
111
155
  for kind in manifest.event_kinds:
112
156
  events._PLUGIN_KINDS.pop(kind, None)