ctrlrelay 0.20.1__tar.gz → 0.20.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 (210) hide show
  1. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/CHANGELOG.md +13 -1
  2. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/PKG-INFO +1 -1
  3. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/pyproject.toml +1 -1
  4. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/usage.py +10 -2
  5. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_usage.py +24 -0
  6. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  7. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  8. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  9. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/dependabot.yml +0 -0
  10. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/workflows/build.yml +0 -0
  11. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/workflows/cla.yml +0 -0
  12. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/workflows/pages.yml +0 -0
  13. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/workflows/publish.yml +0 -0
  14. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.github/workflows/test.yml +0 -0
  15. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/.gitignore +0 -0
  16. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/CODE_OF_CONDUCT.md +0 -0
  17. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/CONTRIBUTING.md +0 -0
  18. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/LICENSE +0 -0
  19. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/README.md +0 -0
  20. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/SECURITY.md +0 -0
  21. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/config/orchestrator.yaml.example +0 -0
  22. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/Gemfile +0 -0
  23. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/_config.yml +0 -0
  24. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/adding-a-machine.md +0 -0
  25. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/architecture.md +0 -0
  26. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/bridge.md +0 -0
  27. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/cli.md +0 -0
  28. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/configuration.md +0 -0
  29. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/development.md +0 -0
  30. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/feedback-loop.md +0 -0
  31. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/getting-started.md +0 -0
  32. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/index.md +0 -0
  33. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/llms.txt +0 -0
  34. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/operations.md +0 -0
  35. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/personalization.md +0 -0
  36. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/registered-mode.md +0 -0
  37. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/superpowers/plans/2026-10-30-status-page.md +0 -0
  38. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/docs/superpowers/specs/2026-10-30-status-page-design.md +0 -0
  39. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/__init__.py +0 -0
  40. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/__init__.py +0 -0
  41. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/__main__.py +0 -0
  42. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/factory.py +0 -0
  43. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/handler.py +0 -0
  44. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/mattermost_handler.py +0 -0
  45. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/node_tag.py +0 -0
  46. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/protocol.py +0 -0
  47. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/server.py +0 -0
  48. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/bridge/telegram_handler.py +0 -0
  49. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/cli.py +0 -0
  50. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/__init__.py +0 -0
  51. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/agent_env.py +0 -0
  52. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/archived.py +0 -0
  53. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/audit.py +0 -0
  54. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/binaries.py +0 -0
  55. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/checkpoint.py +0 -0
  56. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/claims.py +0 -0
  57. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/code_review.py +0 -0
  58. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/config.py +0 -0
  59. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/dispatch_health.py +0 -0
  60. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/dispatcher.py +0 -0
  61. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/events.py +0 -0
  62. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/github.py +0 -0
  63. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/github_budget.py +0 -0
  64. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/heartbeat.py +0 -0
  65. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/job_claims.py +0 -0
  66. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/network.py +0 -0
  67. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/node_check.py +0 -0
  68. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/obs.py +0 -0
  69. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/orphans.py +0 -0
  70. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/poller.py +0 -0
  71. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/pr_verifier.py +0 -0
  72. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/pr_watcher.py +0 -0
  73. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/publish.py +0 -0
  74. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/question_expiry.py +0 -0
  75. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/redact.py +0 -0
  76. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/scheduler.py +0 -0
  77. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/state.py +0 -0
  78. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/core/worktree.py +0 -0
  79. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/dashboard/__init__.py +0 -0
  80. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/dashboard/client.py +0 -0
  81. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/gh_protocol.py +0 -0
  82. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/install.py +0 -0
  83. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/personalization/__init__.py +0 -0
  84. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/personalization/manager.py +0 -0
  85. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/personalization/merge.py +0 -0
  86. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/personalization/paths.py +0 -0
  87. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/__init__.py +0 -0
  88. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/base.py +0 -0
  89. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/dev.py +0 -0
  90. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/post_merge.py +0 -0
  91. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/secops.py +0 -0
  92. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/pipelines/task.py +0 -0
  93. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/setup.py +0 -0
  94. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/__init__.py +0 -0
  95. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/global-CLAUDE.md.snippet +0 -0
  96. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/launchd/__init__.py +0 -0
  97. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/launchd/bridge.plist.template +0 -0
  98. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/launchd/poller.plist.template +0 -0
  99. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/systemd/__init__.py +0 -0
  100. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/systemd/bridge.service.template +0 -0
  101. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/systemd/poller.service.template +0 -0
  102. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/templates/systemd/web.service.template +0 -0
  103. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/transports/__init__.py +0 -0
  104. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/transports/base.py +0 -0
  105. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/transports/file_mock.py +0 -0
  106. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/transports/socket_client.py +0 -0
  107. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/__init__.py +0 -0
  108. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/fleet.py +0 -0
  109. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/health.py +0 -0
  110. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/links.py +0 -0
  111. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/numbers.py +0 -0
  112. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/server.py +0 -0
  113. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/static/index.html +0 -0
  114. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/status.py +0 -0
  115. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/transcript.py +0 -0
  116. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/src/ctrlrelay/web/waiting.py +0 -0
  117. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/__init__.py +0 -0
  118. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/_poller_fakes.py +0 -0
  119. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/_publish_fakes.py +0 -0
  120. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/conftest.py +0 -0
  121. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/fixtures/gh_search_issues.json +0 -0
  122. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/fixtures/stream_json_rate_limit.jsonl +0 -0
  123. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/fixtures/transcript_sample.jsonl +0 -0
  124. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_agent_backend_wiring.py +0 -0
  125. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_agent_no_keys.py +0 -0
  126. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_agent_routing.py +0 -0
  127. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_agent_sandbox.py +0 -0
  128. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_archived.py +0 -0
  129. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_audit.py +0 -0
  130. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_binary_resolution.py +0 -0
  131. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_bridge_protocol.py +0 -0
  132. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_bridge_server.py +0 -0
  133. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_bridge_start_command.py +0 -0
  134. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_chat_machines.py +0 -0
  135. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_checkpoint.py +0 -0
  136. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_claim_reconciliation.py +0 -0
  137. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_ci_wait.py +0 -0
  138. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_dev.py +0 -0
  139. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_repos.py +0 -0
  140. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_secops.py +0 -0
  141. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_start.py +0 -0
  142. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_cli_version.py +0 -0
  143. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_code_review.py +0 -0
  144. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_config.py +0 -0
  145. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dashboard_client.py +0 -0
  146. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dev_integration.py +0 -0
  147. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dev_pipeline.py +0 -0
  148. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dev_plan_step.py +0 -0
  149. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dev_step_switches.py +0 -0
  150. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dev_work_steps.py +0 -0
  151. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dispatch_health.py +0 -0
  152. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dispatch_outcome_hook.py +0 -0
  153. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_dispatcher.py +0 -0
  154. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_docs_site.py +0 -0
  155. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_event_emission.py +0 -0
  156. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_events.py +0 -0
  157. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_fleet.py +0 -0
  158. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_gh_protocol.py +0 -0
  159. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_github.py +0 -0
  160. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_github_budget.py +0 -0
  161. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_install.py +0 -0
  162. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_issue_outcome_announcement.py +0 -0
  163. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_job_claims.py +0 -0
  164. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_mattermost_handler.py +0 -0
  165. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_network.py +0 -0
  166. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_node_check.py +0 -0
  167. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_node_claims.py +0 -0
  168. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_notify_transport.py +0 -0
  169. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_obs.py +0 -0
  170. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_opencode_adapter.py +0 -0
  171. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_orphans.py +0 -0
  172. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_personalization.py +0 -0
  173. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_personalization_merge.py +0 -0
  174. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_pipeline_base.py +0 -0
  175. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_poller.py +0 -0
  176. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_poller_recheck.py +0 -0
  177. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_poller_search.py +0 -0
  178. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_post_merge.py +0 -0
  179. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_pr_verifier.py +0 -0
  180. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_pr_watcher.py +0 -0
  181. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_pre_dispatch_retry.py +0 -0
  182. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_publish_failure_retry.py +0 -0
  183. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_question_backoff.py +0 -0
  184. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_question_expiry.py +0 -0
  185. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_question_survives_restart.py +0 -0
  186. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_redact.py +0 -0
  187. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_relay_alert_off_lock.py +0 -0
  188. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_relay_git_263.py +0 -0
  189. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_resume_prompt_contract.py +0 -0
  190. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_review_copy.py +0 -0
  191. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_scheduler.py +0 -0
  192. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_secops_integration.py +0 -0
  193. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_secops_pipeline.py +0 -0
  194. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_setup.py +0 -0
  195. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_state.py +0 -0
  196. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_task_pipeline.py +0 -0
  197. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_telegram_handler.py +0 -0
  198. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_timeout_save.py +0 -0
  199. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_transport.py +0 -0
  200. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_transport_resolution.py +0 -0
  201. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_unreviewed_pr_announcement.py +0 -0
  202. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_health.py +0 -0
  203. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_links.py +0 -0
  204. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_numbers.py +0 -0
  205. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_peer_links.py +0 -0
  206. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_server.py +0 -0
  207. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_status.py +0 -0
  208. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_transcript.py +0 -0
  209. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_web_waiting.py +0 -0
  210. {ctrlrelay-0.20.1 → ctrlrelay-0.20.2}/tests/test_worktree.py +0 -0
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.2] - 2026-11-04
11
+
12
+ ### Fixed
13
+
14
+ - A node no longer stops taking work when the agent CLI reports its usage
15
+ status as `allowed_warning`. That status means a usage window has passed
16
+ a warning line, and calls are still served; it was treated as a refusal,
17
+ so nodes at 26% of the 7-day window paused until that window reset, a
18
+ week away. How close a window is to its limit is still decided by the
19
+ pause threshold, and any other status still pauses (#338).
20
+
10
21
  ## [0.20.1] - 2026-11-04
11
22
 
12
23
  ### Fixed
@@ -2348,7 +2359,8 @@ pipeline).
2348
2359
  per-phase implementation plans (Phase 0 through Phase 4).
2349
2360
  - `docs/Claude_Code_Project_Guide.md` — project development guide.
2350
2361
 
2351
- [Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.20.1...HEAD
2362
+ [Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.20.2...HEAD
2363
+ [0.20.2]: https://github.com/AInvirion/ctrlrelay/compare/v0.20.1...v0.20.2
2352
2364
  [0.20.1]: https://github.com/AInvirion/ctrlrelay/compare/v0.20.0...v0.20.1
2353
2365
  [0.20.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.19.1...v0.20.0
2354
2366
  [0.19.1]: https://github.com/AInvirion/ctrlrelay/compare/v0.19.0...v0.19.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ctrlrelay
3
- Version: 0.20.1
3
+ Version: 0.20.2
4
4
  Summary: Local-first orchestrator for headless coding agents across multiple GitHub repos
5
5
  Project-URL: Homepage, https://github.com/AInvirion/ctrlrelay
6
6
  Project-URL: Documentation, https://ainvirion.github.io/ctrlrelay/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ctrlrelay"
3
- version = "0.20.1"
3
+ version = "0.20.2"
4
4
  description = "Local-first orchestrator for headless coding agents across multiple GitHub repos"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -43,6 +43,14 @@ UNKNOWN_RESET_PAUSE_SECONDS = 3600
43
43
  # recursion limit (RecursionError). Any of them is unreadable, not fatal.
44
44
  _UNPARSEABLE = (ValueError, RecursionError)
45
45
  ALLOWED = "allowed"
46
+ # Statuses that still let a run through. The agent CLI reports
47
+ # ``allowed_warning`` once a window passes a warning line well short of
48
+ # its limit (seen at 26% of the 7-day window), and the call is served.
49
+ # Pausing on it idled machines for the rest of the 7-day window, so it
50
+ # counts as allowed; how close a window is to its limit is the
51
+ # threshold's question, not the status's. Any other status, including
52
+ # one not seen before, still pauses.
53
+ RUNNABLE_STATUSES = frozenset({ALLOWED, "allowed_warning"})
46
54
 
47
55
 
48
56
  @dataclass(frozen=True)
@@ -225,7 +233,7 @@ def pause_for(
225
233
  ) -> Pause | None:
226
234
  """Whether to stop taking new work.
227
235
 
228
- Paused when the status is not ``allowed`` or a window is at or above
236
+ Paused when the status is not one of ``RUNNABLE_STATUSES`` or a window is at or above
229
237
  ``threshold``, until that window's reset (for the status, the event's
230
238
  own reset). A reading whose reset time has passed says nothing about
231
239
  now, so it does not pause. When the reset time is missing or cannot
@@ -243,7 +251,7 @@ def pause_for(
243
251
  # From the reading, or from now if the reading claims to be from the
244
252
  # future (a clock set back): an hour, never longer.
245
253
  fallback = int(min(reading.read_at, now)) + UNKNOWN_RESET_PAUSE_SECONDS
246
- if reading.status != ALLOWED:
254
+ if reading.status not in RUNNABLE_STATUSES:
247
255
  until = reading.resets_at if reading.resets_at is not None else fallback
248
256
  hits.append((f"usage status is {reading.status}", until))
249
257
  for name, w in reading.windows.items():
@@ -110,6 +110,30 @@ class TestPause:
110
110
  p = pause_for(_reading(status="rejected"), threshold=0.9, now=NOW)
111
111
  assert p is not None and p.until == NOW + 3600
112
112
 
113
+ def test_allowed_warning_below_the_threshold_does_not_pause(self) -> None:
114
+ # The usage.json alkamax wrote on 2026-11-04: a warning status at
115
+ # 26% of the 7-day window, which had paused it for a week.
116
+ r = UsageReading.from_json(json.loads(
117
+ '{"status": "allowed_warning", "read_at": 1793857204, '
118
+ '"resets_at": 1794412800, '
119
+ '"seven_day": {"used": 0.26, "resets_at": 1794412800}}'
120
+ ))
121
+ assert r is not None and r.status == "allowed_warning"
122
+ assert pause_for(r, threshold=0.9, now=1793857204) is None
123
+
124
+ def test_allowed_warning_at_the_threshold_still_pauses(self) -> None:
125
+ p = pause_for(
126
+ _reading(status="allowed_warning",
127
+ windows={"seven_day": Window(0.95, NOW + 86400)}),
128
+ threshold=0.9, now=NOW,
129
+ )
130
+ assert p is not None and p.until == NOW + 86400
131
+ assert "status" not in p.reason
132
+
133
+ @pytest.mark.parametrize("status", ["rejected", "allowed_later", "", "ALLOWED"])
134
+ def test_any_other_status_pauses(self, status: str) -> None:
135
+ assert pause_for(_reading(status=status), threshold=0.9, now=NOW) is not None
136
+
113
137
  def test_after_the_reset_the_old_reading_does_not_pause(self) -> None:
114
138
  r = _reading(windows={"five_hour": Window(0.99, NOW + 600)})
115
139
  assert pause_for(r, threshold=0.9, now=NOW + 600) is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes