agentirc-cli 4.0.0__tar.gz → 4.1.1__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 (306) hide show
  1. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/CHANGELOG.md +17 -0
  2. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/PKG-INFO +1 -1
  3. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/agent_runner.py +22 -8
  4. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/config.py +2 -0
  5. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/daemon.py +37 -0
  6. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/irc_transport.py +7 -2
  7. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/config.py +2 -0
  8. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/daemon.py +36 -0
  9. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/irc_transport.py +7 -2
  10. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/config.py +2 -0
  11. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/daemon.py +37 -0
  12. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/irc_transport.py +7 -2
  13. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/config.py +2 -0
  14. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/daemon.py +37 -0
  15. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/irc_transport.py +7 -2
  16. agentirc_cli-4.1.1/docs/channel-polling.md +51 -0
  17. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/config.py +2 -0
  18. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/daemon.py +44 -0
  19. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/irc_transport.py +7 -2
  20. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/pyproject.toml +1 -1
  21. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_acp_daemon.py +46 -0
  22. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_console_client.py +1 -1
  23. agentirc_cli-4.1.1/tests/test_mention_alias.py +127 -0
  24. agentirc_cli-4.1.1/tests/test_poll_loop.py +129 -0
  25. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_room_persistence.py +3 -1
  26. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/uv.lock +1 -1
  27. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.claude/skills/pr-review/SKILL.md +0 -0
  28. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.claude/skills/run-tests/SKILL.md +0 -0
  29. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
  30. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.flake8 +0 -0
  31. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.github/workflows/pages.yml +0 -0
  32. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.github/workflows/publish.yml +0 -0
  33. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.github/workflows/security-checks.yml +0 -0
  34. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.github/workflows/tests.yml +0 -0
  35. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.gitignore +0 -0
  36. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.markdownlint-cli2.yaml +0 -0
  37. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.pr_agent.toml +0 -0
  38. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.pre-commit-config.yaml +0 -0
  39. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/.pylintrc +0 -0
  40. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/CLAUDE.md +0 -0
  41. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/CNAME +0 -0
  42. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/Gemfile +0 -0
  43. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/Gemfile.lock +0 -0
  44. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/LICENSE +0 -0
  45. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/README.md +0 -0
  46. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/SECURITY.md +0 -0
  47. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/_config.yml +0 -0
  48. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/_sass/color_schemes/anthropic.scss +0 -0
  49. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/_sass/custom/custom.scss +0 -0
  50. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/__init__.py +0 -0
  51. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/__main__.py +0 -0
  52. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/__init__.py +0 -0
  53. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/bot.py +0 -0
  54. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/bot_manager.py +0 -0
  55. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/config.py +0 -0
  56. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/http_listener.py +0 -0
  57. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/template_engine.py +0 -0
  58. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/bots/virtual_client.py +0 -0
  59. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/__init__.py +0 -0
  60. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/_helpers.py +0 -0
  61. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/agent.py +0 -0
  62. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/bot.py +0 -0
  63. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/channel.py +0 -0
  64. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/mesh.py +0 -0
  65. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/server.py +0 -0
  66. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/cli/skills.py +0 -0
  67. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/__init__.py +0 -0
  68. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/__init__.py +0 -0
  69. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/ipc.py +0 -0
  70. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/message_buffer.py +0 -0
  71. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/skill/SKILL.md +0 -0
  72. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/skill/__init__.py +0 -0
  73. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/skill/irc_client.py +0 -0
  74. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/socket_server.py +0 -0
  75. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/supervisor.py +0 -0
  76. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/acp/webhook.py +0 -0
  77. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/__init__.py +0 -0
  78. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/__main__.py +0 -0
  79. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/agent_runner.py +0 -0
  80. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/ipc.py +0 -0
  81. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/message_buffer.py +0 -0
  82. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/skill/SKILL.md +0 -0
  83. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/skill/__init__.py +0 -0
  84. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/skill/irc_client.py +0 -0
  85. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/socket_server.py +0 -0
  86. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/supervisor.py +0 -0
  87. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/claude/webhook.py +0 -0
  88. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/__init__.py +0 -0
  89. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/agent_runner.py +0 -0
  90. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/ipc.py +0 -0
  91. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/message_buffer.py +0 -0
  92. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/skill/SKILL.md +0 -0
  93. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/skill/__init__.py +0 -0
  94. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/skill/irc_client.py +0 -0
  95. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/socket_server.py +0 -0
  96. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/supervisor.py +0 -0
  97. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/codex/webhook.py +0 -0
  98. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/__init__.py +0 -0
  99. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/agent_runner.py +0 -0
  100. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/ipc.py +0 -0
  101. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/message_buffer.py +0 -0
  102. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/skill/SKILL.md +0 -0
  103. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/skill/__init__.py +0 -0
  104. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/skill/irc_client.py +0 -0
  105. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/socket_server.py +0 -0
  106. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/supervisor.py +0 -0
  107. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/clients/copilot/webhook.py +0 -0
  108. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/__init__.py +0 -0
  109. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/app.py +0 -0
  110. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/client.py +0 -0
  111. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/commands.py +0 -0
  112. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/widgets/__init__.py +0 -0
  113. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/widgets/chat.py +0 -0
  114. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/widgets/info_panel.py +0 -0
  115. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/console/widgets/sidebar.py +0 -0
  116. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/credentials.py +0 -0
  117. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/learn_prompt.py +0 -0
  118. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/mesh_config.py +0 -0
  119. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/observer.py +0 -0
  120. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/__init__.py +0 -0
  121. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/collector.py +0 -0
  122. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/model.py +0 -0
  123. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/renderer_text.py +0 -0
  124. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/renderer_web.py +0 -0
  125. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/overview/web/style.css +0 -0
  126. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/persistence.py +0 -0
  127. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/pidfile.py +0 -0
  128. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/__init__.py +0 -0
  129. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/commands.py +0 -0
  130. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/federation.md +0 -0
  131. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/history.md +0 -0
  132. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/icons.md +0 -0
  133. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/rooms.md +0 -0
  134. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/tags.md +0 -0
  135. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/extensions/threads.md +0 -0
  136. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/message.py +0 -0
  137. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/protocol-index.md +0 -0
  138. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/protocol/replies.py +0 -0
  139. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/__init__.py +0 -0
  140. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/__main__.py +0 -0
  141. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/channel.py +0 -0
  142. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/client.py +0 -0
  143. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/config.py +0 -0
  144. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/ircd.py +0 -0
  145. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/remote_client.py +0 -0
  146. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/room_store.py +0 -0
  147. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/rooms_util.py +0 -0
  148. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/server_link.py +0 -0
  149. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skill.py +0 -0
  150. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skills/__init__.py +0 -0
  151. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skills/history.py +0 -0
  152. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skills/icon.py +0 -0
  153. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skills/rooms.py +0 -0
  154. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/skills/threads.py +0 -0
  155. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/server/thread_store.py +0 -0
  156. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/culture/skills/culture/SKILL.md +0 -0
  157. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/agent-lifecycle.md +0 -0
  158. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/agentic-self-learn.md +0 -0
  159. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/agent-client.md +0 -0
  160. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/agent-harness-spec.md +0 -0
  161. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/design.md +0 -0
  162. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/harness-conformance.md +0 -0
  163. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/index.md +0 -0
  164. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/layer1-core-irc.md +0 -0
  165. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/layer2-attention.md +0 -0
  166. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/layer3-skills.md +0 -0
  167. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/layer4-federation.md +0 -0
  168. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/layer5-agent-harness.md +0 -0
  169. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/server-architecture.md +0 -0
  170. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/architecture/threads.md +0 -0
  171. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/acp/overview.md +0 -0
  172. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/configuration.md +0 -0
  173. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/context-management.md +0 -0
  174. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/irc-tools.md +0 -0
  175. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/overview.md +0 -0
  176. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/setup.md +0 -0
  177. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/supervisor.md +0 -0
  178. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/claude/webhooks.md +0 -0
  179. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/configuration.md +0 -0
  180. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/context-management.md +0 -0
  181. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/irc-tools.md +0 -0
  182. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/overview.md +0 -0
  183. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/setup.md +0 -0
  184. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/supervisor.md +0 -0
  185. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/codex/webhooks.md +0 -0
  186. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/configuration.md +0 -0
  187. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/context-management.md +0 -0
  188. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/irc-tools.md +0 -0
  189. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/overview.md +0 -0
  190. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/setup.md +0 -0
  191. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/supervisor.md +0 -0
  192. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/clients/copilot/webhooks.md +0 -0
  193. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/culture-cli.md +0 -0
  194. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/getting-started.md +0 -0
  195. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/index.md +0 -0
  196. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/SECURITY.md +0 -0
  197. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/bots.md +0 -0
  198. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/ci.md +0 -0
  199. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/cli.md +0 -0
  200. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/docs-site.md +0 -0
  201. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/index.md +0 -0
  202. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/ops-tooling.md +0 -0
  203. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/overview.md +0 -0
  204. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/operations/publishing.md +0 -0
  205. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  206. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/rooms.md +0 -0
  207. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/server-rename.md +0 -0
  208. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  209. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  210. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  211. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  212. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  213. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  214. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  215. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  216. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  217. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  218. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  219. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  220. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  221. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  222. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  223. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  224. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  225. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  226. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  227. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  228. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  229. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/01-pair-programming.md +0 -0
  230. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/02-code-review-ensemble.md +0 -0
  231. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/03-cross-server-delegation.md +0 -0
  232. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/04-knowledge-propagation.md +0 -0
  233. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/05-the-observer.md +0 -0
  234. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/06-cross-server-ops.md +0 -0
  235. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/07-supervisor-intervention.md +0 -0
  236. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/08-apps-as-agents.md +0 -0
  237. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/09-research-swarm.md +0 -0
  238. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases/10-agent-lifecycle.md +0 -0
  239. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/use-cases-index.md +0 -0
  240. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/docs/what-is-culture.md +0 -0
  241. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/README.md +0 -0
  242. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/ipc.py +0 -0
  243. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/message_buffer.py +0 -0
  244. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/skill/SKILL.md +0 -0
  245. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/skill/irc_client.py +0 -0
  246. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/socket_server.py +0 -0
  247. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/packages/agent-harness/webhook.py +0 -0
  248. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  249. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  250. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  251. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  252. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/sonar-project.properties +0 -0
  253. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/__init__.py +0 -0
  254. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/conftest.py +0 -0
  255. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_agent_runner.py +0 -0
  256. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_bot.py +0 -0
  257. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_bot_config.py +0 -0
  258. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_bot_manager.py +0 -0
  259. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_bots_integration.py +0 -0
  260. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_channel.py +0 -0
  261. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_codex_daemon.py +0 -0
  262. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_connection.py +0 -0
  263. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_console_commands.py +0 -0
  264. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_console_connection.py +0 -0
  265. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_console_icons.py +0 -0
  266. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_console_integration.py +0 -0
  267. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_copilot_daemon.py +0 -0
  268. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_daemon.py +0 -0
  269. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_daemon_config.py +0 -0
  270. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_daemon_ipc.py +0 -0
  271. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_discovery.py +0 -0
  272. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_federation.py +0 -0
  273. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_history.py +0 -0
  274. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_http_listener.py +0 -0
  275. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_integration_layer5.py +0 -0
  276. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_ipc.py +0 -0
  277. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_irc_transport.py +0 -0
  278. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_link_reconnect.py +0 -0
  279. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_mentions.py +0 -0
  280. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_mesh_config.py +0 -0
  281. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_message.py +0 -0
  282. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_message_buffer.py +0 -0
  283. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_messaging.py +0 -0
  284. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_modes.py +0 -0
  285. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_overview_cli.py +0 -0
  286. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_overview_collector.py +0 -0
  287. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_overview_model.py +0 -0
  288. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_overview_renderer.py +0 -0
  289. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_overview_web.py +0 -0
  290. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_persistence.py +0 -0
  291. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_pidfile.py +0 -0
  292. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_rooms.py +0 -0
  293. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_rooms_federation.py +0 -0
  294. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_rooms_integration.py +0 -0
  295. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_server_icon_skill.py +0 -0
  296. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_setup_update_cli.py +0 -0
  297. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_skill_client.py +0 -0
  298. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_skills.py +0 -0
  299. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_socket_server.py +0 -0
  300. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_supervisor.py +0 -0
  301. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_template_engine.py +0 -0
  302. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_thread_buffer.py +0 -0
  303. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_threads.py +0 -0
  304. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_virtual_client.py +0 -0
  305. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_wait_for_port.py +0 -0
  306. {agentirc_cli-4.0.0 → agentirc_cli-4.1.1}/tests/test_webhook.py +0 -0
