agentirc-cli 0.18.0__tar.gz → 0.20.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 (248) hide show
  1. agentirc_cli-0.20.0/.flake8 +19 -0
  2. agentirc_cli-0.20.0/.github/workflows/security-checks.yml +99 -0
  3. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.gitignore +6 -0
  4. agentirc_cli-0.20.0/.pre-commit-config.yaml +42 -0
  5. agentirc_cli-0.20.0/.pylintrc +77 -0
  6. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CHANGELOG.md +36 -1
  7. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/PKG-INFO +1 -1
  8. agentirc_cli-0.20.0/SECURITY.md +39 -0
  9. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/daemon.py +89 -2
  10. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/irc_transport.py +12 -0
  11. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/message_buffer.py +20 -1
  12. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/daemon.py +89 -2
  13. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/irc_transport.py +12 -0
  14. {agentirc_cli-0.18.0/agentirc/clients/codex → agentirc_cli-0.20.0/agentirc/clients/claude}/message_buffer.py +20 -1
  15. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/daemon.py +89 -2
  16. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/irc_transport.py +12 -0
  17. {agentirc_cli-0.18.0/agentirc/clients/claude → agentirc_cli-0.20.0/agentirc/clients/codex}/message_buffer.py +20 -1
  18. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/daemon.py +89 -2
  19. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/irc_transport.py +12 -0
  20. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/message_buffer.py +20 -1
  21. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/commands.py +8 -0
  22. agentirc_cli-0.20.0/agentirc/protocol/extensions/threads.md +296 -0
  23. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/ircd.py +2 -0
  24. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/server_link.py +90 -0
  25. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skill.py +3 -0
  26. agentirc_cli-0.20.0/agentirc/server/skills/threads.py +598 -0
  27. agentirc_cli-0.20.0/agentirc/server/thread_store.py +50 -0
  28. agentirc_cli-0.20.0/docs/SECURITY.md +120 -0
  29. agentirc_cli-0.20.0/docs/superpowers/plans/2026-04-02-conversation-threads.md +1885 -0
  30. agentirc_cli-0.20.0/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +326 -0
  31. agentirc_cli-0.20.0/docs/threads.md +200 -0
  32. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/daemon.py +96 -2
  33. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/irc_transport.py +12 -0
  34. agentirc_cli-0.20.0/packages/agent-harness/message_buffer.py +65 -0
  35. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/pyproject.toml +40 -1
  36. agentirc_cli-0.20.0/sonar-project.properties +27 -0
  37. agentirc_cli-0.20.0/tests/test_thread_buffer.py +56 -0
  38. agentirc_cli-0.20.0/tests/test_threads.py +383 -0
  39. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/uv.lock +901 -1
  40. agentirc_cli-0.18.0/packages/agent-harness/message_buffer.py +0 -46
  41. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.claude/skills/pr-review/SKILL.md +0 -0
  42. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/pages.yml +0 -0
  43. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/publish.yml +0 -0
  44. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/tests.yml +0 -0
  45. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.markdownlint-cli2.yaml +0 -0
  46. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.pr_agent.toml +0 -0
  47. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CLAUDE.md +0 -0
  48. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CNAME +0 -0
  49. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/Gemfile +0 -0
  50. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/Gemfile.lock +0 -0
  51. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/LICENSE +0 -0
  52. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/README.md +0 -0
  53. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_config.yml +0 -0
  54. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_sass/color_schemes/anthropic.scss +0 -0
  55. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_sass/custom/custom.scss +0 -0
  56. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/__init__.py +0 -0
  57. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/__main__.py +0 -0
  58. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/cli.py +0 -0
  59. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/__init__.py +0 -0
  60. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/__init__.py +0 -0
  61. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/agent_runner.py +0 -0
  62. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/config.py +0 -0
  63. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/ipc.py +0 -0
  64. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/SKILL.md +0 -0
  65. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/__init__.py +0 -0
  66. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/irc_client.py +0 -0
  67. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/socket_server.py +0 -0
  68. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/supervisor.py +0 -0
  69. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/webhook.py +0 -0
  70. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/__init__.py +0 -0
  71. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/__main__.py +0 -0
  72. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/agent_runner.py +0 -0
  73. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/config.py +0 -0
  74. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/ipc.py +0 -0
  75. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
  76. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/__init__.py +0 -0
  77. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
  78. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/socket_server.py +0 -0
  79. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/supervisor.py +0 -0
  80. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/webhook.py +0 -0
  81. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/__init__.py +0 -0
  82. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/agent_runner.py +0 -0
  83. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/config.py +0 -0
  84. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/ipc.py +0 -0
  85. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
  86. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/__init__.py +0 -0
  87. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
  88. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/socket_server.py +0 -0
  89. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/supervisor.py +0 -0
  90. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/webhook.py +0 -0
  91. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/__init__.py +0 -0
  92. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/agent_runner.py +0 -0
  93. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/config.py +0 -0
  94. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/ipc.py +0 -0
  95. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
  96. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
  97. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
  98. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/socket_server.py +0 -0
  99. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/supervisor.py +0 -0
  100. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/webhook.py +0 -0
  101. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/credentials.py +0 -0
  102. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/learn_prompt.py +0 -0
  103. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/mesh_config.py +0 -0
  104. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/observer.py +0 -0
  105. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/__init__.py +0 -0
  106. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/collector.py +0 -0
  107. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/model.py +0 -0
  108. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/renderer_text.py +0 -0
  109. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/renderer_web.py +0 -0
  110. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/web/style.css +0 -0
  111. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/persistence.py +0 -0
  112. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/pidfile.py +0 -0
  113. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/__init__.py +0 -0
  114. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/federation.md +0 -0
  115. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/history.md +0 -0
  116. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/rooms.md +0 -0
  117. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/tags.md +0 -0
  118. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/message.py +0 -0
  119. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/protocol-index.md +0 -0
  120. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/replies.py +0 -0
  121. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/__init__.py +0 -0
  122. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/__main__.py +0 -0
  123. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/channel.py +0 -0
  124. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/client.py +0 -0
  125. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/config.py +0 -0
  126. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/remote_client.py +0 -0
  127. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/room_store.py +0 -0
  128. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/rooms_util.py +0 -0
  129. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/__init__.py +0 -0
  130. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/history.py +0 -0
  131. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/rooms.py +0 -0
  132. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/skills/agentirc/SKILL.md +0 -0
  133. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agent-client.md +0 -0
  134. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agent-harness-spec.md +0 -0
  135. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agentic-self-learn.md +0 -0
  136. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/ci.md +0 -0
  137. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/cli.md +0 -0
  138. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/acp/overview.md +0 -0
  139. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/configuration.md +0 -0
  140. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/context-management.md +0 -0
  141. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/irc-tools.md +0 -0
  142. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/overview.md +0 -0
  143. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/setup.md +0 -0
  144. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/supervisor.md +0 -0
  145. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/webhooks.md +0 -0
  146. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/configuration.md +0 -0
  147. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/context-management.md +0 -0
  148. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/irc-tools.md +0 -0
  149. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/overview.md +0 -0
  150. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/setup.md +0 -0
  151. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/supervisor.md +0 -0
  152. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/webhooks.md +0 -0
  153. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/configuration.md +0 -0
  154. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/context-management.md +0 -0
  155. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/irc-tools.md +0 -0
  156. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/overview.md +0 -0
  157. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/setup.md +0 -0
  158. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/supervisor.md +0 -0
  159. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/webhooks.md +0 -0
  160. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/codex-backend.md +0 -0
  161. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/copilot-backend.md +0 -0
  162. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/design.md +0 -0
  163. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/docs-site.md +0 -0
  164. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/getting-started.md +0 -0
  165. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/grow-your-agent.md +0 -0
  166. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/harness-conformance.md +0 -0
  167. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer1-core-irc.md +0 -0
  168. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer2-attention.md +0 -0
  169. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer3-skills.md +0 -0
  170. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer4-federation.md +0 -0
  171. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer5-agent-harness.md +0 -0
  172. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/ops-tooling.md +0 -0
  173. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/overview.md +0 -0
  174. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/publishing.md +0 -0
  175. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  176. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/rooms.md +0 -0
  177. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/server-architecture.md +0 -0
  178. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  179. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  180. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  181. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  182. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  183. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  184. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  185. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  186. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/01-pair-programming.md +0 -0
  187. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
  188. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
  189. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
  190. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/05-the-observer.md +0 -0
  191. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/06-cross-server-ops.md +0 -0
  192. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
  193. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/08-apps-as-agents.md +0 -0
  194. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/09-research-swarm.md +0 -0
  195. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/10-grow-your-agent.md +0 -0
  196. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases-index.md +0 -0
  197. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/index.md +0 -0
  198. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/README.md +0 -0
  199. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/config.py +0 -0
  200. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/ipc.py +0 -0
  201. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/skill/SKILL.md +0 -0
  202. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/skill/irc_client.py +0 -0
  203. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/socket_server.py +0 -0
  204. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/webhook.py +0 -0
  205. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  206. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/skills/agentirc/SKILL.md +0 -0
  207. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  208. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
  209. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/__init__.py +0 -0
  210. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/conftest.py +0 -0
  211. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_acp_daemon.py +0 -0
  212. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_agent_runner.py +0 -0
  213. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_channel.py +0 -0
  214. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_codex_daemon.py +0 -0
  215. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_connection.py +0 -0
  216. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_copilot_daemon.py +0 -0
  217. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon.py +0 -0
  218. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon_config.py +0 -0
  219. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon_ipc.py +0 -0
  220. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_discovery.py +0 -0
  221. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_federation.py +0 -0
  222. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_history.py +0 -0
  223. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_integration_layer5.py +0 -0
  224. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_ipc.py +0 -0
  225. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_irc_transport.py +0 -0
  226. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_link_reconnect.py +0 -0
  227. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_mentions.py +0 -0
  228. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_mesh_config.py +0 -0
  229. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_message.py +0 -0
  230. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_message_buffer.py +0 -0
  231. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_messaging.py +0 -0
  232. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_modes.py +0 -0
  233. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_cli.py +0 -0
  234. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_collector.py +0 -0
  235. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_model.py +0 -0
  236. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_renderer.py +0 -0
  237. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_web.py +0 -0
  238. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_persistence.py +0 -0
  239. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_room_persistence.py +0 -0
  240. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms.py +0 -0
  241. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms_federation.py +0 -0
  242. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms_integration.py +0 -0
  243. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_setup_update_cli.py +0 -0
  244. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_skill_client.py +0 -0
  245. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_skills.py +0 -0
  246. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_socket_server.py +0 -0
  247. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_supervisor.py +0 -0
  248. {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_webhook.py +0 -0
@@ -0,0 +1,19 @@
1
+ [flake8]
2
+ max-line-length = 100
3
+ exclude =
4
+ .git,
5
+ __pycache__,
6
+ .venv,
7
+ dist,
8
+ build,
9
+ .eggs,
10
+ packages
11
+ extend-ignore =
12
+ E203,
13
+ W503,
14
+ S101
15
+ per-file-ignores =
16
+ agentirc/credentials.py:S603,S607
17
+ agentirc/persistence.py:S603,S607
18
+ agentirc/cli.py:S603,S607
19
+ tests/*:S101
@@ -0,0 +1,99 @@
1
+ name: Security Checks
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: '0 0 * * 0' # Weekly on Sunday at midnight
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ security-scans:
17
+ name: Security Scans
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+
24
+ - uses: astral-sh/setup-uv@v4
25
+
26
+ - run: uv python install 3.12
27
+
28
+ - run: uv sync
29
+
30
+ - name: Run Bandit
31
+ run: uv run bandit -r agentirc/ -f json -o bandit-results.json -c pyproject.toml
32
+ continue-on-error: true
33
+
34
+ - name: Run Pylint
35
+ run: uv run pylint agentirc/ --rcfile=.pylintrc --output-format=json:pylint-results.json,text
36
+ continue-on-error: true
37
+
38
+ - name: Run Safety dependency check
39
+ run: uv run safety check --full-report --output json > safety-results.json
40
+ continue-on-error: true
41
+
42
+ - name: Upload Security Results
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: security-results
46
+ path: |
47
+ bandit-results.json
48
+ pylint-results.json
49
+ safety-results.json
50
+
51
+ - name: Run test coverage
52
+ run: |
53
+ uv run pytest --cov=agentirc --cov-report=xml:coverage.xml --cov-report=term -v
54
+ continue-on-error: true
55
+
56
+ - name: SonarCloud Scan
57
+ uses: SonarSource/sonarqube-scan-action@v7
58
+ env:
59
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
61
+ with:
62
+ args: >
63
+ -Dsonar.projectKey=OriNachum_AgentIRC
64
+ -Dsonar.organization=${{ github.repository_owner }}
65
+ -Dsonar.python.coverage.reportPaths=coverage.xml
66
+ -Dsonar.python.bandit.reportPaths=bandit-results.json
67
+ -Dsonar.python.pylint.reportPaths=pylint-results.json
68
+
69
+ dependency-review:
70
+ name: Dependency Review
71
+ if: github.event_name == 'pull_request'
72
+ runs-on: ubuntu-latest
73
+ permissions:
74
+ contents: read
75
+ pull-requests: write
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ - name: Dependency Review
80
+ uses: actions/dependency-review-action@v4
81
+ with:
82
+ fail-on-severity: high
83
+
84
+ codeql-analysis:
85
+ name: CodeQL Analysis
86
+ runs-on: ubuntu-latest
87
+ permissions:
88
+ contents: read
89
+ security-events: write
90
+ steps:
91
+ - uses: actions/checkout@v4
92
+
93
+ - name: Initialize CodeQL
94
+ uses: github/codeql-action/init@v3
95
+ with:
96
+ languages: python
97
+
98
+ - name: Perform CodeQL Analysis
99
+ uses: github/codeql-action/analyze@v3
@@ -206,6 +206,12 @@ marimo/_static/
206
206
  marimo/_lsp/
207
207
  __marimo__/
208
208
 
209
+ # Security scan reports
210
+ bandit-results.json
211
+ pylint-results.json
212
+ safety-results.json
213
+ .sonar/
214
+
209
215
  # Superpowers & worktrees
210
216
  .superpowers/
211
217
  .worktrees/
@@ -0,0 +1,42 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.4.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - id: check-ast
10
+ - id: detect-private-key
11
+
12
+ - repo: local
13
+ hooks:
14
+ - id: flake8
15
+ name: flake8
16
+ entry: uv run flake8 --config=.flake8
17
+ language: system
18
+ types: [python]
19
+
20
+ - id: isort
21
+ name: isort
22
+ entry: uv run isort
23
+ language: system
24
+ types: [python]
25
+
26
+ - id: black
27
+ name: black
28
+ entry: uv run black
29
+ language: system
30
+ types: [python]
31
+
32
+ - id: bandit
33
+ name: bandit
34
+ entry: uv run bandit -c pyproject.toml
35
+ language: system
36
+ types: [python]
37
+
38
+ - id: pylint
39
+ name: pylint
40
+ entry: uv run pylint --rcfile=.pylintrc
41
+ language: system
42
+ types: [python]
@@ -0,0 +1,77 @@
1
+ [MASTER]
2
+ ignore=CVS
3
+ persistent=yes
4
+ load-plugins=
5
+
6
+ [MESSAGES CONTROL]
7
+ disable=
8
+ C0114, # missing-module-docstring
9
+ C0115, # missing-class-docstring
10
+ C0116, # missing-function-docstring
11
+ C0301, # line-too-long (handled by black)
12
+ C0303, # trailing-whitespace
13
+ R0801, # duplicate-code (assimilai pattern: backends share identical files)
14
+ R0903, # too-few-public-methods
15
+ R0913, # too-many-arguments
16
+ W0511, # fixme
17
+ W0718, # broad-exception-caught (async server/daemon must catch broadly)
18
+ W0719, # broad-exception-raised
19
+ W1202, # logging-format-interpolation
20
+ W1203, # logging-fstring-interpolation
21
+
22
+ [REPORTS]
23
+ output-format=text
24
+ reports=yes
25
+ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
26
+
27
+ [VARIABLES]
28
+ init-import=no
29
+ dummy-variables-rgx=_$|dummy
30
+
31
+ [FORMAT]
32
+ max-line-length=100
33
+ ignore-long-lines=^\s*(# )?<?https?://\S+>?$
34
+ single-line-if-stmt=no
35
+ indent-string=' '
36
+ max-module-lines=1000
37
+
38
+ [SIMILARITIES]
39
+ min-similarity-lines=8
40
+ ignore-comments=yes
41
+ ignore-docstrings=yes
42
+ ignore-imports=yes
43
+
44
+ [BASIC]
45
+ good-names=i,j,k,ex,Run,_,e,f,fd,ws,ts,ok,ch,ip,op
46
+ bad-names=foo,bar,baz,toto,tutu,tata
47
+ function-rgx=[a-z_][a-z0-9_]{2,50}$
48
+ variable-rgx=[a-z_][a-z0-9_]{1,30}$
49
+ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
50
+ attr-rgx=[a-z_][a-z0-9_]{1,30}$
51
+ argument-rgx=[a-z_][a-z0-9_]{1,30}$
52
+ class-rgx=[A-Z_][a-zA-Z0-9]+$
53
+ inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
54
+ no-docstring-rgx=^_
55
+
56
+ [DESIGN]
57
+ max-args=10
58
+ ignored-argument-names=_.*
59
+ max-locals=15
60
+ max-returns=6
61
+ max-branches=12
62
+ max-statements=50
63
+ max-parents=7
64
+ max-attributes=15
65
+ min-public-methods=2
66
+ max-public-methods=20
67
+
68
+ [CLASSES]
69
+ defining-attr-methods=__init__,__new__,setUp
70
+ valid-classmethod-first-arg=cls
71
+ valid-metaclass-classmethod-first-arg=mcs
72
+
73
+ [IMPORTS]
74
+ deprecated-modules=regsub,TERMIOS,Bastion,rexec
75
+
76
+ [EXCEPTIONS]
77
+ overgeneral-exceptions=builtins.BaseException
@@ -4,11 +4,46 @@ 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.18.0] - 2026-04-02
7
+ ## [0.20.0] - 2026-04-03
8
8
 
9
9
 
10
10
  ### Added
11
11
 
12
+ - Bandit SAST security scanning
13
+ - Pylint static code analysis
14
+ - Safety dependency vulnerability scanning
15
+ - CodeQL semantic analysis (GitHub-native)
16
+ - SonarCloud code quality and security integration
17
+ - Pre-commit hooks (flake8+bandit+bugbear, isort, black, pylint, detect-private-key)
18
+ - Security CI workflow (security-checks.yml)
19
+ - Dependency Review on PRs (fails on high severity)
20
+ - SECURITY.md vulnerability disclosure policy
21
+ - docs/SECURITY.md contributor security guidelines
22
+ - Code coverage enforcement in CI
23
+
24
+ ## [0.19.0] - 2026-04-03
25
+
26
+
27
+ ### Added
28
+
29
+ - Conversation threads — inline sub-conversations with [thread:name] prefix
30
+ - Breakout channel promotion from threads
31
+ - Thread-scoped agent context on @mention
32
+ - S2S federation for thread messages
33
+ - JSON persistence for threads across restarts
34
+ - Thread support in all 4 agent backends (claude, codex, copilot, acp)
35
+
36
+ ## [0.18.0] - 2026-04-03
37
+
38
+
39
+ ### Added
40
+
41
+ - Conversation threads — inline sub-conversations with [thread:name] prefix
42
+ - Breakout channel promotion from threads
43
+ - Thread-scoped agent context on @mention
44
+ - S2S federation for thread messages
45
+ - JSON persistence for threads across restarts
46
+ - Thread support in all 4 agent backends (claude, codex, copilot, acp)
12
47
  - S2S link auto-reconnect with exponential backoff (5s to 120s)
13
48
  - Declarative mesh.yaml configuration for multi-machine setup
14
49
  - Cross-platform auto-start persistence (systemd, launchd, Windows schtasks)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: 🌱 The space your agents deserve — an autonomous agent mesh where AI agents live, collaborate, and grow
5
5
  Project-URL: Homepage, https://github.com/OriNachum/agentirc
6
6
  Author: Ori Nachum
@@ -0,0 +1,39 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability in AgentIRC, please report it responsibly.
6
+
7
+ **Do not** open a public GitHub issue for security vulnerabilities.
8
+
9
+ ### How to Report
10
+
11
+ Please report security issues privately using one of the following methods:
12
+
13
+ - **GitHub Security Advisories**: [Report a vulnerability privately](../../security/advisories/new)
14
+ - **Email**: Contact the maintainer directly
15
+
16
+ Include:
17
+
18
+ - A description of the vulnerability
19
+ - Steps to reproduce the issue
20
+ - The potential impact
21
+
22
+ ### Response Timeline
23
+
24
+ - **Acknowledgment**: Within 48 hours
25
+ - **Fix timeline**: Within 7 days of acknowledgment
26
+ - **Disclosure**: Coordinated with the reporter after a fix is available
27
+
28
+ ## Security Measures
29
+
30
+ This project uses automated security scanning:
31
+
32
+ - **Bandit** — Python security vulnerability detection
33
+ - **Pylint** — Static code analysis
34
+ - **CodeQL** — GitHub-native semantic analysis
35
+ - **SonarCloud** — Comprehensive code quality and security
36
+ - **Safety** — Dependency vulnerability scanning
37
+ - **Dependency Review** — PR-level dependency checks
38
+
39
+ See [docs/SECURITY.md](docs/SECURITY.md) for full details on the security toolchain and contributor guidelines.
@@ -273,7 +273,7 @@ class ACPDaemon:
273
273
  def _on_mention(self, target: str, sender: str, text: str) -> None:
274
274
  """Called by IRCTransport when the agent is @mentioned or DM'd.
275
275
 
276
- Formats a prompt and enqueues it so the ACP session picks it up.
276
+ When the mention is inside a thread, provides thread-scoped context.
277
277
  """
278
278
  if self._paused:
279
279
  return
@@ -282,7 +282,21 @@ class ACPDaemon:
282
282
  # Enqueue relay target (FIFO matches prompt queue order)
283
283
  self._mention_targets.append(target if target.startswith("#") else sender)
284
284
  if target.startswith("#"):
285
- prompt = f"[IRC @mention in {target}] <{sender}> {text}"
285
+ import re
286
+ thread_match = re.match(r"^\[thread:([a-zA-Z0-9\-]+)\] ", text)
287
+ if thread_match and self._buffer:
288
+ thread_name = thread_match.group(1)
289
+ thread_msgs = self._buffer.read_thread(target, thread_name)
290
+ history = "\n".join(
291
+ f" <{m.nick}> {m.text}" for m in thread_msgs
292
+ )
293
+ prompt = (
294
+ f"[IRC @mention in {target}, thread:{thread_name}]\n"
295
+ f"Thread history:\n{history}\n"
296
+ f" <{sender}> {text}"
297
+ )
298
+ else:
299
+ prompt = f"[IRC @mention in {target}] <{sender}> {text}"
286
300
  else:
287
301
  prompt = f"[IRC DM] <{sender}> {text}"
288
302
  asyncio.create_task(self._agent_runner.send_prompt(prompt))
@@ -510,6 +524,21 @@ class ACPDaemon:
510
524
  elif msg_type == "resume":
511
525
  return await self._ipc_resume(req_id)
512
526
 
527
+ elif msg_type == "irc_thread_create":
528
+ return await self._ipc_irc_thread_create(req_id, msg)
529
+
530
+ elif msg_type == "irc_thread_reply":
531
+ return await self._ipc_irc_thread_reply(req_id, msg)
532
+
533
+ elif msg_type == "irc_threads":
534
+ return await self._ipc_irc_threads(req_id, msg)
535
+
536
+ elif msg_type == "irc_thread_close":
537
+ return await self._ipc_irc_thread_close(req_id, msg)
538
+
539
+ elif msg_type == "irc_thread_read":
540
+ return await self._ipc_irc_thread_read(req_id, msg)
541
+
513
542
  elif msg_type == "shutdown":
514
543
  asyncio.create_task(self._graceful_shutdown())
515
544
  return make_response(req_id, ok=True)
@@ -642,6 +671,64 @@ class ACPDaemon:
642
671
  await self._transport.part_channel(channel)
643
672
  return make_response(req_id, ok=True)
644
673
 
674
+ async def _ipc_irc_thread_create(self, req_id: str, msg: dict) -> dict:
675
+ channel = msg.get("channel", "")
676
+ thread_name = msg.get("thread", "")
677
+ text = msg.get("message", "")
678
+ if not channel or not thread_name or not text:
679
+ return make_response(req_id, ok=False,
680
+ error="Missing 'channel', 'thread', or 'message'")
681
+ assert self._transport is not None
682
+ await self._transport.send_thread_create(channel, thread_name, text)
683
+ return make_response(req_id, ok=True)
684
+
685
+ async def _ipc_irc_thread_reply(self, req_id: str, msg: dict) -> dict:
686
+ channel = msg.get("channel", "")
687
+ thread_name = msg.get("thread", "")
688
+ text = msg.get("message", "")
689
+ if not channel or not thread_name or not text:
690
+ return make_response(req_id, ok=False,
691
+ error="Missing 'channel', 'thread', or 'message'")
692
+ assert self._transport is not None
693
+ await self._transport.send_thread_reply(channel, thread_name, text)
694
+ return make_response(req_id, ok=True)
695
+
696
+ async def _ipc_irc_threads(self, req_id: str, msg: dict) -> dict:
697
+ channel = msg.get("channel", "")
698
+ if not channel:
699
+ return make_response(req_id, ok=False, error="Missing 'channel'")
700
+ assert self._transport is not None
701
+ await self._transport.send_threads_list(channel)
702
+ return make_response(req_id, ok=True)
703
+
704
+ async def _ipc_irc_thread_close(self, req_id: str, msg: dict) -> dict:
705
+ channel = msg.get("channel", "")
706
+ thread_name = msg.get("thread", "")
707
+ summary = msg.get("summary", "")
708
+ if not channel or not thread_name:
709
+ return make_response(req_id, ok=False,
710
+ error="Missing 'channel' or 'thread'")
711
+ assert self._transport is not None
712
+ await self._transport.send_thread_close(channel, thread_name, summary)
713
+ return make_response(req_id, ok=True)
714
+
715
+ async def _ipc_irc_thread_read(self, req_id: str, msg: dict) -> dict:
716
+ channel = msg.get("channel", "")
717
+ thread_name = msg.get("thread", "")
718
+ limit = int(msg.get("limit", 50))
719
+ if not channel or not thread_name:
720
+ return make_response(req_id, ok=False,
721
+ error="Missing 'channel' or 'thread'")
722
+ assert self._buffer is not None
723
+ messages = self._buffer.read_thread(channel, thread_name, limit=limit)
724
+ return make_response(req_id, ok=True, data={
725
+ "messages": [
726
+ {"nick": m.nick, "text": m.text, "timestamp": m.timestamp,
727
+ "thread": m.thread}
728
+ for m in messages
729
+ ]
730
+ })
731
+
645
732
  async def _ipc_irc_channels(self, req_id: str) -> dict:
646
733
  assert self._transport is not None
647
734
  return make_response(req_id, ok=True, data={"channels": self._transport.channels})
@@ -73,6 +73,18 @@ class IRCTransport:
73
73
  async def send_privmsg(self, target: str, text: str) -> None:
74
74
  await self._send_raw(f"PRIVMSG {target} :{text}")
75
75
 
76
+ async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
77
+ await self._send_raw(f"THREAD CREATE {channel} {thread_name} :{text}")
78
+
79
+ async def send_thread_reply(self, channel: str, thread_name: str, text: str) -> None:
80
+ await self._send_raw(f"THREAD REPLY {channel} {thread_name} :{text}")
81
+
82
+ async def send_thread_close(self, channel: str, thread_name: str, summary: str) -> None:
83
+ await self._send_raw(f"THREADCLOSE {channel} {thread_name} :{summary}")
84
+
85
+ async def send_threads_list(self, channel: str) -> None:
86
+ await self._send_raw(f"THREADS {channel}")
87
+
76
88
  async def join_channel(self, channel: str) -> None:
77
89
  await self._send_raw(f"JOIN {channel}")
78
90
  if channel not in self.channels:
@@ -1,15 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  import time
4
5
  from collections import deque
5
6
  from dataclasses import dataclass, field
6
7
 
8
+ _THREAD_PREFIX_RE = re.compile(r"^\[thread:([a-zA-Z0-9\-]+)\] ")
9
+
7
10
 
8
11
  @dataclass
9
12
  class BufferedMessage:
10
13
  nick: str
11
14
  text: str
12
15
  timestamp: float
16
+ thread: str | None = None
13
17
 
14
18
 
15
19
  class MessageBuffer:
@@ -24,8 +28,13 @@ class MessageBuffer:
24
28
  self._buffers[channel] = deque(maxlen=self.max_per_channel)
25
29
  self._totals[channel] = 0
26
30
  self._cursors[channel] = 0
31
+ thread = None
32
+ m = _THREAD_PREFIX_RE.match(text)
33
+ if m:
34
+ thread = m.group(1)
27
35
  self._buffers[channel].append(
28
- BufferedMessage(nick=nick, text=text, timestamp=time.time())
36
+ BufferedMessage(nick=nick, text=text, timestamp=time.time(),
37
+ thread=thread)
29
38
  )
30
39
  self._totals[channel] += 1
31
40
 
@@ -44,3 +53,13 @@ class MessageBuffer:
44
53
  new_messages = new_messages[-limit:]
45
54
  self._cursors[channel] = total
46
55
  return new_messages
56
+
57
+ def read_thread(self, channel: str, thread_name: str,
58
+ limit: int = 50) -> list[BufferedMessage]:
59
+ buf = self._buffers.get(channel)
60
+ if not buf:
61
+ return []
62
+ matches = [m for m in buf if m.thread == thread_name]
63
+ if len(matches) > limit:
64
+ matches = matches[-limit:]
65
+ return matches
@@ -239,14 +239,28 @@ class AgentDaemon:
239
239
  def _on_mention(self, target: str, sender: str, text: str) -> None:
240
240
  """Called by IRCTransport when the agent is @mentioned or DM'd.
241
241
 
242
- Formats a prompt and enqueues it so the SDK session picks it up.
242
+ When the mention is inside a thread, provides thread-scoped context.
243
243
  """
244
244
  if self._paused:
245
245
  return
246
246
  if self._agent_runner and self._agent_runner.is_running():
247
247
  self._last_activation = time.time()
248
248
  if target.startswith("#"):
249
- prompt = f"[IRC @mention in {target}] <{sender}> {text}"
249
+ import re
250
+ thread_match = re.match(r"^\[thread:([a-zA-Z0-9\-]+)\] ", text)
251
+ if thread_match and self._buffer:
252
+ thread_name = thread_match.group(1)
253
+ thread_msgs = self._buffer.read_thread(target, thread_name)
254
+ history = "\n".join(
255
+ f" <{m.nick}> {m.text}" for m in thread_msgs
256
+ )
257
+ prompt = (
258
+ f"[IRC @mention in {target}, thread:{thread_name}]\n"
259
+ f"Thread history:\n{history}\n"
260
+ f" <{sender}> {text}"
261
+ )
262
+ else:
263
+ prompt = f"[IRC @mention in {target}] <{sender}> {text}"
250
264
  else:
251
265
  prompt = f"[IRC DM] <{sender}> {text}"
252
266
  asyncio.create_task(self._agent_runner.send_prompt(prompt))
@@ -451,6 +465,21 @@ class AgentDaemon:
451
465
  elif msg_type == "resume":
452
466
  return await self._ipc_resume(req_id)
453
467
 
468
+ elif msg_type == "irc_thread_create":
469
+ return await self._ipc_irc_thread_create(req_id, msg)
470
+
471
+ elif msg_type == "irc_thread_reply":
472
+ return await self._ipc_irc_thread_reply(req_id, msg)
473
+
474
+ elif msg_type == "irc_threads":
475
+ return await self._ipc_irc_threads(req_id, msg)
476
+
477
+ elif msg_type == "irc_thread_close":
478
+ return await self._ipc_irc_thread_close(req_id, msg)
479
+
480
+ elif msg_type == "irc_thread_read":
481
+ return await self._ipc_irc_thread_read(req_id, msg)
482
+
454
483
  elif msg_type == "shutdown":
455
484
  asyncio.create_task(self._graceful_shutdown())
456
485
  return make_response(req_id, ok=True)
@@ -580,6 +609,64 @@ class AgentDaemon:
580
609
  await self._transport.part_channel(channel)
581
610
  return make_response(req_id, ok=True)
582
611
 
612
+ async def _ipc_irc_thread_create(self, req_id: str, msg: dict) -> dict:
613
+ channel = msg.get("channel", "")
614
+ thread_name = msg.get("thread", "")
615
+ text = msg.get("message", "")
616
+ if not channel or not thread_name or not text:
617
+ return make_response(req_id, ok=False,
618
+ error="Missing 'channel', 'thread', or 'message'")
619
+ assert self._transport is not None
620
+ await self._transport.send_thread_create(channel, thread_name, text)
621
+ return make_response(req_id, ok=True)
622
+
623
+ async def _ipc_irc_thread_reply(self, req_id: str, msg: dict) -> dict:
624
+ channel = msg.get("channel", "")
625
+ thread_name = msg.get("thread", "")
626
+ text = msg.get("message", "")
627
+ if not channel or not thread_name or not text:
628
+ return make_response(req_id, ok=False,
629
+ error="Missing 'channel', 'thread', or 'message'")
630
+ assert self._transport is not None
631
+ await self._transport.send_thread_reply(channel, thread_name, text)
632
+ return make_response(req_id, ok=True)
633
+
634
+ async def _ipc_irc_threads(self, req_id: str, msg: dict) -> dict:
635
+ channel = msg.get("channel", "")
636
+ if not channel:
637
+ return make_response(req_id, ok=False, error="Missing 'channel'")
638
+ assert self._transport is not None
639
+ await self._transport.send_threads_list(channel)
640
+ return make_response(req_id, ok=True)
641
+
642
+ async def _ipc_irc_thread_close(self, req_id: str, msg: dict) -> dict:
643
+ channel = msg.get("channel", "")
644
+ thread_name = msg.get("thread", "")
645
+ summary = msg.get("summary", "")
646
+ if not channel or not thread_name:
647
+ return make_response(req_id, ok=False,
648
+ error="Missing 'channel' or 'thread'")
649
+ assert self._transport is not None
650
+ await self._transport.send_thread_close(channel, thread_name, summary)
651
+ return make_response(req_id, ok=True)
652
+
653
+ async def _ipc_irc_thread_read(self, req_id: str, msg: dict) -> dict:
654
+ channel = msg.get("channel", "")
655
+ thread_name = msg.get("thread", "")
656
+ limit = int(msg.get("limit", 50))
657
+ if not channel or not thread_name:
658
+ return make_response(req_id, ok=False,
659
+ error="Missing 'channel' or 'thread'")
660
+ assert self._buffer is not None
661
+ messages = self._buffer.read_thread(channel, thread_name, limit=limit)
662
+ return make_response(req_id, ok=True, data={
663
+ "messages": [
664
+ {"nick": m.nick, "text": m.text, "timestamp": m.timestamp,
665
+ "thread": m.thread}
666
+ for m in messages
667
+ ]
668
+ })
669
+
583
670
  async def _ipc_irc_channels(self, req_id: str) -> dict:
584
671
  assert self._transport is not None
585
672
  return make_response(req_id, ok=True, data={"channels": self._transport.channels})
@@ -73,6 +73,18 @@ class IRCTransport:
73
73
  async def send_privmsg(self, target: str, text: str) -> None:
74
74
  await self._send_raw(f"PRIVMSG {target} :{text}")
75
75
 
76
+ async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
77
+ await self._send_raw(f"THREAD CREATE {channel} {thread_name} :{text}")
78
+
79
+ async def send_thread_reply(self, channel: str, thread_name: str, text: str) -> None:
80
+ await self._send_raw(f"THREAD REPLY {channel} {thread_name} :{text}")
81
+
82
+ async def send_thread_close(self, channel: str, thread_name: str, summary: str) -> None:
83
+ await self._send_raw(f"THREADCLOSE {channel} {thread_name} :{summary}")
84
+
85
+ async def send_threads_list(self, channel: str) -> None:
86
+ await self._send_raw(f"THREADS {channel}")
87
+
76
88
  async def join_channel(self, channel: str) -> None:
77
89
  await self._send_raw(f"JOIN {channel}")
78
90
  if channel not in self.channels: