agentirc-cli 0.10.7__tar.gz → 0.12.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.10.7 → agentirc_cli-0.12.0}/CHANGELOG.md +22 -0
  2. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/PKG-INFO +9 -1
  3. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/README.md +8 -0
  4. agentirc_cli-0.12.0/agentirc/__init__.py +1 -0
  5. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/cli.py +226 -32
  6. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/config.py +4 -0
  7. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/daemon.py +169 -0
  8. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/irc_transport.py +5 -1
  9. agentirc_cli-0.12.0/agentirc/learn_prompt.py +194 -0
  10. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/observer.py +23 -0
  11. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/cli.md +65 -1
  12. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/index.md +8 -0
  13. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/pyproject.toml +1 -1
  14. agentirc_cli-0.12.0/tests/test_daemon_ipc.py +105 -0
  15. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/uv.lock +1 -1
  16. agentirc_cli-0.10.7/agentirc/__init__.py +0 -1
  17. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.claude/skills/pr-review/SKILL.md +0 -0
  18. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.github/workflows/pages.yml +0 -0
  19. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.github/workflows/publish.yml +0 -0
  20. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.github/workflows/tests.yml +0 -0
  21. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.gitignore +0 -0
  22. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.markdownlint-cli2.yaml +0 -0
  23. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/.pr_agent.toml +0 -0
  24. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/CLAUDE.md +0 -0
  25. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/CNAME +0 -0
  26. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/Gemfile +0 -0
  27. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/Gemfile.lock +0 -0
  28. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/LICENSE +0 -0
  29. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/_config.yml +0 -0
  30. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/_sass/color_schemes/anthropic.scss +0 -0
  31. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/_sass/custom/custom.scss +0 -0
  32. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/__init__.py +0 -0
  33. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/__init__.py +0 -0
  34. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/__main__.py +0 -0
  35. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/agent_runner.py +0 -0
  36. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/ipc.py +0 -0
  37. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/message_buffer.py +0 -0
  38. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
  39. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/skill/__init__.py +0 -0
  40. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
  41. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/socket_server.py +0 -0
  42. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/supervisor.py +0 -0
  43. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/claude/webhook.py +0 -0
  44. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/__init__.py +0 -0
  45. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/agent_runner.py +0 -0
  46. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/config.py +0 -0
  47. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/daemon.py +0 -0
  48. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/ipc.py +0 -0
  49. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/irc_transport.py +0 -0
  50. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/message_buffer.py +0 -0
  51. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
  52. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/skill/__init__.py +0 -0
  53. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
  54. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/socket_server.py +0 -0
  55. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/supervisor.py +0 -0
  56. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/codex/webhook.py +0 -0
  57. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/__init__.py +0 -0
  58. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/agent_runner.py +0 -0
  59. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/config.py +0 -0
  60. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/daemon.py +0 -0
  61. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/ipc.py +0 -0
  62. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/irc_transport.py +0 -0
  63. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/message_buffer.py +0 -0
  64. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
  65. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
  66. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
  67. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/socket_server.py +0 -0
  68. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/supervisor.py +0 -0
  69. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/copilot/webhook.py +0 -0
  70. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/__init__.py +0 -0
  71. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/agent_runner.py +0 -0
  72. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/config.py +0 -0
  73. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/daemon.py +0 -0
  74. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/ipc.py +0 -0
  75. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/irc_transport.py +0 -0
  76. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/message_buffer.py +0 -0
  77. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/skill/SKILL.md +0 -0
  78. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/skill/__init__.py +0 -0
  79. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/skill/irc_client.py +0 -0
  80. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/socket_server.py +0 -0
  81. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/supervisor.py +0 -0
  82. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/clients/opencode/webhook.py +0 -0
  83. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/pidfile.py +0 -0
  84. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/__init__.py +0 -0
  85. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/commands.py +0 -0
  86. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/extensions/federation.md +0 -0
  87. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/extensions/history.md +0 -0
  88. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/message.py +0 -0
  89. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/protocol-index.md +0 -0
  90. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/protocol/replies.py +0 -0
  91. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/__init__.py +0 -0
  92. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/__main__.py +0 -0
  93. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/channel.py +0 -0
  94. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/client.py +0 -0
  95. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/config.py +0 -0
  96. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/ircd.py +0 -0
  97. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/remote_client.py +0 -0
  98. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/server_link.py +0 -0
  99. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/skill.py +0 -0
  100. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/skills/__init__.py +0 -0
  101. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/agentirc/server/skills/history.py +0 -0
  102. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/agent-client.md +0 -0
  103. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/agent-harness-spec.md +0 -0
  104. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/ci.md +0 -0
  105. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/configuration.md +0 -0
  106. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/context-management.md +0 -0
  107. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/irc-tools.md +0 -0
  108. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/overview.md +0 -0
  109. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/setup.md +0 -0
  110. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/supervisor.md +0 -0
  111. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/claude/webhooks.md +0 -0
  112. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/configuration.md +0 -0
  113. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/context-management.md +0 -0
  114. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/irc-tools.md +0 -0
  115. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/overview.md +0 -0
  116. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/setup.md +0 -0
  117. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/supervisor.md +0 -0
  118. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/codex/webhooks.md +0 -0
  119. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/configuration.md +0 -0
  120. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/context-management.md +0 -0
  121. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/irc-tools.md +0 -0
  122. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/overview.md +0 -0
  123. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/setup.md +0 -0
  124. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/supervisor.md +0 -0
  125. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/copilot/webhooks.md +0 -0
  126. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/configuration.md +0 -0
  127. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/context-management.md +0 -0
  128. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/irc-tools.md +0 -0
  129. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/overview.md +0 -0
  130. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/setup.md +0 -0
  131. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/supervisor.md +0 -0
  132. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/clients/opencode/webhooks.md +0 -0
  133. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/codex-backend.md +0 -0
  134. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/copilot-backend.md +0 -0
  135. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/design.md +0 -0
  136. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/docs-site.md +0 -0
  137. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/getting-started.md +0 -0
  138. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/grow-your-agent.md +0 -0
  139. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/layer1-core-irc.md +0 -0
  140. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/layer2-attention.md +0 -0
  141. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/layer3-skills.md +0 -0
  142. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/layer4-federation.md +0 -0
  143. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/layer5-agent-harness.md +0 -0
  144. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/opencode-backend.md +0 -0
  145. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/publishing.md +0 -0
  146. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  147. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/server-architecture.md +0 -0
  148. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  149. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  150. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  151. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  152. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/01-pair-programming.md +0 -0
  153. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
  154. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
  155. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
  156. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/05-the-observer.md +0 -0
  157. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/06-cross-server-ops.md +0 -0
  158. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
  159. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/08-apps-as-agents.md +0 -0
  160. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/09-research-swarm.md +0 -0
  161. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases/10-grow-your-agent.md +0 -0
  162. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/docs/use-cases-index.md +0 -0
  163. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/README.md +0 -0
  164. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/config.py +0 -0
  165. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/daemon.py +0 -0
  166. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/ipc.py +0 -0
  167. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/irc_transport.py +0 -0
  168. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/message_buffer.py +0 -0
  169. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/skill/SKILL.md +0 -0
  170. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/skill/irc_client.py +0 -0
  171. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/socket_server.py +0 -0
  172. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/packages/agent-harness/webhook.py +0 -0
  173. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  174. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  175. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
  176. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/__init__.py +0 -0
  177. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/conftest.py +0 -0
  178. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_agent_runner.py +0 -0
  179. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_channel.py +0 -0
  180. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_codex_daemon.py +0 -0
  181. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_connection.py +0 -0
  182. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_copilot_daemon.py +0 -0
  183. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_daemon.py +0 -0
  184. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_daemon_config.py +0 -0
  185. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_discovery.py +0 -0
  186. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_federation.py +0 -0
  187. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_history.py +0 -0
  188. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_integration_layer5.py +0 -0
  189. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_ipc.py +0 -0
  190. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_irc_transport.py +0 -0
  191. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_mentions.py +0 -0
  192. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_message.py +0 -0
  193. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_message_buffer.py +0 -0
  194. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_messaging.py +0 -0
  195. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_modes.py +0 -0
  196. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_opencode_daemon.py +0 -0
  197. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_skill_client.py +0 -0
  198. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_skills.py +0 -0
  199. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_socket_server.py +0 -0
  200. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_supervisor.py +0 -0
  201. {agentirc_cli-0.10.7 → agentirc_cli-0.12.0}/tests/test_webhook.py +0 -0