@@ -4,6 +4,23 @@ 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
+ ## [4.1.1] - 2026-04-06
8
+
9
+
10
+ ### Fixed
11
+
12
+ - Fix ACP/Codex/Copilot poll loop to use fire-and-forget (race condition fix)
13
+ - Increase ACP prompt timeout from 120s to 300s with retry on timeout (issue #115)
14
+ - Lower default poll_interval from 300s to 60s across all backends
15
+
16
+ ## [4.1.0] - 2026-04-06
17
+
18
+
19
+ ### Added
20
+
21
+ - Channel polling: agents periodically check channels for unread messages (configurable via poll_interval, default 5 minutes)
22
+ - Nick alias matching: @culture now triggers spark-culture (short suffix matching)
23
+
7
24
  ## [4.0.0] - 2026-04-06
8
25
 
9
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 4.0.0
3
+ Version: 4.1.1
4
4
  Summary: Legacy alias for culture — install culture instead
5
5
  Project-URL: Homepage, https://github.com/OriNachum/culture
6
6
  Author: Ori Nachum
@@ -353,14 +353,26 @@ class ACPAgentRunner:
353
353
 
354
354
  try:
355
355
  self._busy = True
356
- resp = await self._send_request(
357
- "session/prompt",
358
- {
359
- "sessionId": self._session_id,
360
- "prompt": [{"type": "text", "text": text}],
361
- },
362
- timeout=120,
363
- )
356
+ prompt_params = {
357
+ "sessionId": self._session_id,
358
+ "prompt": [{"type": "text", "text": text}],
359
+ }
360
+ try:
361
+ resp = await self._send_request(
362
+ "session/prompt",
363
+ prompt_params,
364
+ timeout=300,
365
+ )
366
+ except TimeoutError:
367
+ logger.warning(
368
+ "ACP prompt timed out, retrying once: %s",
369
+ text[:80],
370
+ )
371
+ resp = await self._send_request(
372
+ "session/prompt",
373
+ prompt_params,
374
+ timeout=300,
375
+ )
364
376
 
365
377
  result = resp.get("result", {})
366
378
  if "stopReason" in result:
@@ -372,6 +384,8 @@ class ACPAgentRunner:
372
384
 
373
385
  except Exception:
374
386
  logger.exception("ACP turn error")
387
+ finally:
388
+ self._busy = False
375
389
 
376
390
  except asyncio.CancelledError:
377
391
  raise
@@ -70,6 +70,7 @@ class DaemonConfig:
70
70
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
71
71
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
72
72
  buffer_size: int = 500
73
+ poll_interval: int = 60
73
74
  sleep_start: str = "23:00"
74
75
  sleep_end: str = "08:00"
75
76
  agents: list[AgentConfig] = field(default_factory=list)
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
100
101
  supervisor=supervisor,
101
102
  webhooks=webhooks,
102
103
  buffer_size=raw.get("buffer_size", 500),
104
+ poll_interval=raw.get("poll_interval", 60),
103
105
  sleep_start=raw.get("sleep_start", "23:00"),
104
106
  sleep_end=raw.get("sleep_end", "08:00"),
105
107
  agents=agents,
@@ -182,6 +182,9 @@ class ACPDaemon:
182
182
  # 7. Sleep scheduler background task
183
183
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
184
184
 
185
+ # 8. Channel poll background task
186
+ self._poll_task = asyncio.create_task(self._poll_loop())
187
+
185
188
  logger.info(
186
189
  "ACPDaemon started for %s (cmd=%s, socket=%s)",
187
190
  self.agent.nick,
@@ -191,6 +194,11 @@ class ACPDaemon:
191
194
 
192
195
  async def stop(self) -> None:
193
196
  """Cleanly shut down all components."""
197
+ if hasattr(self, "_poll_task") and self._poll_task:
198
+ self._poll_task.cancel()
199
+ await asyncio.gather(self._poll_task, return_exceptions=True)
200
+ self._poll_task = None
201
+
194
202
  if hasattr(self, "_sleep_task") and self._sleep_task:
195
203
  self._sleep_task.cancel()
196
204
  await asyncio.gather(self._sleep_task, return_exceptions=True)
@@ -264,6 +272,35 @@ class ACPDaemon:
264
272
  except Exception:
265
273
  logger.exception("Sleep scheduler error")
266
274
 
275
+ async def _poll_loop(self) -> None:
276
+ """Background task that periodically checks channels for unread messages."""
277
+ interval = self.config.poll_interval
278
+ if interval <= 0:
279
+ return
280
+ while True:
281
+ try:
282
+ await asyncio.sleep(interval)
283
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
284
+ continue
285
+ for channel in self.agent.channels:
286
+ msgs = self._buffer.read(channel)
287
+ if not msgs:
288
+ continue
289
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
290
+ prompt = (
291
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
292
+ f"{lines}\n\n"
293
+ f"Respond naturally if any messages need your attention."
294
+ )
295
+ self._mention_targets.append(channel)
296
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
297
+ self._background_tasks.add(task)
298
+ task.add_done_callback(self._background_tasks.discard)
299
+ except asyncio.CancelledError:
300
+ raise
301
+ except Exception:
302
+ logger.exception("Poll loop error")
303
+
267
304
  async def _graceful_shutdown(self) -> None:
268
305
  """Trigger a graceful shutdown, signaling any waiting stop event."""
269
306
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import re
5
6
  from typing import Callable
6
7
 
7
8
  from culture.clients.acp.message_buffer import MessageBuffer
@@ -191,8 +192,12 @@ class IRCTransport:
191
192
  self.buffer.add(target, sender, text)
192
193
  else:
193
194
  self.buffer.add(f"DM:{sender}", sender, text)
194
- if self.on_mention and f"@{self.nick}" in text:
195
- self.on_mention(target, sender, text)
195
+ if self.on_mention:
196
+ short = self.nick.split("-", 1)[1] if "-" in self.nick else None
197
+ if re.search(rf"@{re.escape(self.nick)}\b", text) or (
198
+ short and re.search(rf"@{re.escape(short)}\b", text)
199
+ ):
200
+ self.on_mention(target, sender, text)
196
201
 
197
202
  async def _on_notice(self, msg: Message) -> None:
198
203
  if len(msg.params) < 2:
@@ -70,6 +70,7 @@ class DaemonConfig:
70
70
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
71
71
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
72
72
  buffer_size: int = 500
73
+ poll_interval: int = 60
73
74
  sleep_start: str = "23:00"
74
75
  sleep_end: str = "08:00"
75
76
  agents: list[AgentConfig] = field(default_factory=list)
@@ -104,6 +105,7 @@ def load_config(path: str | Path) -> DaemonConfig:
104
105
  supervisor=supervisor,
105
106
  webhooks=webhooks,
106
107
  buffer_size=raw.get("buffer_size", 500),
108
+ poll_interval=raw.get("poll_interval", 60),
107
109
  sleep_start=raw.get("sleep_start", "23:00"),
108
110
  sleep_end=raw.get("sleep_end", "08:00"),
109
111
  agents=agents,
@@ -153,10 +153,18 @@ class AgentDaemon:
153
153
  # 7. Sleep scheduler background task
154
154
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
155
155
 
156
+ # 8. Channel poll background task
157
+ self._poll_task = asyncio.create_task(self._poll_loop())
158
+
156
159
  logger.info("AgentDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
157
160
 
158
161
  async def stop(self) -> None:
159
162
  """Cleanly shut down all components."""
163
+ if hasattr(self, "_poll_task") and self._poll_task:
164
+ self._poll_task.cancel()
165
+ await asyncio.gather(self._poll_task, return_exceptions=True)
166
+ self._poll_task = None
167
+
160
168
  if hasattr(self, "_sleep_task") and self._sleep_task:
161
169
  self._sleep_task.cancel()
162
170
  await asyncio.gather(self._sleep_task, return_exceptions=True)
@@ -230,6 +238,34 @@ class AgentDaemon:
230
238
  except Exception:
231
239
  logger.exception("Sleep scheduler error")
232
240
 
241
+ async def _poll_loop(self) -> None:
242
+ """Background task that periodically checks channels for unread messages."""
243
+ interval = self.config.poll_interval
244
+ if interval <= 0:
245
+ return
246
+ while True:
247
+ try:
248
+ await asyncio.sleep(interval)
249
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
250
+ continue
251
+ for channel in self.agent.channels:
252
+ msgs = self._buffer.read(channel)
253
+ if not msgs:
254
+ continue
255
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
256
+ prompt = (
257
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
258
+ f"{lines}\n\n"
259
+ f"Respond naturally if any messages need your attention."
260
+ )
261
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
262
+ self._background_tasks.add(task)
263
+ task.add_done_callback(self._background_tasks.discard)
264
+ except asyncio.CancelledError:
265
+ raise
266
+ except Exception:
267
+ logger.exception("Poll loop error")
268
+
233
269
  async def _graceful_shutdown(self) -> None:
234
270
  """Trigger a graceful shutdown, signaling any waiting stop event."""
235
271
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import re
5
6
  from typing import Callable
6
7
 
7
8
  from culture.clients.claude.message_buffer import MessageBuffer
@@ -191,8 +192,12 @@ class IRCTransport:
191
192
  self.buffer.add(target, sender, text)
192
193
  else:
193
194
  self.buffer.add(f"DM:{sender}", sender, text)
194
- if self.on_mention and f"@{self.nick}" in text:
195
- self.on_mention(target, sender, text)
195
+ if self.on_mention:
196
+ short = self.nick.split("-", 1)[1] if "-" in self.nick else None
197
+ if re.search(rf"@{re.escape(self.nick)}\b", text) or (
198
+ short and re.search(rf"@{re.escape(short)}\b", text)
199
+ ):
200
+ self.on_mention(target, sender, text)
196
201
 
197
202
  async def _on_notice(self, msg: Message) -> None:
198
203
  if len(msg.params) < 2:
@@ -68,6 +68,7 @@ class DaemonConfig:
68
68
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
69
69
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
70
70
  buffer_size: int = 500
71
+ poll_interval: int = 60
71
72
  sleep_start: str = "23:00"
72
73
  sleep_end: str = "08:00"
73
74
  agents: list[AgentConfig] = field(default_factory=list)
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
100
101
  supervisor=supervisor,
101
102
  webhooks=webhooks,
102
103
  buffer_size=raw.get("buffer_size", 500),
104
+ poll_interval=raw.get("poll_interval", 60),
103
105
  sleep_start=raw.get("sleep_start", "23:00"),
104
106
  sleep_end=raw.get("sleep_end", "08:00"),
105
107
  agents=agents,
@@ -162,10 +162,18 @@ class CodexDaemon:
162
162
  # 7. Sleep scheduler background task
163
163
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
164
164
 
165
+ # 8. Channel poll background task
166
+ self._poll_task = asyncio.create_task(self._poll_loop())
167
+
165
168
  logger.info("CodexDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
166
169
 
167
170
  async def stop(self) -> None:
168
171
  """Cleanly shut down all components."""
172
+ if hasattr(self, "_poll_task") and self._poll_task:
173
+ self._poll_task.cancel()
174
+ await asyncio.gather(self._poll_task, return_exceptions=True)
175
+ self._poll_task = None
176
+
169
177
  if hasattr(self, "_sleep_task") and self._sleep_task:
170
178
  self._sleep_task.cancel()
171
179
  await asyncio.gather(self._sleep_task, return_exceptions=True)
@@ -239,6 +247,35 @@ class CodexDaemon:
239
247
  except Exception:
240
248
  logger.exception("Sleep scheduler error")
241
249
 
250
+ async def _poll_loop(self) -> None:
251
+ """Background task that periodically checks channels for unread messages."""
252
+ interval = self.config.poll_interval
253
+ if interval <= 0:
254
+ return
255
+ while True:
256
+ try:
257
+ await asyncio.sleep(interval)
258
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
259
+ continue
260
+ for channel in self.agent.channels:
261
+ msgs = self._buffer.read(channel)
262
+ if not msgs:
263
+ continue
264
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
265
+ prompt = (
266
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
267
+ f"{lines}\n\n"
268
+ f"Respond naturally if any messages need your attention."
269
+ )
270
+ self._mention_targets.append(channel)
271
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
272
+ self._background_tasks.add(task)
273
+ task.add_done_callback(self._background_tasks.discard)
274
+ except asyncio.CancelledError:
275
+ raise
276
+ except Exception:
277
+ logger.exception("Poll loop error")
278
+
242
279
  async def _graceful_shutdown(self) -> None:
243
280
  """Trigger a graceful shutdown, signaling any waiting stop event."""
244
281
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import re
5
6
  from typing import Callable
6
7
 
7
8
  from culture.clients.codex.message_buffer import MessageBuffer
@@ -191,8 +192,12 @@ class IRCTransport:
191
192
  self.buffer.add(target, sender, text)
192
193
  else:
193
194
  self.buffer.add(f"DM:{sender}", sender, text)
194
- if self.on_mention and f"@{self.nick}" in text:
195
- self.on_mention(target, sender, text)
195
+ if self.on_mention:
196
+ short = self.nick.split("-", 1)[1] if "-" in self.nick else None
197
+ if re.search(rf"@{re.escape(self.nick)}\b", text) or (
198
+ short and re.search(rf"@{re.escape(short)}\b", text)
199
+ ):
200
+ self.on_mention(target, sender, text)
196
201
 
197
202
  async def _on_notice(self, msg: Message) -> None:
198
203
  if len(msg.params) < 2:
@@ -68,6 +68,7 @@ class DaemonConfig:
68
68
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
69
69
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
70
70
  buffer_size: int = 500
71
+ poll_interval: int = 60
71
72
  sleep_start: str = "23:00"
72
73
  sleep_end: str = "08:00"
73
74
  agents: list[AgentConfig] = field(default_factory=list)
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
100
101
  supervisor=supervisor,
101
102
  webhooks=webhooks,
102
103
  buffer_size=raw.get("buffer_size", 500),
104
+ poll_interval=raw.get("poll_interval", 60),
103
105
  sleep_start=raw.get("sleep_start", "23:00"),
104
106
  sleep_end=raw.get("sleep_end", "08:00"),
105
107
  agents=agents,
@@ -162,10 +162,18 @@ class CopilotDaemon:
162
162
  # 7. Sleep scheduler background task
163
163
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
164
164
 
165
+ # 8. Channel poll background task
166
+ self._poll_task = asyncio.create_task(self._poll_loop())
167
+
165
168
  logger.info("CopilotDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
166
169
 
167
170
  async def stop(self) -> None:
168
171
  """Cleanly shut down all components."""
172
+ if hasattr(self, "_poll_task") and self._poll_task:
173
+ self._poll_task.cancel()
174
+ await asyncio.gather(self._poll_task, return_exceptions=True)
175
+ self._poll_task = None
176
+
169
177
  if hasattr(self, "_sleep_task") and self._sleep_task:
170
178
  self._sleep_task.cancel()
171
179
  await asyncio.gather(self._sleep_task, return_exceptions=True)
@@ -239,6 +247,35 @@ class CopilotDaemon:
239
247
  except Exception:
240
248
  logger.exception("Sleep scheduler error")
241
249
 
250
+ async def _poll_loop(self) -> None:
251
+ """Background task that periodically checks channels for unread messages."""
252
+ interval = self.config.poll_interval
253
+ if interval <= 0:
254
+ return
255
+ while True:
256
+ try:
257
+ await asyncio.sleep(interval)
258
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
259
+ continue
260
+ for channel in self.agent.channels:
261
+ msgs = self._buffer.read(channel)
262
+ if not msgs:
263
+ continue
264
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
265
+ prompt = (
266
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
267
+ f"{lines}\n\n"
268
+ f"Respond naturally if any messages need your attention."
269
+ )
270
+ self._mention_targets.append(channel)
271
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
272
+ self._background_tasks.add(task)
273
+ task.add_done_callback(self._background_tasks.discard)
274
+ except asyncio.CancelledError:
275
+ raise
276
+ except Exception:
277
+ logger.exception("Poll loop error")
278
+
242
279
  async def _graceful_shutdown(self) -> None:
243
280
  """Trigger a graceful shutdown, signaling any waiting stop event."""
244
281
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import re
5
6
  from typing import Callable
6
7
 
7
8
  from culture.clients.copilot.message_buffer import MessageBuffer
@@ -191,8 +192,12 @@ class IRCTransport:
191
192
  self.buffer.add(target, sender, text)
192
193
  else:
193
194
  self.buffer.add(f"DM:{sender}", sender, text)
194
- if self.on_mention and f"@{self.nick}" in text:
195
- self.on_mention(target, sender, text)
195
+ if self.on_mention:
196
+ short = self.nick.split("-", 1)[1] if "-" in self.nick else None
197
+ if re.search(rf"@{re.escape(self.nick)}\b", text) or (
198
+ short and re.search(rf"@{re.escape(short)}\b", text)
199
+ ):
200
+ self.on_mention(target, sender, text)
196
201
 
197
202
  async def _on_notice(self, msg: Message) -> None:
198
203
  if len(msg.params) < 2:
@@ -0,0 +1,51 @@
1
+ # Channel Polling
2
+
3
+ Agents periodically check their subscribed channels for unread messages and respond to anything that needs attention. This runs alongside the existing @mention system.
4
+
5
+ ## How It Works
6
+
7
+ - **@mentions**: Trigger immediate agent activation (no change)
8
+ - **Polling**: Every `poll_interval` seconds, the daemon checks each channel for unread messages. If any exist, they're sent to the agent as context.
9
+
10
+ The poll prompt looks like:
11
+
12
+ ```text
13
+ [IRC Channel Poll: #general] Recent unread messages:
14
+ <spark-ori> hello everyone
15
+ <spark-ori> anyone working on the API?
16
+
17
+ Respond naturally if any messages need your attention.
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ Add `poll_interval` to `agents.yaml`:
23
+
24
+ ```yaml
25
+ poll_interval: 60 # seconds (default: 1 minute)
26
+ buffer_size: 500
27
+ sleep_start: '23:00'
28
+ sleep_end: '08:00'
29
+ ```
30
+
31
+ Set `poll_interval: 0` to disable polling (agents only respond to @mentions).
32
+
33
+ ## Nick Alias Matching
34
+
35
+ Agents respond to both their full nick and short suffix:
36
+
37
+ | Agent Nick | Responds To |
38
+ |------------|-------------|
39
+ | `spark-culture` | `@spark-culture`, `@culture` |
40
+ | `spark-daria` | `@spark-daria`, `@daria` |
41
+ | `thor-claude` | `@thor-claude`, `@claude` |
42
+
43
+ The short name is the part after the first hyphen in the nick.
44
+
45
+ ## Interaction with Sleep Schedule
46
+
47
+ Polling respects the sleep schedule. When an agent is paused (during sleep hours), the poll loop skips processing. Messages accumulate in the buffer and are picked up on the next poll after the agent wakes.
48
+
49
+ ## Interaction with @mentions
50
+
51
+ If an @mention arrives between polls, it triggers immediately. The next poll may include the same message in its context, but the prompt instructs the agent to only respond if something needs attention, avoiding duplicate responses.
@@ -68,6 +68,7 @@ class DaemonConfig:
68
68
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
69
69
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
70
70
  buffer_size: int = 500
71
+ poll_interval: int = 60
71
72
  sleep_start: str = "23:00"
72
73
  sleep_end: str = "08:00"
73
74
  agents: list[AgentConfig] = field(default_factory=list)
@@ -98,6 +99,7 @@ def load_config(path: str | Path) -> DaemonConfig:
98
99
  supervisor=supervisor,
99
100
  webhooks=webhooks,
100
101
  buffer_size=raw.get("buffer_size", 500),
102
+ poll_interval=raw.get("poll_interval", 60),
101
103
  sleep_start=raw.get("sleep_start", "23:00"),
102
104
  sleep_end=raw.get("sleep_end", "08:00"),
103
105
  agents=agents,
@@ -60,8 +60,15 @@ class AgentDaemon:
60
60
  self._buffer: MessageBuffer | None = None
61
61
  self._socket_server: SocketServer | None = None
62
62
  self._webhook: WebhookClient | None = None
63
+ self._agent_runner: Any = None
63
64
  self._stop_event: asyncio.Event | None = None
64
65
 
66
+ # FIFO queue of relay targets — each @mention or poll enqueues a
67
+ # target, each agent response dequeues one for correct routing.
68
+ from collections import deque
69
+
70
+ self._mention_targets: deque[str] = deque()
71
+
65
72
  # Pause/sleep state
66
73
  self._paused: bool = False
67
74
  self._last_activation: float | None = None
@@ -137,8 +144,16 @@ class AgentDaemon:
137
144
  # 6. Sleep scheduler background task
138
145
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
139
146
 
147
+ # 7. Channel poll background task
148
+ self._poll_task = asyncio.create_task(self._poll_loop())
149
+
140
150
  async def stop(self) -> None:
141
151
  """Stop all daemon components."""
152
+ if hasattr(self, "_poll_task") and self._poll_task:
153
+ self._poll_task.cancel()
154
+ await asyncio.gather(self._poll_task, return_exceptions=True)
155
+ self._poll_task = None
156
+
142
157
  if hasattr(self, "_sleep_task") and self._sleep_task:
143
158
  self._sleep_task.cancel()
144
159
  await asyncio.gather(self._sleep_task, return_exceptions=True)
@@ -214,6 +229,35 @@ class AgentDaemon:
214
229
  except Exception:
215
230
  logger.exception("Sleep scheduler error")
216
231
 
232
+ async def _poll_loop(self) -> None:
233
+ """Background task that periodically checks channels for unread messages."""
234
+ interval = self.config.poll_interval
235
+ if interval <= 0:
236
+ return
237
+ while True:
238
+ try:
239
+ await asyncio.sleep(interval)
240
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
241
+ continue
242
+ for channel in self.agent.channels:
243
+ msgs = self._buffer.read(channel)
244
+ if not msgs:
245
+ continue
246
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
247
+ prompt = (
248
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
249
+ f"{lines}\n\n"
250
+ f"Respond naturally if any messages need your attention."
251
+ )
252
+ self._mention_targets.append(channel)
253
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
254
+ self._background_tasks.add(task)
255
+ task.add_done_callback(self._background_tasks.discard)
256
+ except asyncio.CancelledError:
257
+ raise
258
+ except Exception:
259
+ logger.exception("Poll loop error")
260
+
217
261
  def _on_mention(self, target: str, sender: str, text: str) -> None:
218
262
  """Called when the agent is @mentioned. Sends prompt to runner.
219
263
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  import asyncio
5
5
  import logging
6
+ import re
6
7
  from typing import Callable
7
8
 
8
9
  from culture.clients.BACKEND.message_buffer import MessageBuffer
@@ -192,8 +193,12 @@ class IRCTransport:
192
193
  self.buffer.add(target, sender, text)
193
194
  else:
194
195
  self.buffer.add(f"DM:{sender}", sender, text)
195
- if self.on_mention and f"@{self.nick}" in text:
196
- self.on_mention(target, sender, text)
196
+ if self.on_mention:
197
+ short = self.nick.split("-", 1)[1] if "-" in self.nick else None
198
+ if re.search(rf"@{re.escape(self.nick)}\b", text) or (
199
+ short and re.search(rf"@{re.escape(short)}\b", text)
200
+ ):
201
+ self.on_mention(target, sender, text)
197
202
 
198
203
  async def _on_notice(self, msg: Message) -> None:
199
204
  if len(msg.params) < 2:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentirc-cli"
3
- version = "4.0.0"
3
+ version = "4.1.1"
4
4
  description = "Legacy alias for culture — install culture instead"
5
5
  readme = "README.md"
6
6
  license = "MIT"