agentirc-cli 0.12.1__tar.gz → 0.13.0__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 (201) hide show
  1. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/CHANGELOG.md +10 -0
  2. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/PKG-INFO +1 -1
  3. agentirc_cli-0.13.0/agentirc/__init__.py +1 -0
  4. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/config.py +2 -0
  5. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/daemon.py +3 -0
  6. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/supervisor.py +3 -1
  7. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/config.py +6 -0
  8. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/daemon.py +175 -0
  9. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/supervisor.py +10 -1
  10. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/config.py +6 -0
  11. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/daemon.py +175 -0
  12. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/supervisor.py +12 -1
  13. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/config.py +6 -0
  14. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/daemon.py +175 -0
  15. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/supervisor.py +11 -1
  16. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/configuration.md +4 -0
  17. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/configuration.md +4 -0
  18. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/configuration.md +4 -0
  19. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/configuration.md +4 -0
  20. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/config.py +6 -0
  21. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/daemon.py +131 -0
  22. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/pyproject.toml +1 -1
  23. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/uv.lock +1 -1
  24. agentirc_cli-0.12.1/agentirc/__init__.py +0 -1
  25. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.claude/skills/pr-review/SKILL.md +0 -0
  26. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.github/workflows/pages.yml +0 -0
  27. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.github/workflows/publish.yml +0 -0
  28. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.github/workflows/tests.yml +0 -0
  29. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.gitignore +0 -0
  30. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.markdownlint-cli2.yaml +0 -0
  31. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/.pr_agent.toml +0 -0
  32. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/CLAUDE.md +0 -0
  33. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/CNAME +0 -0
  34. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/Gemfile +0 -0
  35. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/Gemfile.lock +0 -0
  36. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/LICENSE +0 -0
  37. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/README.md +0 -0
  38. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/_config.yml +0 -0
  39. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/_sass/color_schemes/anthropic.scss +0 -0
  40. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/_sass/custom/custom.scss +0 -0
  41. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/cli.py +0 -0
  42. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/__init__.py +0 -0
  43. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/__init__.py +0 -0
  44. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/__main__.py +0 -0
  45. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/agent_runner.py +0 -0
  46. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/ipc.py +0 -0
  47. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/irc_transport.py +0 -0
  48. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/message_buffer.py +0 -0
  49. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
  50. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/skill/__init__.py +0 -0
  51. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
  52. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/socket_server.py +0 -0
  53. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/claude/webhook.py +0 -0
  54. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/__init__.py +0 -0
  55. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/agent_runner.py +0 -0
  56. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/ipc.py +0 -0
  57. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/irc_transport.py +0 -0
  58. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/message_buffer.py +0 -0
  59. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
  60. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/skill/__init__.py +0 -0
  61. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
  62. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/socket_server.py +0 -0
  63. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/codex/webhook.py +0 -0
  64. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/__init__.py +0 -0
  65. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/agent_runner.py +0 -0
  66. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/ipc.py +0 -0
  67. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/irc_transport.py +0 -0
  68. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/message_buffer.py +0 -0
  69. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
  70. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
  71. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
  72. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/socket_server.py +0 -0
  73. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/copilot/webhook.py +0 -0
  74. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/__init__.py +0 -0
  75. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/agent_runner.py +0 -0
  76. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/ipc.py +0 -0
  77. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/irc_transport.py +0 -0
  78. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/message_buffer.py +0 -0
  79. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/skill/SKILL.md +0 -0
  80. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/skill/__init__.py +0 -0
  81. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/skill/irc_client.py +0 -0
  82. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/socket_server.py +0 -0
  83. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/clients/opencode/webhook.py +0 -0
  84. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/learn_prompt.py +0 -0
  85. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/observer.py +0 -0
  86. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/pidfile.py +0 -0
  87. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/__init__.py +0 -0
  88. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/commands.py +0 -0
  89. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/extensions/federation.md +0 -0
  90. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/extensions/history.md +0 -0
  91. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/message.py +0 -0
  92. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/protocol-index.md +0 -0
  93. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/protocol/replies.py +0 -0
  94. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/__init__.py +0 -0
  95. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/__main__.py +0 -0
  96. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/channel.py +0 -0
  97. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/client.py +0 -0
  98. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/config.py +0 -0
  99. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/ircd.py +0 -0
  100. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/remote_client.py +0 -0
  101. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/server_link.py +0 -0
  102. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/skill.py +0 -0
  103. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/skills/__init__.py +0 -0
  104. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/agentirc/server/skills/history.py +0 -0
  105. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/agent-client.md +0 -0
  106. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/agent-harness-spec.md +0 -0
  107. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/ci.md +0 -0
  108. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/cli.md +0 -0
  109. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/context-management.md +0 -0
  110. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/irc-tools.md +0 -0
  111. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/overview.md +0 -0
  112. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/setup.md +0 -0
  113. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/supervisor.md +0 -0
  114. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/claude/webhooks.md +0 -0
  115. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/context-management.md +0 -0
  116. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/irc-tools.md +0 -0
  117. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/overview.md +0 -0
  118. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/setup.md +0 -0
  119. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/supervisor.md +0 -0
  120. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/codex/webhooks.md +0 -0
  121. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/context-management.md +0 -0
  122. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/irc-tools.md +0 -0
  123. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/overview.md +0 -0
  124. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/setup.md +0 -0
  125. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/supervisor.md +0 -0
  126. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/copilot/webhooks.md +0 -0
  127. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/context-management.md +0 -0
  128. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/irc-tools.md +0 -0
  129. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/overview.md +0 -0
  130. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/setup.md +0 -0
  131. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/supervisor.md +0 -0
  132. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/clients/opencode/webhooks.md +0 -0
  133. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/codex-backend.md +0 -0
  134. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/copilot-backend.md +0 -0
  135. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/design.md +0 -0
  136. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/docs-site.md +0 -0
  137. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/getting-started.md +0 -0
  138. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/grow-your-agent.md +0 -0
  139. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/layer1-core-irc.md +0 -0
  140. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/layer2-attention.md +0 -0
  141. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/layer3-skills.md +0 -0
  142. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/layer4-federation.md +0 -0
  143. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/layer5-agent-harness.md +0 -0
  144. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/opencode-backend.md +0 -0
  145. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/publishing.md +0 -0
  146. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  147. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/server-architecture.md +0 -0
  148. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  149. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  150. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  151. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  152. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/01-pair-programming.md +0 -0
  153. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
  154. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
  155. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
  156. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/05-the-observer.md +0 -0
  157. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/06-cross-server-ops.md +0 -0
  158. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
  159. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/08-apps-as-agents.md +0 -0
  160. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/09-research-swarm.md +0 -0
  161. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases/10-grow-your-agent.md +0 -0
  162. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/docs/use-cases-index.md +0 -0
  163. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/index.md +0 -0
  164. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/README.md +0 -0
  165. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/ipc.py +0 -0
  166. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/irc_transport.py +0 -0
  167. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/message_buffer.py +0 -0
  168. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/skill/SKILL.md +0 -0
  169. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/skill/irc_client.py +0 -0
  170. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/socket_server.py +0 -0
  171. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/packages/agent-harness/webhook.py +0 -0
  172. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  173. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  174. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
  175. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/__init__.py +0 -0
  176. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/conftest.py +0 -0
  177. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_agent_runner.py +0 -0
  178. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_channel.py +0 -0
  179. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_codex_daemon.py +0 -0
  180. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_connection.py +0 -0
  181. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_copilot_daemon.py +0 -0
  182. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_daemon.py +0 -0
  183. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_daemon_config.py +0 -0
  184. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_daemon_ipc.py +0 -0
  185. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_discovery.py +0 -0
  186. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_federation.py +0 -0
  187. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_history.py +0 -0
  188. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_integration_layer5.py +0 -0
  189. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_ipc.py +0 -0
  190. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_irc_transport.py +0 -0
  191. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_mentions.py +0 -0
  192. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_message.py +0 -0
  193. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_message_buffer.py +0 -0
  194. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_messaging.py +0 -0
  195. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_modes.py +0 -0
  196. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_opencode_daemon.py +0 -0
  197. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_skill_client.py +0 -0
  198. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_skills.py +0 -0
  199. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_socket_server.py +0 -0
  200. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_supervisor.py +0 -0
  201. {agentirc_cli-0.12.1 → agentirc_cli-0.13.0}/tests/test_webhook.py +0 -0
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.13.0] - 2026-03-29
8
+
9
+ ### Added
10
+
11
+ - `system_prompt` field in AgentConfig — custom system prompt via agents.yaml (all backends)
12
+ - `prompt_override` field in SupervisorConfig — custom supervisor eval prompt via config (all backends)
13
+ - Status/pause/resume IPC handlers for OpenCode, Codex, and Copilot daemons (parity with Claude)
14
+ - Sleep scheduler with `sleep_start`/`sleep_end` config for OpenCode, Codex, and Copilot
15
+ - Null relay target fix in `_query_agent_status()` to prevent misrouting
16
+
7
17
  ## [0.12.1] - 2026-03-29
8
18
 
9
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: IRC protocol chatrooms for AI agents (and humans allowed)
5
5
  Project-URL: Homepage, https://github.com/OriNachum/agentirc
6
6
  Author: Ori Nachum
@@ -0,0 +1 @@
1
+ __version__ = "0.13.0"
@@ -25,6 +25,7 @@ class SupervisorConfig:
25
25
  window_size: int = 20
26
26
  eval_interval: int = 5
27
27
  escalation_threshold: int = 3
28
+ prompt_override: str = ""
28
29
 
29
30
 
30
31
  @dataclass
@@ -47,6 +48,7 @@ class AgentConfig:
47
48
  channels: list[str] = field(default_factory=lambda: ["#general"])
48
49
  model: str = "claude-opus-4-6"
49
50
  thinking: str = "medium"
51
+ system_prompt: str = ""
50
52
 
51
53
 
52
54
  @dataclass
@@ -113,6 +113,7 @@ class AgentDaemon:
113
113
  evaluate_fn=make_sdk_evaluate_fn(
114
114
  model=self.config.supervisor.model,
115
115
  thinking=self.config.supervisor.thinking,
116
+ prompt_override=self.config.supervisor.prompt_override,
116
117
  ),
117
118
  on_whisper=self._on_supervisor_whisper,
118
119
  on_escalation=self._on_supervisor_escalation,
@@ -269,6 +270,8 @@ class AgentDaemon:
269
270
  self._status_query_event.set()
270
271
 
271
272
  def _build_system_prompt(self) -> str:
273
+ if self.agent.system_prompt:
274
+ return self.agent.system_prompt
272
275
  return (
273
276
  f"You are {self.agent.nick}, an AI agent on the agentirc IRC network.\n"
274
277
  f"You have IRC tools available via the irc skill. Use them to communicate.\n"
@@ -115,8 +115,10 @@ def _format_window(window: list[dict[str, Any]], task: str) -> str:
115
115
  def make_sdk_evaluate_fn(
116
116
  model: str = "claude-sonnet-4-6",
117
117
  thinking: str | None = None,
118
+ prompt_override: str = "",
118
119
  ) -> EvaluateFn:
119
120
  """Create an evaluate_fn that uses the Claude Agent SDK."""
121
+ system_prompt = prompt_override or _SUPERVISOR_SYSTEM_PROMPT
120
122
 
121
123
  async def evaluate(window: list[dict[str, Any]], task: str) -> SupervisorVerdict:
122
124
  prompt = _format_window(window, task)
@@ -124,7 +126,7 @@ def make_sdk_evaluate_fn(
124
126
  opts = ClaudeAgentOptions(
125
127
  model=model,
126
128
  max_turns=1,
127
- system_prompt=_SUPERVISOR_SYSTEM_PROMPT,
129
+ system_prompt=system_prompt,
128
130
  tools=[],
129
131
  )
130
132
  if thinking:
@@ -24,6 +24,7 @@ class SupervisorConfig:
24
24
  window_size: int = 20
25
25
  eval_interval: int = 5
26
26
  escalation_threshold: int = 3
27
+ prompt_override: str = ""
27
28
 
28
29
 
29
30
  @dataclass
@@ -45,6 +46,7 @@ class AgentConfig:
45
46
  directory: str = "."
46
47
  channels: list[str] = field(default_factory=lambda: ["#general"])
47
48
  model: str = "gpt-5.4"
49
+ system_prompt: str = ""
48
50
 
49
51
 
50
52
  @dataclass
@@ -54,6 +56,8 @@ class DaemonConfig:
54
56
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
55
57
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
56
58
  buffer_size: int = 500
59
+ sleep_start: str = "23:00"
60
+ sleep_end: str = "08:00"
57
61
  agents: list[AgentConfig] = field(default_factory=list)
58
62
 
59
63
  def get_agent(self, nick: str) -> AgentConfig | None:
@@ -82,6 +86,8 @@ def load_config(path: str | Path) -> DaemonConfig:
82
86
  supervisor=supervisor,
83
87
  webhooks=webhooks,
84
88
  buffer_size=raw.get("buffer_size", 500),
89
+ sleep_start=raw.get("sleep_start", "23:00"),
90
+ sleep_end=raw.get("sleep_end", "08:00"),
85
91
  agents=agents,
86
92
  )
87
93
 
@@ -7,6 +7,7 @@ CodexSupervisor (codex exec for periodic evaluation).
7
7
  from __future__ import annotations
8
8
 
9
9
  import asyncio
10
+ import datetime
10
11
  import logging
11
12
  import os
12
13
  import time
@@ -66,6 +67,15 @@ class CodexDaemon:
66
67
  self._crash_times: list[float] = []
67
68
  self._circuit_open = False
68
69
 
70
+ # Pause/sleep state
71
+ self._paused: bool = False
72
+ self._last_activation: float | None = None
73
+
74
+ # Status query state — for asking the agent what it's doing
75
+ self._status_query_event: asyncio.Event | None = None
76
+ self._status_query_response: str = ""
77
+ self._last_activity_text: str = ""
78
+
69
79
  # Graceful shutdown
70
80
  self._stop_event: asyncio.Event | None = None
71
81
  self._pid_name: str = ""
@@ -114,6 +124,7 @@ class CodexDaemon:
114
124
  window_size=self.config.supervisor.window_size,
115
125
  eval_interval=self.config.supervisor.eval_interval,
116
126
  escalation_threshold=self.config.supervisor.escalation_threshold,
127
+ prompt_override=self.config.supervisor.prompt_override,
117
128
  on_whisper=self._on_supervisor_whisper,
118
129
  on_escalation=self._on_supervisor_escalation,
119
130
  )
@@ -122,12 +133,23 @@ class CodexDaemon:
122
133
  if not self.skip_codex:
123
134
  await self._start_agent_runner()
124
135
 
136
+ # 7. Sleep scheduler background task
137
+ self._sleep_task = asyncio.create_task(self._sleep_scheduler())
138
+
125
139
  logger.info(
126
140
  "CodexDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path
127
141
  )
128
142
 
129
143
  async def stop(self) -> None:
130
144
  """Cleanly shut down all components."""
145
+ if hasattr(self, "_sleep_task") and self._sleep_task:
146
+ self._sleep_task.cancel()
147
+ try:
148
+ await self._sleep_task
149
+ except asyncio.CancelledError:
150
+ pass
151
+ self._sleep_task = None
152
+
131
153
  if self._agent_runner is not None:
132
154
  await self._agent_runner.stop()
133
155
  self._agent_runner = None
@@ -146,6 +168,54 @@ class CodexDaemon:
146
168
 
147
169
  logger.info("CodexDaemon stopped for %s", self.agent.nick)
148
170
 
171
+ def _parse_sleep_schedule(self) -> tuple[int, int] | None:
172
+ """Parse sleep_start/sleep_end into minutes. Returns None if invalid."""
173
+ try:
174
+ sh, sm = (int(x) for x in self.config.sleep_start.split(":"))
175
+ wh, wm = (int(x) for x in self.config.sleep_end.split(":"))
176
+ if not (0 <= sh <= 23 and 0 <= sm <= 59 and 0 <= wh <= 23 and 0 <= wm <= 59):
177
+ raise ValueError("hours/minutes out of range")
178
+ return (sh * 60 + sm, wh * 60 + wm)
179
+ except (ValueError, AttributeError):
180
+ logger.warning(
181
+ "Invalid sleep schedule '%s'-'%s' for %s — scheduler disabled",
182
+ getattr(self.config, "sleep_start", None),
183
+ getattr(self.config, "sleep_end", None),
184
+ self.agent.nick,
185
+ )
186
+ return None
187
+
188
+ async def _sleep_scheduler(self) -> None:
189
+ """Background task that auto-pauses/resumes based on sleep schedule."""
190
+ schedule = self._parse_sleep_schedule()
191
+ if schedule is None:
192
+ return
193
+ sleep_minutes, wake_minutes = schedule
194
+
195
+ while True:
196
+ try:
197
+ await asyncio.sleep(60) # Check every minute
198
+ now = datetime.datetime.now()
199
+ current_minutes = now.hour * 60 + now.minute
200
+
201
+ if sleep_minutes > wake_minutes:
202
+ # Overnight: e.g., 23:00-08:00
203
+ should_sleep = current_minutes >= sleep_minutes or current_minutes < wake_minutes
204
+ else:
205
+ # Same day: e.g., 13:00-14:00
206
+ should_sleep = sleep_minutes <= current_minutes < wake_minutes
207
+
208
+ if should_sleep and not self._paused:
209
+ self._paused = True
210
+ logger.info("Sleep schedule: pausing %s", self.agent.nick)
211
+ elif not should_sleep and self._paused:
212
+ self._paused = False
213
+ logger.info("Sleep schedule: resuming %s", self.agent.nick)
214
+ except asyncio.CancelledError:
215
+ return
216
+ except Exception:
217
+ logger.exception("Sleep scheduler error")
218
+
149
219
  async def _graceful_shutdown(self) -> None:
150
220
  """Trigger a graceful shutdown, signaling any waiting stop event."""
151
221
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -179,7 +249,10 @@ class CodexDaemon:
179
249
 
180
250
  Formats a prompt and enqueues it so the Codex session picks it up.
181
251
  """
252
+ if self._paused:
253
+ return
182
254
  if self._agent_runner and self._agent_runner.is_running():
255
+ self._last_activation = time.time()
183
256
  # Enqueue relay target (FIFO matches prompt queue order)
184
257
  self._mention_targets.append(target if target.startswith("#") else sender)
185
258
  if target.startswith("#"):
@@ -209,7 +282,24 @@ class CodexDaemon:
209
282
  if self._supervisor:
210
283
  await self._supervisor.observe(msg)
211
284
 
285
+ # Capture last assistant text for status reporting
286
+ if msg.get("type") == "assistant":
287
+ for block in msg.get("content", []):
288
+ if isinstance(block, dict) and block.get("type") == "text":
289
+ self._last_activity_text = block["text"]
290
+ break
291
+ elif isinstance(block, str):
292
+ self._last_activity_text = block
293
+ break
294
+
295
+ # If a status query is pending, fulfill it
296
+ if self._status_query_event and not self._status_query_event.is_set():
297
+ self._status_query_response = self._last_activity_text
298
+ self._status_query_event.set()
299
+
212
300
  def _build_system_prompt(self) -> str:
301
+ if self.agent.system_prompt:
302
+ return self.agent.system_prompt
213
303
  return (
214
304
  f"You are {self.agent.nick}, an AI agent on the agentirc IRC network.\n"
215
305
  f"You have IRC tools available via the irc skill. Use them to communicate.\n"
@@ -333,6 +423,15 @@ class CodexDaemon:
333
423
  elif msg_type == "clear":
334
424
  return await self._ipc_clear(req_id)
335
425
 
426
+ elif msg_type == "status":
427
+ return await self._ipc_status(req_id, msg)
428
+
429
+ elif msg_type == "pause":
430
+ return await self._ipc_pause(req_id)
431
+
432
+ elif msg_type == "resume":
433
+ return await self._ipc_resume(req_id)
434
+
336
435
  elif msg_type == "shutdown":
337
436
  asyncio.create_task(self._graceful_shutdown())
338
437
  return make_response(req_id, ok=True)
@@ -348,6 +447,82 @@ class CodexDaemon:
348
447
  # IPC sub-handlers
349
448
  # ------------------------------------------------------------------
350
449
 
450
+ async def _ipc_pause(self, req_id: str) -> dict:
451
+ self._paused = True
452
+ logger.info("Agent %s paused", self.agent.nick)
453
+ return make_response(req_id, ok=True)
454
+
455
+ async def _ipc_resume(self, req_id: str) -> dict:
456
+ self._paused = False
457
+ logger.info("Agent %s resumed", self.agent.nick)
458
+ # NOTE: Catch-up on missed messages is not yet implemented.
459
+ # IRCTransport does not process HISTORY responses into the buffer.
460
+ # The agent resumes and will see new messages going forward.
461
+ return make_response(req_id, ok=True)
462
+
463
+ async def _ipc_status(self, req_id: str, msg: dict | None = None) -> dict:
464
+ running = self._agent_runner is not None and self._agent_runner.is_running()
465
+ turn_count = self._supervisor._turn_count if self._supervisor else 0
466
+
467
+ # Determine activity description
468
+ query = msg.get("query", False) if msg else False
469
+ description = self._describe_activity(live_query=query)
470
+
471
+ # If live query requested and agent is active, ask the agent directly
472
+ if query and running and not self._paused:
473
+ description = await self._query_agent_status()
474
+
475
+ return make_response(req_id, ok=True, data={
476
+ "running": running,
477
+ "paused": self._paused,
478
+ "turn_count": turn_count,
479
+ "last_activation": self._last_activation,
480
+ "activity": "paused" if self._paused else ("working" if running else "idle"),
481
+ "description": description,
482
+ })
483
+
484
+ def _describe_activity(self, live_query: bool = False) -> str:
485
+ """Return a human-readable description of what the agent is doing."""
486
+ if self._paused:
487
+ return "paused"
488
+ if not self._last_activity_text:
489
+ return "nothing"
490
+ # Return first line of last activity, truncated
491
+ first_line = self._last_activity_text.strip().split("\n")[0]
492
+ if len(first_line) > 120:
493
+ first_line = first_line[:117] + "..."
494
+ return first_line
495
+
496
+ async def _query_agent_status(self) -> str:
497
+ """Ask the agent directly what it's working on."""
498
+ if not self._agent_runner or not self._agent_runner.is_running():
499
+ return "nothing"
500
+
501
+ self._status_query_event = asyncio.Event()
502
+ self._status_query_response = ""
503
+
504
+ try:
505
+ # Enqueue a None relay target so the status response doesn't
506
+ # steal a real mention's relay target from the deque.
507
+ self._mention_targets.append(None)
508
+ await self._agent_runner.send_prompt(
509
+ "[SYSTEM] Briefly describe what you are currently working on "
510
+ "in one sentence. Reply with just the description, no preamble."
511
+ )
512
+ # Wait up to 10s for the agent to respond
513
+ await asyncio.wait_for(self._status_query_event.wait(), timeout=10.0)
514
+ response = self._status_query_response.strip()
515
+ # Take first line, truncate
516
+ first_line = response.split("\n")[0]
517
+ if len(first_line) > 120:
518
+ first_line = first_line[:117] + "..."
519
+ return first_line or "nothing"
520
+ except asyncio.TimeoutError:
521
+ return "busy (no response)"
522
+ finally:
523
+ self._status_query_event = None
524
+ self._status_query_response = ""
525
+
351
526
  async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
352
527
  channel = msg.get("channel", "")
353
528
  text = msg.get("message", "")
@@ -51,6 +51,7 @@ class CodexSupervisor:
51
51
  window_size: int = 20,
52
52
  eval_interval: int = 5,
53
53
  escalation_threshold: int = 3,
54
+ prompt_override: str = "",
54
55
  on_whisper: Callable[[str, str], Awaitable[None]] | None = None,
55
56
  on_escalation: Callable[[str], Awaitable[None]] | None = None,
56
57
  ):
@@ -58,6 +59,7 @@ class CodexSupervisor:
58
59
  self.window_size = window_size
59
60
  self.eval_interval = eval_interval
60
61
  self.escalation_threshold = escalation_threshold
62
+ self.prompt_override = prompt_override
61
63
  self.on_whisper = on_whisper
62
64
  self.on_escalation = on_escalation
63
65
 
@@ -86,7 +88,14 @@ class CodexSupervisor:
86
88
  async def _evaluate(self) -> None:
87
89
  """Run codex exec to evaluate the agent's recent activity."""
88
90
  transcript = self._format_transcript()
89
- prompt = SUPERVISOR_PROMPT.format(transcript=transcript)
91
+ template = self.prompt_override or SUPERVISOR_PROMPT
92
+ try:
93
+ prompt = template.format(transcript=transcript)
94
+ except (KeyError, IndexError, ValueError) as exc:
95
+ logger.warning(
96
+ "Invalid prompt_override template, falling back to default: %s", exc
97
+ )
98
+ prompt = SUPERVISOR_PROMPT.format(transcript=transcript)
90
99
 
91
100
  # Isolate from host config (~/.codex/, XDG, etc.)
92
101
  isolated_home = tempfile.mkdtemp(prefix="agentirc-codex-sv-")
@@ -24,6 +24,7 @@ class SupervisorConfig:
24
24
  window_size: int = 20
25
25
  eval_interval: int = 5
26
26
  escalation_threshold: int = 3
27
+ prompt_override: str = ""
27
28
 
28
29
 
29
30
  @dataclass
@@ -45,6 +46,7 @@ class AgentConfig:
45
46
  directory: str = "."
46
47
  channels: list[str] = field(default_factory=lambda: ["#general"])
47
48
  model: str = "gpt-4.1"
49
+ system_prompt: str = ""
48
50
 
49
51
 
50
52
  @dataclass
@@ -54,6 +56,8 @@ class DaemonConfig:
54
56
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
55
57
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
56
58
  buffer_size: int = 500
59
+ sleep_start: str = "23:00"
60
+ sleep_end: str = "08:00"
57
61
  agents: list[AgentConfig] = field(default_factory=list)
58
62
 
59
63
  def get_agent(self, nick: str) -> AgentConfig | None:
@@ -82,6 +86,8 @@ def load_config(path: str | Path) -> DaemonConfig:
82
86
  supervisor=supervisor,
83
87
  webhooks=webhooks,
84
88
  buffer_size=raw.get("buffer_size", 500),
89
+ sleep_start=raw.get("sleep_start", "23:00"),
90
+ sleep_end=raw.get("sleep_end", "08:00"),
85
91
  agents=agents,
86
92
  )
87
93
 
@@ -7,6 +7,7 @@ CopilotSupervisor (Copilot SDK for periodic evaluation).
7
7
  from __future__ import annotations
8
8
 
9
9
  import asyncio
10
+ import datetime
10
11
  import logging
11
12
  import os
12
13
  import time
@@ -66,6 +67,15 @@ class CopilotDaemon:
66
67
  self._crash_times: list[float] = []
67
68
  self._circuit_open = False
68
69
 
70
+ # Pause/sleep state
71
+ self._paused: bool = False
72
+ self._last_activation: float | None = None
73
+
74
+ # Status query state — for asking the agent what it's doing
75
+ self._status_query_event: asyncio.Event | None = None
76
+ self._status_query_response: str = ""
77
+ self._last_activity_text: str = ""
78
+
69
79
  # Graceful shutdown
70
80
  self._stop_event: asyncio.Event | None = None
71
81
  self._pid_name: str = ""
@@ -116,18 +126,30 @@ class CopilotDaemon:
116
126
  escalation_threshold=self.config.supervisor.escalation_threshold,
117
127
  on_whisper=self._on_supervisor_whisper,
118
128
  on_escalation=self._on_supervisor_escalation,
129
+ prompt_override=self.config.supervisor.prompt_override,
119
130
  )
120
131
 
121
132
  # 6. Optionally start the Copilot agent runner
122
133
  if not self.skip_copilot:
123
134
  await self._start_agent_runner()
124
135
 
136
+ # 7. Sleep scheduler background task
137
+ self._sleep_task = asyncio.create_task(self._sleep_scheduler())
138
+
125
139
  logger.info(
126
140
  "CopilotDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path
127
141
  )
128
142
 
129
143
  async def stop(self) -> None:
130
144
  """Cleanly shut down all components."""
145
+ if hasattr(self, "_sleep_task") and self._sleep_task:
146
+ self._sleep_task.cancel()
147
+ try:
148
+ await self._sleep_task
149
+ except asyncio.CancelledError:
150
+ pass
151
+ self._sleep_task = None
152
+
131
153
  if self._agent_runner is not None:
132
154
  await self._agent_runner.stop()
133
155
  self._agent_runner = None
@@ -146,6 +168,54 @@ class CopilotDaemon:
146
168
 
147
169
  logger.info("CopilotDaemon stopped for %s", self.agent.nick)
148
170
 
171
+ def _parse_sleep_schedule(self) -> tuple[int, int] | None:
172
+ """Parse sleep_start/sleep_end into minutes. Returns None if invalid."""
173
+ try:
174
+ sh, sm = (int(x) for x in self.config.sleep_start.split(":"))
175
+ wh, wm = (int(x) for x in self.config.sleep_end.split(":"))
176
+ if not (0 <= sh <= 23 and 0 <= sm <= 59 and 0 <= wh <= 23 and 0 <= wm <= 59):
177
+ raise ValueError("hours/minutes out of range")
178
+ return (sh * 60 + sm, wh * 60 + wm)
179
+ except (ValueError, AttributeError):
180
+ logger.warning(
181
+ "Invalid sleep schedule '%s'-'%s' for %s — scheduler disabled",
182
+ getattr(self.config, "sleep_start", None),
183
+ getattr(self.config, "sleep_end", None),
184
+ self.agent.nick,
185
+ )
186
+ return None
187
+
188
+ async def _sleep_scheduler(self) -> None:
189
+ """Background task that auto-pauses/resumes based on sleep schedule."""
190
+ schedule = self._parse_sleep_schedule()
191
+ if schedule is None:
192
+ return
193
+ sleep_minutes, wake_minutes = schedule
194
+
195
+ while True:
196
+ try:
197
+ await asyncio.sleep(60) # Check every minute
198
+ now = datetime.datetime.now()
199
+ current_minutes = now.hour * 60 + now.minute
200
+
201
+ if sleep_minutes > wake_minutes:
202
+ # Overnight: e.g., 23:00-08:00
203
+ should_sleep = current_minutes >= sleep_minutes or current_minutes < wake_minutes
204
+ else:
205
+ # Same day: e.g., 13:00-14:00
206
+ should_sleep = sleep_minutes <= current_minutes < wake_minutes
207
+
208
+ if should_sleep and not self._paused:
209
+ self._paused = True
210
+ logger.info("Sleep schedule: pausing %s", self.agent.nick)
211
+ elif not should_sleep and self._paused:
212
+ self._paused = False
213
+ logger.info("Sleep schedule: resuming %s", self.agent.nick)
214
+ except asyncio.CancelledError:
215
+ return
216
+ except Exception:
217
+ logger.exception("Sleep scheduler error")
218
+
149
219
  async def _graceful_shutdown(self) -> None:
150
220
  """Trigger a graceful shutdown, signaling any waiting stop event."""
151
221
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -186,7 +256,10 @@ class CopilotDaemon:
186
256
 
187
257
  Formats a prompt and enqueues it so the Copilot session picks it up.
188
258
  """
259
+ if self._paused:
260
+ return
189
261
  if self._agent_runner and self._agent_runner.is_running():
262
+ self._last_activation = time.time()
190
263
  # Enqueue relay target (FIFO matches prompt queue order)
191
264
  self._mention_targets.append(target if target.startswith("#") else sender)
192
265
  if target.startswith("#"):
@@ -216,7 +289,24 @@ class CopilotDaemon:
216
289
  if self._supervisor:
217
290
  await self._supervisor.observe(msg)
218
291
 
292
+ # Capture last assistant text for status reporting
293
+ if msg.get("type") == "assistant":
294
+ for block in msg.get("content", []):
295
+ if isinstance(block, dict) and block.get("type") == "text":
296
+ self._last_activity_text = block["text"]
297
+ break
298
+ elif isinstance(block, str):
299
+ self._last_activity_text = block
300
+ break
301
+
302
+ # If a status query is pending, fulfill it
303
+ if self._status_query_event and not self._status_query_event.is_set():
304
+ self._status_query_response = self._last_activity_text
305
+ self._status_query_event.set()
306
+
219
307
  def _build_system_prompt(self) -> str:
308
+ if self.agent.system_prompt:
309
+ return self.agent.system_prompt
220
310
  return (
221
311
  f"You are {self.agent.nick}, an AI agent on the agentirc IRC network.\n"
222
312
  f"You have IRC tools available via the irc skill. Use them to communicate.\n"
@@ -340,6 +430,15 @@ class CopilotDaemon:
340
430
  elif msg_type == "clear":
341
431
  return await self._ipc_clear(req_id)
342
432
 
433
+ elif msg_type == "status":
434
+ return await self._ipc_status(req_id, msg)
435
+
436
+ elif msg_type == "pause":
437
+ return await self._ipc_pause(req_id)
438
+
439
+ elif msg_type == "resume":
440
+ return await self._ipc_resume(req_id)
441
+
343
442
  elif msg_type == "shutdown":
344
443
  asyncio.create_task(self._graceful_shutdown())
345
444
  return make_response(req_id, ok=True)
@@ -355,6 +454,82 @@ class CopilotDaemon:
355
454
  # IPC sub-handlers
356
455
  # ------------------------------------------------------------------
357
456
 
457
+ async def _ipc_pause(self, req_id: str) -> dict:
458
+ self._paused = True
459
+ logger.info("Agent %s paused", self.agent.nick)
460
+ return make_response(req_id, ok=True)
461
+
462
+ async def _ipc_resume(self, req_id: str) -> dict:
463
+ self._paused = False
464
+ logger.info("Agent %s resumed", self.agent.nick)
465
+ # NOTE: Catch-up on missed messages is not yet implemented.
466
+ # IRCTransport does not process HISTORY responses into the buffer.
467
+ # The agent resumes and will see new messages going forward.
468
+ return make_response(req_id, ok=True)
469
+
470
+ async def _ipc_status(self, req_id: str, msg: dict | None = None) -> dict:
471
+ running = self._agent_runner is not None and self._agent_runner.is_running()
472
+ turn_count = self._supervisor._turn_count if self._supervisor else 0
473
+
474
+ # Determine activity description
475
+ query = msg.get("query", False) if msg else False
476
+ description = self._describe_activity(live_query=query)
477
+
478
+ # If live query requested and agent is active, ask the agent directly
479
+ if query and running and not self._paused:
480
+ description = await self._query_agent_status()
481
+
482
+ return make_response(req_id, ok=True, data={
483
+ "running": running,
484
+ "paused": self._paused,
485
+ "turn_count": turn_count,
486
+ "last_activation": self._last_activation,
487
+ "activity": "paused" if self._paused else ("working" if running else "idle"),
488
+ "description": description,
489
+ })
490
+
491
+ def _describe_activity(self, live_query: bool = False) -> str:
492
+ """Return a human-readable description of what the agent is doing."""
493
+ if self._paused:
494
+ return "paused"
495
+ if not self._last_activity_text:
496
+ return "nothing"
497
+ # Return first line of last activity, truncated
498
+ first_line = self._last_activity_text.strip().split("\n")[0]
499
+ if len(first_line) > 120:
500
+ first_line = first_line[:117] + "..."
501
+ return first_line
502
+
503
+ async def _query_agent_status(self) -> str:
504
+ """Ask the agent directly what it's working on."""
505
+ if not self._agent_runner or not self._agent_runner.is_running():
506
+ return "nothing"
507
+
508
+ self._status_query_event = asyncio.Event()
509
+ self._status_query_response = ""
510
+
511
+ try:
512
+ # Enqueue a None relay target so the status response doesn't
513
+ # steal a real mention's relay target from the deque.
514
+ self._mention_targets.append(None)
515
+ await self._agent_runner.send_prompt(
516
+ "[SYSTEM] Briefly describe what you are currently working on "
517
+ "in one sentence. Reply with just the description, no preamble."
518
+ )
519
+ # Wait up to 10s for the agent to respond
520
+ await asyncio.wait_for(self._status_query_event.wait(), timeout=10.0)
521
+ response = self._status_query_response.strip()
522
+ # Take first line, truncate
523
+ first_line = response.split("\n")[0]
524
+ if len(first_line) > 120:
525
+ first_line = first_line[:117] + "..."
526
+ return first_line or "nothing"
527
+ except asyncio.TimeoutError:
528
+ return "busy (no response)"
529
+ finally:
530
+ self._status_query_event = None
531
+ self._status_query_response = ""
532
+
358
533
  async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
359
534
  channel = msg.get("channel", "")
360
535
  text = msg.get("message", "")