@@ -4,6 +4,28 @@ 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.12.0] - 2026-03-29
8
+
9
+
10
+ ### Added
11
+
12
+ - agentirc learn command — self-teaching prompt for agents to learn IRC tools and create skills
13
+
14
+ ## [0.11.0] - 2026-03-28
15
+
16
+
17
+ ### Added
18
+
19
+ - agentirc send command for sending messages to channels and agents
20
+ - agentirc status --full flag and per-agent detailed view
21
+ - agentirc sleep/wake commands with configurable schedule (default 23:00-08:00)
22
+
23
+
24
+ ### Changed
25
+
26
+ - Extended IPC protocol with status, pause, and resume handlers
27
+ - Added sleep_start/sleep_end config fields to DaemonConfig
28
+
7
29
  ## [0.10.7] - 2026-03-28
8
30
 
9
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 0.10.7
3
+ Version: 0.12.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
@@ -109,6 +109,14 @@ agentirc who "#general" # see who's in a channel
109
109
  agentirc read "#general" # read recent messages
110
110
  ```
111
111
 
112
+ ### Teach Your Agent
113
+
114
+ ```bash
115
+ agentirc learn
116
+ ```
117
+
118
+ Prints a self-teaching prompt your agent reads to learn how to use IRC tools, create skills, and participate in the mesh.
119
+
112
120
  ### Talk to an Agent
113
121
 
114
122
  Connect any IRC client (weechat, irssi) to localhost:6667:
@@ -88,6 +88,14 @@ agentirc who "#general" # see who's in a channel
88
88
  agentirc read "#general" # read recent messages
89
89
  ```
90
90
 
91
+ ### Teach Your Agent
92
+
93
+ ```bash
94
+ agentirc learn
95
+ ```
96
+
97
+ Prints a self-teaching prompt your agent reads to learn how to use IRC tools, create skills, and participate in the mesh.
98
+
91
99
  ### Talk to an Agent
92
100
 
93
101
  Connect any IRC client (weechat, irssi) to localhost:6667:
@@ -0,0 +1 @@
1
+ __version__ = "0.12.0"
@@ -5,10 +5,14 @@ Subcommands:
5
5
  agentirc init Register an agent for the current directory
6
6
  agentirc start [nick] [--all] Start agent daemon(s)
7
7
  agentirc stop [nick] [--all] Stop agent daemon(s)
8
- agentirc status List running agents
8
+ agentirc status [nick] [--full] List running agents (--full queries activity)
9
+ agentirc send <target> <message> Send a message to a channel or agent
9
10
  agentirc read <channel> Read recent channel messages
10
11
  agentirc who <channel> List channel members
11
12
  agentirc channels List active channels
13
+ agentirc learn [--nick X] Print self-teaching prompt for your agent
14
+ agentirc sleep [nick] [--all] Pause agent(s) — stay connected but idle
15
+ agentirc wake [nick] [--all] Resume paused agent(s)
12
16
  """
13
17
  from __future__ import annotations
14
18
 
@@ -115,6 +119,8 @@ def main() -> None:
115
119
 
116
120
  # -- status subcommand -------------------------------------------------
117
121
  status_parser = sub.add_parser("status", help="List running agents")
122
+ status_parser.add_argument("nick", nargs="?", help="Show detailed status for a specific agent")
123
+ status_parser.add_argument("--full", action="store_true", help="Query agents for activity status")
118
124
  status_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
119
125
 
120
126
  # -- read subcommand ---------------------------------------------------
@@ -128,10 +134,33 @@ def main() -> None:
128
134
  who_parser.add_argument("channel", help="Channel or nick target")
129
135
  who_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
130
136
 
137
+ # -- send subcommand ---------------------------------------------------
138
+ send_parser = sub.add_parser("send", help="Send a message to a channel or agent")
139
+ send_parser.add_argument("target", help="Channel (e.g. #general) or agent nick")
140
+ send_parser.add_argument("message", help="Message text to send")
141
+ send_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
142
+
131
143
  # -- channels subcommand -----------------------------------------------
132
144
  channels_parser = sub.add_parser("channels", help="List active channels")
133
145
  channels_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
134
146
 
147
+ # -- learn subcommand --------------------------------------------------
148
+ learn_parser = sub.add_parser("learn", help="Print self-teaching prompt for your agent")
149
+ learn_parser.add_argument("--nick", default=None, help="Agent nick (auto-detects from cwd)")
150
+ learn_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
151
+
152
+ # -- sleep subcommand --------------------------------------------------
153
+ sleep_parser = sub.add_parser("sleep", help="Pause agent(s) — stay connected but idle")
154
+ sleep_parser.add_argument("nick", nargs="?", help="Agent nick to pause")
155
+ sleep_parser.add_argument("--all", action="store_true", help="Pause all agents")
156
+ sleep_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
157
+
158
+ # -- wake subcommand ---------------------------------------------------
159
+ wake_parser = sub.add_parser("wake", help="Resume paused agent(s)")
160
+ wake_parser.add_argument("nick", nargs="?", help="Agent nick to resume")
161
+ wake_parser.add_argument("--all", action="store_true", help="Resume all agents")
162
+ wake_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
163
+
135
164
  # -- skills subcommand -------------------------------------------------
136
165
  skills_parser = sub.add_parser("skills", help="Install IRC skills for AI agents")
137
166
  skills_sub = skills_parser.add_subparsers(dest="skills_command")
@@ -159,9 +188,13 @@ def main() -> None:
159
188
  "start": _cmd_start,
160
189
  "stop": _cmd_stop,
161
190
  "status": _cmd_status,
191
+ "send": _cmd_send,
162
192
  "read": _cmd_read,
163
193
  "who": _cmd_who,
164
194
  "channels": _cmd_channels,
195
+ "learn": _cmd_learn,
196
+ "sleep": _cmd_sleep,
197
+ "wake": _cmd_wake,
165
198
  "skills": _cmd_skills,
166
199
  }
167
200
  handler = dispatch.get(args.command)
@@ -652,21 +685,33 @@ def _stop_agent(nick: str) -> None:
652
685
  print(f"Agent '{nick}' killed")
653
686
 
654
687
 
655
- async def _ipc_shutdown(socket_path: str) -> bool:
656
- """Send a shutdown command via Unix socket IPC."""
688
+ async def _ipc_request(socket_path: str, msg_type: str, **kwargs) -> dict | None:
689
+ """Send an IPC request via Unix socket and return the response."""
657
690
  from agentirc.clients.claude.ipc import decode_message, encode_message, make_request
658
691
 
659
- reader, writer = await asyncio.wait_for(
660
- asyncio.open_unix_connection(socket_path),
661
- timeout=3.0,
662
- )
663
692
  try:
664
- req = make_request("shutdown")
693
+ reader, writer = await asyncio.wait_for(
694
+ asyncio.open_unix_connection(socket_path),
695
+ timeout=3.0,
696
+ )
697
+ except (ConnectionRefusedError, FileNotFoundError, OSError):
698
+ return None
699
+ try:
700
+ req = make_request(msg_type, **kwargs)
665
701
  writer.write(encode_message(req))
666
702
  await writer.drain()
667
- data = await asyncio.wait_for(reader.readline(), timeout=3.0)
668
- resp = decode_message(data)
669
- return resp is not None and resp.get("ok", False)
703
+ # Read lines until we get a response (skip whispers)
704
+ deadline = asyncio.get_event_loop().time() + 3.0
705
+ while True:
706
+ remaining = deadline - asyncio.get_event_loop().time()
707
+ if remaining <= 0:
708
+ return None
709
+ data = await asyncio.wait_for(reader.readline(), timeout=remaining)
710
+ msg = decode_message(data)
711
+ if msg and msg.get("type") == "response":
712
+ return msg
713
+ except (asyncio.TimeoutError, ConnectionError, BrokenPipeError, OSError):
714
+ return None
670
715
  finally:
671
716
  writer.close()
672
717
  try:
@@ -675,10 +720,37 @@ async def _ipc_shutdown(socket_path: str) -> bool:
675
720
  pass
676
721
 
677
722
 
723
+ async def _ipc_shutdown(socket_path: str) -> bool:
724
+ """Send a shutdown command via Unix socket IPC."""
725
+ resp = await _ipc_request(socket_path, "shutdown")
726
+ return resp is not None and resp.get("ok", False)
727
+
728
+
678
729
  # -----------------------------------------------------------------------
679
730
  # Agent status
680
731
  # -----------------------------------------------------------------------
681
732
 
733
+ def _agent_socket_path(nick: str) -> str:
734
+ return os.path.join(
735
+ os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
736
+ f"agentirc-{nick}.sock",
737
+ )
738
+
739
+
740
+ def _agent_process_status(agent) -> tuple[str, int | None]:
741
+ """Return (status_str, pid_or_none) for an agent."""
742
+ pid_name = f"agent-{agent.nick}"
743
+ pid = read_pid(pid_name)
744
+ if pid and is_process_alive(pid):
745
+ socket_path = _agent_socket_path(agent.nick)
746
+ if os.path.exists(socket_path):
747
+ return "running", pid
748
+ return "starting", pid
749
+ if pid:
750
+ remove_pid(pid_name)
751
+ return "stopped", None
752
+
753
+
682
754
  def _cmd_status(args: argparse.Namespace) -> None:
683
755
  config = load_config_or_default(args.config)
684
756
 
@@ -686,30 +758,69 @@ def _cmd_status(args: argparse.Namespace) -> None:
686
758
  print("No agents configured")
687
759
  return
688
760
 
689
- print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10}")
690
- print("-" * 52)
761
+ # Single agent detailed view
762
+ if args.nick:
763
+ agent = None
764
+ for a in config.agents:
765
+ if a.nick == args.nick:
766
+ agent = a
767
+ break
768
+ if not agent:
769
+ print(f"Agent '{args.nick}' not found in config", file=sys.stderr)
770
+ sys.exit(1)
691
771
 
692
- for agent in config.agents:
693
- pid_name = f"agent-{agent.nick}"
694
- pid = read_pid(pid_name)
695
- status = "stopped"
696
-
697
- if pid and is_process_alive(pid):
698
- # Also check if socket is connectable
699
- socket_path = os.path.join(
700
- os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
701
- f"agentirc-{agent.nick}.sock",
702
- )
703
- if os.path.exists(socket_path):
704
- status = "running"
772
+ status, pid = _agent_process_status(agent)
773
+ print(agent.nick)
774
+ print(f" Status: {status}")
775
+ print(f" PID: {pid or '-'}")
776
+
777
+ # Query IPC for activity if running ask the agent directly
778
+ if status == "running":
779
+ resp = asyncio.run(_ipc_request(
780
+ _agent_socket_path(agent.nick), "status", query=True
781
+ ))
782
+ if resp and resp.get("ok"):
783
+ data = resp.get("data", {})
784
+ print(f" Activity: {data.get('description', 'nothing')}")
785
+ print(f" Turns: {data.get('turn_count', 0)}")
786
+ print(f" Paused: {'yes' if data.get('paused') else 'no'}")
705
787
  else:
706
- status = "starting"
707
- print(f"{agent.nick:<30} {status:<12} {pid:<10}")
708
- elif pid:
709
- remove_pid(pid_name)
710
- print(f"{agent.nick:<30} {'stopped':<12} {'-':<10}")
788
+ print(f" Activity: unknown (daemon may need restart)")
789
+ else:
790
+ print(f" Activity: -")
791
+
792
+ channels = agent.channels if isinstance(agent.channels, list) else []
793
+ print(f" Directory: {agent.directory}")
794
+ print(f" Backend: {agent.agent}")
795
+ print(f" Channels: {', '.join(channels)}")
796
+ print(f" Model: {agent.model}")
797
+ print(f" Config: {args.config}")
798
+ return
799
+
800
+ # All agents view
801
+ show_activity = args.full
802
+
803
+ if show_activity:
804
+ print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10} {'ACTIVITY'}")
805
+ print("-" * 72)
806
+ else:
807
+ print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10}")
808
+ print("-" * 52)
809
+
810
+ for agent in config.agents:
811
+ status, pid = _agent_process_status(agent)
812
+ activity = "-"
813
+
814
+ if show_activity and status == "running":
815
+ # Use cached description (no live query — too slow for all agents)
816
+ resp = asyncio.run(_ipc_request(_agent_socket_path(agent.nick), "status"))
817
+ if resp and resp.get("ok"):
818
+ activity = resp.get("data", {}).get("description", "nothing")
819
+
820
+ if show_activity:
821
+ print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10} {activity}")
711
822
  else:
712
- print(f"{agent.nick:<30} {'stopped':<12} {'-':<10}")
823
+ print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10}")
713
824
 
714
825
 
715
826
  # -----------------------------------------------------------------------
@@ -728,6 +839,89 @@ def _get_observer(config_path: str):
728
839
  )
729
840
 
730
841
 
842
+ def _ipc_to_agents(args: argparse.Namespace, msg_type: str, action_verb: str) -> None:
843
+ """Send an IPC message (pause/resume) to one or all agents."""
844
+ config = load_config_or_default(args.config)
845
+
846
+ if args.nick and args.all:
847
+ print(f"Cannot specify both nick and --all", file=sys.stderr)
848
+ sys.exit(1)
849
+
850
+ if not args.nick and not args.all:
851
+ print(f"Usage: agentirc {action_verb} <nick> or --all", file=sys.stderr)
852
+ sys.exit(1)
853
+
854
+ targets = config.agents if args.all else []
855
+ if args.nick:
856
+ for a in config.agents:
857
+ if a.nick == args.nick:
858
+ targets = [a]
859
+ break
860
+ else:
861
+ print(f"Agent '{args.nick}' not found in config", file=sys.stderr)
862
+ sys.exit(1)
863
+
864
+ for agent in targets:
865
+ socket_path = _agent_socket_path(agent.nick)
866
+ resp = asyncio.run(_ipc_request(socket_path, msg_type))
867
+ if resp and resp.get("ok"):
868
+ print(f"{agent.nick}: {action_verb}")
869
+ else:
870
+ print(f"{agent.nick}: failed (not running?)", file=sys.stderr)
871
+
872
+
873
+ def _cmd_sleep(args: argparse.Namespace) -> None:
874
+ _ipc_to_agents(args, "pause", "paused")
875
+
876
+
877
+ def _cmd_wake(args: argparse.Namespace) -> None:
878
+ _ipc_to_agents(args, "resume", "resumed")
879
+
880
+
881
+ def _cmd_learn(args: argparse.Namespace) -> None:
882
+ from agentirc.learn_prompt import generate_learn_prompt
883
+
884
+ config = load_config_or_default(args.config)
885
+ cwd = os.getcwd()
886
+
887
+ # Find agent: by --nick flag, or by matching cwd to an agent's directory
888
+ agent = None
889
+ if args.nick:
890
+ for a in config.agents:
891
+ if a.nick == args.nick:
892
+ agent = a
893
+ break
894
+ if not agent:
895
+ print(f"Agent '{args.nick}' not found in config", file=sys.stderr)
896
+ sys.exit(1)
897
+ else:
898
+ for a in config.agents:
899
+ if os.path.realpath(a.directory) == os.path.realpath(cwd):
900
+ agent = a
901
+ break
902
+
903
+ if agent:
904
+ print(generate_learn_prompt(
905
+ nick=agent.nick,
906
+ server=config.server.name,
907
+ directory=agent.directory,
908
+ backend=agent.agent,
909
+ channels=agent.channels,
910
+ ))
911
+ else:
912
+ print(generate_learn_prompt(
913
+ server=config.server.name,
914
+ directory=cwd,
915
+ ))
916
+
917
+
918
+ def _cmd_send(args: argparse.Namespace) -> None:
919
+ observer = _get_observer(args.config)
920
+ target = args.target if args.target.startswith("#") else args.target
921
+ asyncio.run(observer.send_message(target, args.message))
922
+ print(f"Sent to {target}")
923
+
924
+
731
925
  def _cmd_read(args: argparse.Namespace) -> None:
732
926
  observer = _get_observer(args.config)
733
927
  channel = args.channel if args.channel.startswith("#") else f"#{args.channel}"
@@ -56,6 +56,8 @@ class DaemonConfig:
56
56
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
57
57
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
58
58
  buffer_size: int = 500
59
+ sleep_start: str = "23:00"
60
+ sleep_end: str = "08:00"
59
61
  agents: list[AgentConfig] = field(default_factory=list)
60
62
 
61
63
  def get_agent(self, nick: str) -> AgentConfig | None:
@@ -84,6 +86,8 @@ def load_config(path: str | Path) -> DaemonConfig:
84
86
  supervisor=supervisor,
85
87
  webhooks=webhooks,
86
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"),
87
91
  agents=agents,
88
92
  )
89
93
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import datetime
4
5
  import logging
5
6
  import os
6
7
  import time
@@ -53,6 +54,15 @@ class AgentDaemon:
53
54
  self._crash_times: list[float] = []
54
55
  self._circuit_open = False
55
56
 
57
+ # Pause/sleep state
58
+ self._paused: bool = False
59
+ self._last_activation: float | None = None
60
+
61
+ # Status query state — for asking the agent what it's doing
62
+ self._status_query_event: asyncio.Event | None = None
63
+ self._status_query_response: str = ""
64
+ self._last_activity_text: str = ""
65
+
56
66
  # Graceful shutdown
57
67
  self._stop_event: asyncio.Event | None = None
58
68
  self._pid_name: str = ""
@@ -112,12 +122,23 @@ class AgentDaemon:
112
122
  if not self.skip_claude:
113
123
  await self._start_agent_runner()
114
124
 
125
+ # 7. Sleep scheduler background task
126
+ self._sleep_task = asyncio.create_task(self._sleep_scheduler())
127
+
115
128
  logger.info(
116
129
  "AgentDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path
117
130
  )
118
131
 
119
132
  async def stop(self) -> None:
120
133
  """Cleanly shut down all components."""
134
+ if hasattr(self, "_sleep_task") and self._sleep_task:
135
+ self._sleep_task.cancel()
136
+ try:
137
+ await self._sleep_task
138
+ except asyncio.CancelledError:
139
+ pass
140
+ self._sleep_task = None
141
+
121
142
  if self._agent_runner is not None:
122
143
  await self._agent_runner.stop()
123
144
  self._agent_runner = None
@@ -136,6 +157,54 @@ class AgentDaemon:
136
157
 
137
158
  logger.info("AgentDaemon stopped for %s", self.agent.nick)
138
159
 
160
+ def _parse_sleep_schedule(self) -> tuple[int, int] | None:
161
+ """Parse sleep_start/sleep_end into minutes. Returns None if invalid."""
162
+ try:
163
+ sh, sm = (int(x) for x in self.config.sleep_start.split(":"))
164
+ wh, wm = (int(x) for x in self.config.sleep_end.split(":"))
165
+ if not (0 <= sh <= 23 and 0 <= sm <= 59 and 0 <= wh <= 23 and 0 <= wm <= 59):
166
+ raise ValueError("hours/minutes out of range")
167
+ return (sh * 60 + sm, wh * 60 + wm)
168
+ except (ValueError, AttributeError):
169
+ logger.warning(
170
+ "Invalid sleep schedule '%s'-'%s' for %s — scheduler disabled",
171
+ getattr(self.config, "sleep_start", None),
172
+ getattr(self.config, "sleep_end", None),
173
+ self.agent.nick,
174
+ )
175
+ return None
176
+
177
+ async def _sleep_scheduler(self) -> None:
178
+ """Background task that auto-pauses/resumes based on sleep schedule."""
179
+ schedule = self._parse_sleep_schedule()
180
+ if schedule is None:
181
+ return
182
+ sleep_minutes, wake_minutes = schedule
183
+
184
+ while True:
185
+ try:
186
+ await asyncio.sleep(60) # Check every minute
187
+ now = datetime.datetime.now()
188
+ current_minutes = now.hour * 60 + now.minute
189
+
190
+ if sleep_minutes > wake_minutes:
191
+ # Overnight: e.g., 23:00-08:00
192
+ should_sleep = current_minutes >= sleep_minutes or current_minutes < wake_minutes
193
+ else:
194
+ # Same day: e.g., 13:00-14:00
195
+ should_sleep = sleep_minutes <= current_minutes < wake_minutes
196
+
197
+ if should_sleep and not self._paused:
198
+ self._paused = True
199
+ logger.info("Sleep schedule: pausing %s", self.agent.nick)
200
+ elif not should_sleep and self._paused:
201
+ self._paused = False
202
+ logger.info("Sleep schedule: resuming %s", self.agent.nick)
203
+ except asyncio.CancelledError:
204
+ return
205
+ except Exception:
206
+ logger.exception("Sleep scheduler error")
207
+
139
208
  async def _graceful_shutdown(self) -> None:
140
209
  """Trigger a graceful shutdown, signaling any waiting stop event."""
141
210
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -169,7 +238,10 @@ class AgentDaemon:
169
238
 
170
239
  Formats a prompt and enqueues it so the SDK session picks it up.
171
240
  """
241
+ if self._paused:
242
+ return
172
243
  if self._agent_runner and self._agent_runner.is_running():
244
+ self._last_activation = time.time()
173
245
  if target.startswith("#"):
174
246
  prompt = f"[IRC @mention in {target}] <{sender}> {text}"
175
247
  else:
@@ -181,6 +253,21 @@ class AgentDaemon:
181
253
  if self._supervisor:
182
254
  await self._supervisor.observe(msg)
183
255
 
256
+ # Capture last assistant text for status reporting
257
+ if msg.get("type") == "assistant":
258
+ for block in msg.get("content", []):
259
+ if isinstance(block, dict) and block.get("type") == "text":
260
+ self._last_activity_text = block["text"]
261
+ break
262
+ elif isinstance(block, str):
263
+ self._last_activity_text = block
264
+ break
265
+
266
+ # If a status query is pending, fulfill it
267
+ if self._status_query_event and not self._status_query_event.is_set():
268
+ self._status_query_response = self._last_activity_text
269
+ self._status_query_event.set()
270
+
184
271
  def _build_system_prompt(self) -> str:
185
272
  return (
186
273
  f"You are {self.agent.nick}, an AI agent on the agentirc IRC network.\n"
@@ -305,6 +392,15 @@ class AgentDaemon:
305
392
  elif msg_type == "clear":
306
393
  return await self._ipc_clear(req_id)
307
394
 
395
+ elif msg_type == "status":
396
+ return await self._ipc_status(req_id, msg)
397
+
398
+ elif msg_type == "pause":
399
+ return await self._ipc_pause(req_id)
400
+
401
+ elif msg_type == "resume":
402
+ return await self._ipc_resume(req_id)
403
+
308
404
  elif msg_type == "shutdown":
309
405
  asyncio.create_task(self._graceful_shutdown())
310
406
  return make_response(req_id, ok=True)
@@ -320,6 +416,79 @@ class AgentDaemon:
320
416
  # IPC sub-handlers
321
417
  # ------------------------------------------------------------------
322
418
 
419
+ async def _ipc_pause(self, req_id: str) -> dict:
420
+ self._paused = True
421
+ logger.info("Agent %s paused", self.agent.nick)
422
+ return make_response(req_id, ok=True)
423
+
424
+ async def _ipc_resume(self, req_id: str) -> dict:
425
+ self._paused = False
426
+ logger.info("Agent %s resumed", self.agent.nick)
427
+ # NOTE: Catch-up on missed messages is not yet implemented.
428
+ # IRCTransport does not process HISTORY responses into the buffer.
429
+ # The agent resumes and will see new messages going forward.
430
+ return make_response(req_id, ok=True)
431
+
432
+ async def _ipc_status(self, req_id: str, msg: dict | None = None) -> dict:
433
+ running = self._agent_runner is not None and self._agent_runner.is_running()
434
+ turn_count = self._supervisor._turn_count if self._supervisor else 0
435
+
436
+ # Determine activity description
437
+ query = msg.get("query", False) if msg else False
438
+ description = self._describe_activity(live_query=query)
439
+
440
+ # If live query requested and agent is active, ask the agent directly
441
+ if query and running and not self._paused:
442
+ description = await self._query_agent_status()
443
+
444
+ return make_response(req_id, ok=True, data={
445
+ "running": running,
446
+ "paused": self._paused,
447
+ "turn_count": turn_count,
448
+ "last_activation": self._last_activation,
449
+ "activity": "paused" if self._paused else ("working" if running else "idle"),
450
+ "description": description,
451
+ })
452
+
453
+ def _describe_activity(self, live_query: bool = False) -> str:
454
+ """Return a human-readable description of what the agent is doing."""
455
+ if self._paused:
456
+ return "paused"
457
+ if not self._last_activity_text:
458
+ return "nothing"
459
+ # Return first line of last activity, truncated
460
+ first_line = self._last_activity_text.strip().split("\n")[0]
461
+ if len(first_line) > 120:
462
+ first_line = first_line[:117] + "..."
463
+ return first_line
464
+
465
+ async def _query_agent_status(self) -> str:
466
+ """Ask the agent directly what it's working on."""
467
+ if not self._agent_runner or not self._agent_runner.is_running():
468
+ return "nothing"
469
+
470
+ self._status_query_event = asyncio.Event()
471
+ self._status_query_response = ""
472
+
473
+ try:
474
+ await self._agent_runner.send_prompt(
475
+ "[SYSTEM] Briefly describe what you are currently working on "
476
+ "in one sentence. Reply with just the description, no preamble."
477
+ )
478
+ # Wait up to 10s for the agent to respond
479
+ await asyncio.wait_for(self._status_query_event.wait(), timeout=10.0)
480
+ response = self._status_query_response.strip()
481
+ # Take first line, truncate
482
+ first_line = response.split("\n")[0]
483
+ if len(first_line) > 120:
484
+ first_line = first_line[:117] + "..."
485
+ return first_line or "nothing"
486
+ except asyncio.TimeoutError:
487
+ return "busy (no response)"
488
+ finally:
489
+ self._status_query_event = None
490
+ self._status_query_response = ""
491
+
323
492
  async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
324
493
  channel = msg.get("channel", "")
325
494
  text = msg.get("message", "")
@@ -82,11 +82,15 @@ class IRCTransport:
82
82
  async def send_who(self, target: str) -> None:
83
83
  await self._send_raw(f"WHO {target}")
84
84
 
85
- async def _send_raw(self, line: str) -> None:
85
+ async def send_raw(self, line: str) -> None:
86
+ """Send a raw IRC line. Public for commands like HISTORY."""
86
87
  if self._writer:
87
88
  self._writer.write(f"{line}\r\n".encode())
88
89
  await self._writer.drain()
89
90
 
91
+ async def _send_raw(self, line: str) -> None:
92
+ await self.send_raw(line)
93
+
90
94
  async def _read_loop(self) -> None:
91
95
  buf = ""
92
96
  try: