agentirc-cli 0.13.0__tar.gz → 0.14.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 (214) hide show
  1. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/CHANGELOG.md +22 -0
  2. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/PKG-INFO +2 -1
  3. agentirc_cli-0.14.0/agentirc/__init__.py +1 -0
  4. agentirc_cli-0.14.0/agentirc/__main__.py +5 -0
  5. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/cli.py +58 -1
  6. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/irc_transport.py +5 -1
  7. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/irc_transport.py +5 -1
  8. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/agent_runner.py +42 -24
  9. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/daemon.py +21 -2
  10. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/irc_transport.py +5 -1
  11. agentirc_cli-0.14.0/agentirc/overview/__init__.py +1 -0
  12. agentirc_cli-0.14.0/agentirc/overview/collector.py +288 -0
  13. agentirc_cli-0.14.0/agentirc/overview/model.py +53 -0
  14. agentirc_cli-0.14.0/agentirc/overview/renderer_text.py +196 -0
  15. agentirc_cli-0.14.0/agentirc/overview/renderer_web.py +119 -0
  16. agentirc_cli-0.14.0/agentirc/overview/web/style.css +88 -0
  17. agentirc_cli-0.14.0/docs/overview.md +68 -0
  18. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/irc_transport.py +5 -1
  19. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/pyproject.toml +3 -1
  20. agentirc_cli-0.14.0/tests/test_overview_cli.py +38 -0
  21. agentirc_cli-0.14.0/tests/test_overview_collector.py +102 -0
  22. agentirc_cli-0.14.0/tests/test_overview_model.py +62 -0
  23. agentirc_cli-0.14.0/tests/test_overview_renderer.py +160 -0
  24. agentirc_cli-0.14.0/tests/test_overview_web.py +50 -0
  25. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/uv.lock +12 -1
  26. agentirc_cli-0.13.0/agentirc/__init__.py +0 -1
  27. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.claude/skills/pr-review/SKILL.md +0 -0
  28. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.github/workflows/pages.yml +0 -0
  29. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.github/workflows/publish.yml +0 -0
  30. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.github/workflows/tests.yml +0 -0
  31. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.gitignore +0 -0
  32. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.markdownlint-cli2.yaml +0 -0
  33. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/.pr_agent.toml +0 -0
  34. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/CLAUDE.md +0 -0
  35. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/CNAME +0 -0
  36. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/Gemfile +0 -0
  37. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/Gemfile.lock +0 -0
  38. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/LICENSE +0 -0
  39. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/README.md +0 -0
  40. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/_config.yml +0 -0
  41. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/_sass/color_schemes/anthropic.scss +0 -0
  42. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/_sass/custom/custom.scss +0 -0
  43. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/__init__.py +0 -0
  44. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/__init__.py +0 -0
  45. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/__main__.py +0 -0
  46. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/agent_runner.py +0 -0
  47. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/config.py +0 -0
  48. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/daemon.py +0 -0
  49. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/ipc.py +0 -0
  50. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/irc_transport.py +0 -0
  51. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/message_buffer.py +0 -0
  52. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
  53. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/skill/__init__.py +0 -0
  54. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
  55. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/socket_server.py +0 -0
  56. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/supervisor.py +0 -0
  57. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/claude/webhook.py +0 -0
  58. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/__init__.py +0 -0
  59. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/agent_runner.py +0 -0
  60. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/config.py +0 -0
  61. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/daemon.py +0 -0
  62. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/ipc.py +0 -0
  63. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/message_buffer.py +0 -0
  64. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
  65. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/skill/__init__.py +0 -0
  66. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
  67. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/socket_server.py +0 -0
  68. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/supervisor.py +0 -0
  69. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/codex/webhook.py +0 -0
  70. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/__init__.py +0 -0
  71. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/agent_runner.py +0 -0
  72. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/config.py +0 -0
  73. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/daemon.py +0 -0
  74. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/ipc.py +0 -0
  75. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/message_buffer.py +0 -0
  76. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
  77. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
  78. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
  79. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/socket_server.py +0 -0
  80. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/supervisor.py +0 -0
  81. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/copilot/webhook.py +0 -0
  82. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/__init__.py +0 -0
  83. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/config.py +0 -0
  84. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/ipc.py +0 -0
  85. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/message_buffer.py +0 -0
  86. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/skill/SKILL.md +0 -0
  87. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/skill/__init__.py +0 -0
  88. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/skill/irc_client.py +0 -0
  89. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/socket_server.py +0 -0
  90. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/supervisor.py +0 -0
  91. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/clients/opencode/webhook.py +0 -0
  92. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/learn_prompt.py +0 -0
  93. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/observer.py +0 -0
  94. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/pidfile.py +0 -0
  95. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/__init__.py +0 -0
  96. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/commands.py +0 -0
  97. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/extensions/federation.md +0 -0
  98. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/extensions/history.md +0 -0
  99. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/message.py +0 -0
  100. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/protocol-index.md +0 -0
  101. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/protocol/replies.py +0 -0
  102. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/__init__.py +0 -0
  103. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/__main__.py +0 -0
  104. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/channel.py +0 -0
  105. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/client.py +0 -0
  106. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/config.py +0 -0
  107. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/ircd.py +0 -0
  108. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/remote_client.py +0 -0
  109. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/server_link.py +0 -0
  110. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/skill.py +0 -0
  111. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/skills/__init__.py +0 -0
  112. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/agentirc/server/skills/history.py +0 -0
  113. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/agent-client.md +0 -0
  114. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/agent-harness-spec.md +0 -0
  115. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/ci.md +0 -0
  116. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/cli.md +0 -0
  117. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/configuration.md +0 -0
  118. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/context-management.md +0 -0
  119. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/irc-tools.md +0 -0
  120. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/overview.md +0 -0
  121. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/setup.md +0 -0
  122. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/supervisor.md +0 -0
  123. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/claude/webhooks.md +0 -0
  124. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/configuration.md +0 -0
  125. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/context-management.md +0 -0
  126. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/irc-tools.md +0 -0
  127. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/overview.md +0 -0
  128. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/setup.md +0 -0
  129. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/supervisor.md +0 -0
  130. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/codex/webhooks.md +0 -0
  131. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/configuration.md +0 -0
  132. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/context-management.md +0 -0
  133. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/irc-tools.md +0 -0
  134. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/overview.md +0 -0
  135. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/setup.md +0 -0
  136. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/supervisor.md +0 -0
  137. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/copilot/webhooks.md +0 -0
  138. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/configuration.md +0 -0
  139. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/context-management.md +0 -0
  140. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/irc-tools.md +0 -0
  141. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/overview.md +0 -0
  142. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/setup.md +0 -0
  143. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/supervisor.md +0 -0
  144. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/clients/opencode/webhooks.md +0 -0
  145. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/codex-backend.md +0 -0
  146. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/copilot-backend.md +0 -0
  147. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/design.md +0 -0
  148. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/docs-site.md +0 -0
  149. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/getting-started.md +0 -0
  150. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/grow-your-agent.md +0 -0
  151. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/layer1-core-irc.md +0 -0
  152. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/layer2-attention.md +0 -0
  153. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/layer3-skills.md +0 -0
  154. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/layer4-federation.md +0 -0
  155. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/layer5-agent-harness.md +0 -0
  156. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/opencode-backend.md +0 -0
  157. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/publishing.md +0 -0
  158. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  159. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/server-architecture.md +0 -0
  160. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  161. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  162. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  163. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  164. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/01-pair-programming.md +0 -0
  165. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
  166. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
  167. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
  168. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/05-the-observer.md +0 -0
  169. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/06-cross-server-ops.md +0 -0
  170. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
  171. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/08-apps-as-agents.md +0 -0
  172. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/09-research-swarm.md +0 -0
  173. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases/10-grow-your-agent.md +0 -0
  174. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/docs/use-cases-index.md +0 -0
  175. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/index.md +0 -0
  176. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/README.md +0 -0
  177. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/config.py +0 -0
  178. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/daemon.py +0 -0
  179. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/ipc.py +0 -0
  180. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/message_buffer.py +0 -0
  181. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/skill/SKILL.md +0 -0
  182. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/skill/irc_client.py +0 -0
  183. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/socket_server.py +0 -0
  184. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/packages/agent-harness/webhook.py +0 -0
  185. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  186. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  187. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
  188. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/__init__.py +0 -0
  189. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/conftest.py +0 -0
  190. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_agent_runner.py +0 -0
  191. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_channel.py +0 -0
  192. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_codex_daemon.py +0 -0
  193. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_connection.py +0 -0
  194. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_copilot_daemon.py +0 -0
  195. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_daemon.py +0 -0
  196. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_daemon_config.py +0 -0
  197. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_daemon_ipc.py +0 -0
  198. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_discovery.py +0 -0
  199. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_federation.py +0 -0
  200. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_history.py +0 -0
  201. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_integration_layer5.py +0 -0
  202. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_ipc.py +0 -0
  203. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_irc_transport.py +0 -0
  204. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_mentions.py +0 -0
  205. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_message.py +0 -0
  206. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_message_buffer.py +0 -0
  207. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_messaging.py +0 -0
  208. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_modes.py +0 -0
  209. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_opencode_daemon.py +0 -0
  210. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_skill_client.py +0 -0
  211. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_skills.py +0 -0
  212. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_socket_server.py +0 -0
  213. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_supervisor.py +0 -0
  214. {agentirc_cli-0.13.0 → agentirc_cli-0.14.0}/tests/test_webhook.py +0 -0
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.14.0] - 2026-03-30
8
+
9
+
10
+ ### Added
11
+
12
+ - agentirc overview CLI subcommand — mesh-wide situational awareness
13
+ - Markdown-formatted default view with rooms, agents, messages, federation
14
+ - Room drill-down (--room) and agent drill-down (--agent) views
15
+ - Configurable message count (--messages N, default 4, max 20)
16
+ - Live web dashboard (--serve) with anthropic cream styling and auto-refresh
17
+ - IRC Observer-based collector with daemon IPC enrichment for local agents
18
+
19
+ ## [0.13.1] - 2026-03-30
20
+
21
+
22
+ ### Fixed
23
+
24
+ - Fix OpenCode agent crash (exit code -1) caused by 30s timeout on system prompt session/prompt call
25
+ - Capture stderr from opencode subprocess for debugging
26
+ - Add _running guard to busy-wait loops to prevent hang on process death
27
+ - Wrap _start_agent_runner with error handling so runner failures schedule retry instead of crashing daemon
28
+
7
29
  ## [0.13.0] - 2026-03-29
8
30
 
9
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: IRC protocol chatrooms for AI agents (and humans allowed)
5
5
  Project-URL: Homepage, https://github.com/OriNachum/agentirc
6
6
  Author: Ori Nachum
@@ -14,6 +14,7 @@ Classifier: Topic :: Communications :: Chat :: Internet Relay Chat
14
14
  Requires-Python: >=3.12
15
15
  Requires-Dist: anthropic>=0.40
16
16
  Requires-Dist: claude-agent-sdk>=0.1
17
+ Requires-Dist: mistune>=3.0
17
18
  Requires-Dist: pyyaml>=6.0
18
19
  Provides-Extra: copilot
19
20
  Requires-Dist: github-copilot-sdk; extra == 'copilot'
@@ -0,0 +1 @@
1
+ __version__ = "0.14.0"
@@ -0,0 +1,5 @@
1
+ """Allow running agentirc as ``python -m agentirc``."""
2
+ from agentirc.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -13,6 +13,7 @@ Subcommands:
13
13
  agentirc learn [--nick X] Print self-teaching prompt for your agent
14
14
  agentirc sleep [nick] [--all] Pause agent(s) — stay connected but idle
15
15
  agentirc wake [nick] [--all] Resume paused agent(s)
16
+ agentirc overview [--room X] [--agent X] Show mesh overview
16
17
  """
17
18
  from __future__ import annotations
18
19
 
@@ -72,7 +73,7 @@ LOG_DIR = os.path.expanduser("~/.agentirc/logs")
72
73
  # Main entry point
73
74
  # -----------------------------------------------------------------------
74
75
 
75
- def main() -> None:
76
+ def _build_parser() -> argparse.ArgumentParser:
76
77
  parser = argparse.ArgumentParser(
77
78
  prog="agentirc",
78
79
  description="agentirc — AI agent IRC mesh",
@@ -170,6 +171,20 @@ def main() -> None:
170
171
  help="Target agent: claude, codex, opencode, copilot, or all",
171
172
  )
172
173
 
174
+ # -- overview subcommand -----------------------------------------------
175
+ overview_parser = sub.add_parser("overview", help="Show mesh overview: rooms, agents, messages")
176
+ overview_parser.add_argument("--room", default=None, help="Drill down into a specific room")
177
+ overview_parser.add_argument("--agent", default=None, help="Drill down into a specific agent")
178
+ overview_parser.add_argument("--messages", "-n", type=int, default=4, help="Messages per room (default: 4, max: 20)")
179
+ overview_parser.add_argument("--serve", action="store_true", help="Start live web dashboard")
180
+ overview_parser.add_argument("--refresh", type=int, default=5, help="Web refresh interval in seconds (default: 5, min: 1)")
181
+ overview_parser.add_argument("--config", default=DEFAULT_CONFIG)
182
+
183
+ return parser
184
+
185
+
186
+ def main() -> None:
187
+ parser = _build_parser()
173
188
  args = parser.parse_args()
174
189
 
175
190
  if args.command is None:
@@ -196,6 +211,7 @@ def main() -> None:
196
211
  "sleep": _cmd_sleep,
197
212
  "wake": _cmd_wake,
198
213
  "skills": _cmd_skills,
214
+ "overview": _cmd_overview,
199
215
  }
200
216
  handler = dispatch.get(args.command)
201
217
  if handler:
@@ -1057,3 +1073,44 @@ def _cmd_skills(args: argparse.Namespace) -> None:
1057
1073
  if target == "all":
1058
1074
  print("\nSkills installed for Claude Code, Codex, OpenCode, and Copilot.")
1059
1075
  print(f"\nSet AGENTIRC_NICK in your shell profile to enable the skill.")
1076
+
1077
+
1078
+ # -----------------------------------------------------------------------
1079
+ # Overview subcommand
1080
+ # -----------------------------------------------------------------------
1081
+
1082
+ def _cmd_overview(args: argparse.Namespace) -> None:
1083
+ """Show mesh overview."""
1084
+ from agentirc.overview.collector import collect_mesh_state
1085
+ from agentirc.overview.renderer_text import render_text
1086
+
1087
+ config = load_config_or_default(args.config)
1088
+ message_limit = max(1, min(args.messages, 20))
1089
+ refresh_interval = max(1, args.refresh)
1090
+
1091
+ if args.serve:
1092
+ from agentirc.overview.renderer_web import serve_web
1093
+ serve_web(
1094
+ host=config.server.host,
1095
+ port=config.server.port,
1096
+ server_name=config.server.name,
1097
+ room_filter=args.room,
1098
+ agent_filter=args.agent,
1099
+ message_limit=message_limit,
1100
+ refresh_interval=refresh_interval,
1101
+ )
1102
+ return
1103
+
1104
+ mesh = asyncio.run(collect_mesh_state(
1105
+ host=config.server.host,
1106
+ port=config.server.port,
1107
+ server_name=config.server.name,
1108
+ message_limit=message_limit,
1109
+ ))
1110
+ output = render_text(
1111
+ mesh,
1112
+ room_filter=args.room,
1113
+ agent_filter=args.agent,
1114
+ message_limit=message_limit,
1115
+ )
1116
+ print(output, end="")
@@ -82,11 +82,15 @@ class IRCTransport:
82
82
  async def send_who(self, target: str) -> None:
83
83
  await self._send_raw(f"WHO {target}")
84
84
 
85
- async def _send_raw(self, line: str) -> None:
85
+ async def send_raw(self, line: str) -> None:
86
+ """Send a raw IRC line. Public for commands like HISTORY."""
86
87
  if self._writer:
87
88
  self._writer.write(f"{line}\r\n".encode())
88
89
  await self._writer.drain()
89
90
 
91
+ async def _send_raw(self, line: str) -> None:
92
+ await self.send_raw(line)
93
+
90
94
  async def _read_loop(self) -> None:
91
95
  buf = ""
92
96
  try:
@@ -82,11 +82,15 @@ class IRCTransport:
82
82
  async def send_who(self, target: str) -> None:
83
83
  await self._send_raw(f"WHO {target}")
84
84
 
85
- async def _send_raw(self, line: str) -> None:
85
+ async def send_raw(self, line: str) -> None:
86
+ """Send a raw IRC line. Public for commands like HISTORY."""
86
87
  if self._writer:
87
88
  self._writer.write(f"{line}\r\n".encode())
88
89
  await self._writer.drain()
89
90
 
91
+ async def _send_raw(self, line: str) -> None:
92
+ await self.send_raw(line)
93
+
90
94
  async def _read_loop(self) -> None:
91
95
  buf = ""
92
96
  try:
@@ -38,6 +38,7 @@ class OpenCodeAgentRunner:
38
38
  self._prompt_queue: asyncio.Queue[str] = asyncio.Queue()
39
39
  self._task: asyncio.Task | None = None
40
40
  self._reader_task: asyncio.Task | None = None
41
+ self._stderr_task: asyncio.Task | None = None
41
42
  self._stopping = False
42
43
  self._request_id = 0
43
44
  self._pending: dict[int, asyncio.Future] = {}
@@ -68,13 +69,14 @@ class OpenCodeAgentRunner:
68
69
  "opencode", "acp",
69
70
  stdin=asyncio.subprocess.PIPE,
70
71
  stdout=asyncio.subprocess.PIPE,
71
- stderr=asyncio.subprocess.DEVNULL,
72
+ stderr=asyncio.subprocess.PIPE,
72
73
  limit=1024 * 1024, # 1MB line buffer
73
74
  env=isolated_env,
74
75
  )
75
76
 
76
- # Start reading responses
77
+ # Start reading responses and stderr
77
78
  self._reader_task = asyncio.create_task(self._read_loop())
79
+ self._stderr_task = asyncio.create_task(self._stderr_loop())
78
80
 
79
81
  # Initialize with ACP protocol
80
82
  resp = await self._send_request("initialize", {
@@ -102,30 +104,19 @@ class OpenCodeAgentRunner:
102
104
  self._running = True
103
105
  logger.info("OpenCode session started: %s", self._session_id)
104
106
 
105
- # Send system prompt as the first turn so all subsequent turns
106
- # are conditioned on it (ACP has no dedicated system instructions field)
107
- if self.system_prompt:
108
- self._busy = True
109
- resp = await self._send_request("session/prompt", {
110
- "sessionId": self._session_id,
111
- "prompt": [{"type": "text", "text": self.system_prompt}],
112
- })
113
- # Wait for turn to finish before accepting user prompts
114
- if resp.get("result", {}).get("stopReason"):
115
- self._busy = False
116
- self._accumulated_text = ""
117
- else:
118
- while self._busy:
119
- await asyncio.sleep(0.1)
120
-
121
107
  # Start the prompt processing loop
122
108
  self._task = asyncio.create_task(self._prompt_loop())
123
109
 
110
+ # Queue system prompt as the first turn so all subsequent turns
111
+ # are conditioned on it (ACP has no dedicated system instructions field).
112
+ # Queued rather than awaited to avoid blocking start() on LLM completion.
113
+ if self.system_prompt:
114
+ await self.send_prompt(self.system_prompt)
115
+
124
116
  if initial_prompt:
125
117
  await self.send_prompt(initial_prompt)
126
118
  except Exception:
127
- shutil.rmtree(self._isolated_home, ignore_errors=True)
128
- self._isolated_home = None
119
+ await self.stop()
129
120
  raise
130
121
 
131
122
  async def stop(self) -> None:
@@ -147,6 +138,13 @@ class OpenCodeAgentRunner:
147
138
  except asyncio.CancelledError:
148
139
  pass
149
140
 
141
+ if self._stderr_task:
142
+ self._stderr_task.cancel()
143
+ try:
144
+ await self._stderr_task
145
+ except asyncio.CancelledError:
146
+ pass
147
+
150
148
  if self._process:
151
149
  try:
152
150
  self._process.terminate()
@@ -179,7 +177,7 @@ class OpenCodeAgentRunner:
179
177
  self._request_id += 1
180
178
  return self._request_id
181
179
 
182
- async def _send_request(self, method: str, params: dict) -> dict:
180
+ async def _send_request(self, method: str, params: dict, timeout: float = 30) -> dict:
183
181
  """Send a JSON-RPC request and wait for the response."""
184
182
  if not self._process or not self._process.stdin:
185
183
  raise ConnectionError("ACP server not running")
@@ -196,7 +194,7 @@ class OpenCodeAgentRunner:
196
194
  await self._process.stdin.drain()
197
195
 
198
196
  try:
199
- return await asyncio.wait_for(future, timeout=30)
197
+ return await asyncio.wait_for(future, timeout=timeout)
200
198
  except (asyncio.TimeoutError, asyncio.CancelledError):
201
199
  self._pending.pop(req_id, None)
202
200
  if not future.done():
@@ -263,9 +261,29 @@ class OpenCodeAgentRunner:
263
261
 
264
262
  self._running = False
265
263
 
264
+ # Cancel companion tasks so they don't outlive the process
265
+ for task in (self._task, self._stderr_task):
266
+ if task and not task.done():
267
+ task.cancel()
268
+
266
269
  if not self._stopping and self.on_exit:
267
270
  await self.on_exit(returncode)
268
271
 
272
+ async def _stderr_loop(self) -> None:
273
+ """Log stderr output from the opencode process."""
274
+ if not self._process or not self._process.stderr:
275
+ return
276
+ try:
277
+ while True:
278
+ line = await self._process.stderr.readline()
279
+ if not line:
280
+ break
281
+ text = line.decode("utf-8", errors="replace").rstrip()
282
+ if text:
283
+ logger.warning("opencode stderr: %s", text)
284
+ except (asyncio.CancelledError, ConnectionError):
285
+ pass
286
+
269
287
  async def _handle_notification(self, msg: dict) -> None:
270
288
  """Handle ACP server notifications."""
271
289
  method = msg.get("method", "")
@@ -321,7 +339,7 @@ class OpenCodeAgentRunner:
321
339
  resp = await self._send_request("session/prompt", {
322
340
  "sessionId": self._session_id,
323
341
  "prompt": [{"type": "text", "text": text}],
324
- })
342
+ }, timeout=120)
325
343
 
326
344
  # Check if response itself signals turn completion
327
345
  result = resp.get("result", {})
@@ -338,7 +356,7 @@ class OpenCodeAgentRunner:
338
356
  self._busy = False
339
357
 
340
358
  # Wait for turn to complete (via notifications)
341
- while self._busy:
359
+ while self._busy and self._running:
342
360
  await asyncio.sleep(0.1)
343
361
 
344
362
  except Exception:
@@ -131,7 +131,16 @@ class OpenCodeDaemon:
131
131
 
132
132
  # 6. Optionally start the OpenCode agent runner
133
133
  if not self.skip_opencode:
134
- await self._start_agent_runner()
134
+ try:
135
+ await self._start_agent_runner()
136
+ except Exception:
137
+ logger.exception(
138
+ "Failed to start agent runner for %s, scheduling retry",
139
+ self.agent.nick,
140
+ )
141
+ self._agent_runner = None
142
+ self._crash_times.append(time.time())
143
+ asyncio.create_task(self._delayed_restart())
135
144
 
136
145
  # 7. Sleep scheduler background task
137
146
  self._sleep_task = asyncio.create_task(self._sleep_scheduler())
@@ -241,6 +250,8 @@ class OpenCodeDaemon:
241
250
  on_exit=self._on_agent_exit,
242
251
  on_message=self._on_agent_message,
243
252
  )
253
+ # Absorb the system prompt response without relaying to IRC
254
+ self._mention_targets.append(None)
244
255
  await self._agent_runner.start()
245
256
  logger.info("OpenCodeAgentRunner started for %s", self.agent.nick)
246
257
 
@@ -366,7 +377,15 @@ class OpenCodeDaemon:
366
377
  async def _delayed_restart(self) -> None:
367
378
  await asyncio.sleep(CRASH_RESTART_DELAY)
368
379
  if not self._circuit_open and self._transport is not None:
369
- await self._start_agent_runner()
380
+ try:
381
+ await self._start_agent_runner()
382
+ except Exception:
383
+ logger.exception(
384
+ "Failed to restart agent runner for %s",
385
+ self.agent.nick,
386
+ )
387
+ # Record as a crash so the circuit breaker can track it
388
+ await self._on_agent_exit(-1)
370
389
 
371
390
  # ------------------------------------------------------------------
372
391
  # Supervisor callbacks
@@ -82,11 +82,15 @@ class IRCTransport:
82
82
  async def send_who(self, target: str) -> None:
83
83
  await self._send_raw(f"WHO {target}")
84
84
 
85
- async def _send_raw(self, line: str) -> None:
85
+ async def send_raw(self, line: str) -> None:
86
+ """Send a raw IRC line. Public for commands like HISTORY."""
86
87
  if self._writer:
87
88
  self._writer.write(f"{line}\r\n".encode())
88
89
  await self._writer.drain()
89
90
 
91
+ async def _send_raw(self, line: str) -> None:
92
+ await self.send_raw(line)
93
+
90
94
  async def _read_loop(self) -> None:
91
95
  buf = ""
92
96
  try:
@@ -0,0 +1 @@
1
+ """agentirc overview — mesh visualization and situational awareness."""
@@ -0,0 +1,288 @@
1
+ """Collect mesh state via IRC Observer queries and daemon IPC."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import glob
6
+ import os
7
+
8
+ from agentirc.protocol.message import Message as IRCMessage
9
+
10
+ from .model import Agent, Message, MeshState, Room
11
+
12
+
13
+ RECV_TIMEOUT = 5.0
14
+ REGISTER_TIMEOUT = 10.0
15
+
16
+
17
+ def _temp_nick(server_name: str) -> str:
18
+ return f"{server_name}-_overview{os.urandom(2).hex()}"
19
+
20
+
21
+ async def collect_mesh_state(
22
+ host: str,
23
+ port: int,
24
+ server_name: str,
25
+ message_limit: int = 4,
26
+ ipc_enabled: bool = True,
27
+ ) -> MeshState:
28
+ """Collect a full mesh snapshot.
29
+
30
+ Connects as an ephemeral IRC client, queries LIST/WHO/HISTORY,
31
+ optionally enriches local agents via daemon IPC.
32
+ """
33
+ reader, writer, nick = await _connect(host, port, server_name)
34
+ try:
35
+ channels = await _query_list(reader, writer, nick)
36
+ rooms: list[Room] = []
37
+ all_agents: dict[str, Agent] = {}
38
+
39
+ for ch_name, ch_topic in channels:
40
+ members, _ = await _query_names(reader, writer, nick, ch_name)
41
+ who_data = await _query_who(reader, writer, nick, ch_name)
42
+ messages = await _query_history(reader, writer, nick, ch_name, message_limit)
43
+
44
+ room_agents = []
45
+ fed_servers: set[str] = set()
46
+ for member_nick, is_op in members:
47
+ server_of = who_data.get(member_nick, server_name)
48
+ is_remote = server_of != server_name
49
+ if is_remote:
50
+ fed_servers.add(server_of)
51
+
52
+ if member_nick not in all_agents:
53
+ all_agents[member_nick] = Agent(
54
+ nick=member_nick,
55
+ status="remote" if is_remote else "active",
56
+ activity="",
57
+ channels=[],
58
+ server=server_of,
59
+ )
60
+ agent = all_agents[member_nick]
61
+ if ch_name not in agent.channels:
62
+ agent.channels.append(ch_name)
63
+ room_agents.append(agent)
64
+
65
+ op_nicks = [n for n, is_op in members if is_op]
66
+ rooms.append(Room(
67
+ name=ch_name,
68
+ topic=ch_topic,
69
+ members=room_agents,
70
+ operators=op_nicks,
71
+ federation_servers=sorted(fed_servers),
72
+ messages=messages,
73
+ ))
74
+
75
+ fed_links = sorted({a.server for a in all_agents.values() if a.server != server_name})
76
+
77
+ # Enrich local agents via daemon IPC
78
+ if ipc_enabled:
79
+ await _enrich_via_ipc(all_agents, server_name)
80
+
81
+ return MeshState(
82
+ server_name=server_name,
83
+ rooms=rooms,
84
+ agents=sorted(all_agents.values(), key=lambda a: a.nick),
85
+ federation_links=fed_links,
86
+ )
87
+ finally:
88
+ await _disconnect(writer)
89
+
90
+
91
+ async def _connect(
92
+ host: str, port: int, server_name: str,
93
+ ) -> tuple[asyncio.StreamReader, asyncio.StreamWriter, str]:
94
+ """Connect and register as an ephemeral observer."""
95
+ reader, writer = await asyncio.wait_for(
96
+ asyncio.open_connection(host, port), timeout=REGISTER_TIMEOUT,
97
+ )
98
+ nick = _temp_nick(server_name)
99
+ writer.write(f"NICK {nick}\r\nUSER overview 0 * :overview\r\n".encode())
100
+ await writer.drain()
101
+
102
+ deadline = asyncio.get_event_loop().time() + REGISTER_TIMEOUT
103
+ while True:
104
+ remaining = deadline - asyncio.get_event_loop().time()
105
+ if remaining <= 0:
106
+ raise TimeoutError("Registration timed out")
107
+ data = await asyncio.wait_for(reader.readline(), timeout=remaining)
108
+ line = data.decode().strip()
109
+ if not line:
110
+ continue
111
+ msg = IRCMessage.parse(line)
112
+ if msg.command == "PING":
113
+ writer.write(f"PONG :{msg.params[0]}\r\n".encode())
114
+ await writer.drain()
115
+ elif msg.command == "001":
116
+ return reader, writer, nick
117
+ elif msg.command == "433":
118
+ nick = _temp_nick(server_name)
119
+ writer.write(f"NICK {nick}\r\n".encode())
120
+ await writer.drain()
121
+
122
+
123
+ async def _disconnect(writer: asyncio.StreamWriter) -> None:
124
+ try:
125
+ writer.write(b"QUIT :overview done\r\n")
126
+ await writer.drain()
127
+ writer.close()
128
+ await writer.wait_closed()
129
+ except Exception:
130
+ pass
131
+
132
+
133
+ async def _recv_until(
134
+ reader: asyncio.StreamReader,
135
+ writer: asyncio.StreamWriter,
136
+ stop_commands: set[str],
137
+ timeout: float = RECV_TIMEOUT,
138
+ ) -> list[IRCMessage]:
139
+ """Read IRC messages until a stop command is seen."""
140
+ messages = []
141
+ deadline = asyncio.get_event_loop().time() + timeout
142
+ while True:
143
+ remaining = deadline - asyncio.get_event_loop().time()
144
+ if remaining <= 0:
145
+ break
146
+ try:
147
+ data = await asyncio.wait_for(reader.readline(), timeout=remaining)
148
+ except asyncio.TimeoutError:
149
+ break
150
+ line = data.decode().strip()
151
+ if not line:
152
+ continue
153
+ msg = IRCMessage.parse(line)
154
+ if msg.command == "PING":
155
+ writer.write(f"PONG :{msg.params[0]}\r\n".encode())
156
+ await writer.drain()
157
+ continue
158
+ messages.append(msg)
159
+ if msg.command in stop_commands:
160
+ break
161
+ return messages
162
+
163
+
164
+ async def _query_list(
165
+ reader: asyncio.StreamReader,
166
+ writer: asyncio.StreamWriter,
167
+ nick: str,
168
+ ) -> list[tuple[str, str]]:
169
+ """Query LIST and return [(channel_name, topic)]."""
170
+ writer.write(b"LIST\r\n")
171
+ await writer.drain()
172
+ messages = await _recv_until(reader, writer, {"323"})
173
+ channels = []
174
+ for msg in messages:
175
+ if msg.command == "322" and len(msg.params) >= 4:
176
+ ch_name = msg.params[1]
177
+ topic = msg.params[3] if len(msg.params) > 3 else ""
178
+ channels.append((ch_name, topic))
179
+ return channels
180
+
181
+
182
+ async def _query_names(
183
+ reader: asyncio.StreamReader,
184
+ writer: asyncio.StreamWriter,
185
+ nick: str,
186
+ channel: str,
187
+ ) -> tuple[list[tuple[str, bool]], list[str]]:
188
+ """Query NAMES and return [(nick, is_operator)] and [operator_nicks]."""
189
+ writer.write(f"NAMES {channel}\r\n".encode())
190
+ await writer.drain()
191
+ messages = await _recv_until(reader, writer, {"366"})
192
+ members = []
193
+ operators = []
194
+ for msg in messages:
195
+ if msg.command == "353" and len(msg.params) >= 4:
196
+ names_str = msg.params[3] if len(msg.params) > 3 else msg.params[-1]
197
+ for name in names_str.split():
198
+ is_op = name.startswith("@")
199
+ clean = name.lstrip("@+")
200
+ members.append((clean, is_op))
201
+ if is_op:
202
+ operators.append(clean)
203
+ return members, operators
204
+
205
+
206
+ async def _query_who(
207
+ reader: asyncio.StreamReader,
208
+ writer: asyncio.StreamWriter,
209
+ nick: str,
210
+ channel: str,
211
+ ) -> dict[str, str]:
212
+ """Query WHO and return {nick: server_name}."""
213
+ writer.write(f"WHO {channel}\r\n".encode())
214
+ await writer.drain()
215
+ messages = await _recv_until(reader, writer, {"315"})
216
+ result = {}
217
+ for msg in messages:
218
+ if msg.command == "352" and len(msg.params) >= 6:
219
+ member_nick = msg.params[5]
220
+ member_server = msg.params[4]
221
+ result[member_nick] = member_server
222
+ return result
223
+
224
+
225
+ async def _query_history(
226
+ reader: asyncio.StreamReader,
227
+ writer: asyncio.StreamWriter,
228
+ nick: str,
229
+ channel: str,
230
+ limit: int,
231
+ ) -> list[Message]:
232
+ """Query HISTORY RECENT and return Message objects."""
233
+ writer.write(f"HISTORY RECENT {channel} {limit}\r\n".encode())
234
+ await writer.drain()
235
+ messages = await _recv_until(reader, writer, {"HISTORYEND"})
236
+ result = []
237
+ for msg in messages:
238
+ if msg.command == "HISTORY" and len(msg.params) >= 4:
239
+ result.append(Message(
240
+ nick=msg.params[1],
241
+ text=msg.params[3],
242
+ timestamp=float(msg.params[2]),
243
+ channel=channel,
244
+ ))
245
+ return result
246
+
247
+
248
+ async def _enrich_via_ipc(agents: dict[str, Agent], server_name: str) -> None:
249
+ """Enrich local agents with daemon IPC status data."""
250
+ from agentirc.clients.claude.ipc import decode_message, encode_message, make_request
251
+
252
+ runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
253
+ socket_pattern = os.path.join(runtime_dir, "agentirc-*.sock")
254
+
255
+ for sock_path in glob.glob(socket_pattern):
256
+ # Extract nick from socket filename: agentirc-<nick>.sock
257
+ basename = os.path.basename(sock_path)
258
+ agent_nick = basename[len("agentirc-"):-len(".sock")]
259
+
260
+ if agent_nick not in agents:
261
+ continue
262
+
263
+ agent = agents[agent_nick]
264
+ if agent.server != server_name:
265
+ continue
266
+
267
+ try:
268
+ r, w = await asyncio.wait_for(
269
+ asyncio.open_unix_connection(sock_path), timeout=3.0,
270
+ )
271
+ req = make_request("status")
272
+ w.write(encode_message(req))
273
+ await w.drain()
274
+
275
+ data = await asyncio.wait_for(r.readline(), timeout=3.0)
276
+ resp = decode_message(data)
277
+
278
+ if resp and resp.get("type") == "response" and resp.get("ok"):
279
+ info = resp.get("data", {})
280
+ agent.activity = info.get("description", "")
281
+ agent.turns = info.get("turn_count")
282
+ if info.get("paused"):
283
+ agent.status = "paused"
284
+
285
+ w.close()
286
+ await w.wait_closed()
287
+ except Exception:
288
+ pass