agentirc-cli 4.3.2__tar.gz → 4.3.3__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 (310) hide show
  1. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/CHANGELOG.md +7 -0
  2. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/PKG-INFO +1 -1
  3. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/agent_runner.py +46 -36
  4. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/daemon.py +22 -16
  5. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/skill/irc_client.py +98 -57
  6. {agentirc_cli-4.3.2/culture/clients/claude → agentirc_cli-4.3.3/culture/clients/acp}/supervisor.py +8 -4
  7. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/agent_runner.py +14 -6
  8. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/skill/irc_client.py +98 -57
  9. {agentirc_cli-4.3.2/culture/clients/acp → agentirc_cli-4.3.3/culture/clients/claude}/supervisor.py +8 -4
  10. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/agent_runner.py +41 -31
  11. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/daemon.py +22 -16
  12. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/skill/irc_client.py +98 -57
  13. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/supervisor.py +22 -14
  14. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/agent_runner.py +26 -17
  15. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/daemon.py +22 -16
  16. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/skill/irc_client.py +98 -57
  17. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/supervisor.py +25 -15
  18. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/observer.py +55 -90
  19. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/collector.py +64 -41
  20. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/renderer_text.py +67 -39
  21. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/renderer_web.py +22 -14
  22. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/persistence.py +34 -19
  23. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/client.py +146 -154
  24. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/ircd.py +16 -11
  25. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/server_link.py +125 -133
  26. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skills/rooms.py +34 -24
  27. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/skill/irc_client.py +98 -57
  28. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/pyproject.toml +1 -1
  29. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/uv.lock +1 -1
  30. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.claude/skills/pr-review/SKILL.md +0 -0
  31. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.claude/skills/run-tests/SKILL.md +0 -0
  32. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.claude/skills/run-tests/scripts/test.sh +0 -0
  33. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.flake8 +0 -0
  34. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.github/workflows/pages.yml +0 -0
  35. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.github/workflows/publish.yml +0 -0
  36. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.github/workflows/security-checks.yml +0 -0
  37. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.github/workflows/tests.yml +0 -0
  38. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.gitignore +0 -0
  39. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.markdownlint-cli2.yaml +0 -0
  40. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.pr_agent.toml +0 -0
  41. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.pre-commit-config.yaml +0 -0
  42. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/.pylintrc +0 -0
  43. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/CLAUDE.md +0 -0
  44. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/CNAME +0 -0
  45. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/Gemfile +0 -0
  46. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/Gemfile.lock +0 -0
  47. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/LICENSE +0 -0
  48. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/README.md +0 -0
  49. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/SECURITY.md +0 -0
  50. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/_config.yml +0 -0
  51. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/_sass/color_schemes/anthropic.scss +0 -0
  52. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/_sass/custom/custom.scss +0 -0
  53. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/__init__.py +0 -0
  54. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/__main__.py +0 -0
  55. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/aio.py +0 -0
  56. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/__init__.py +0 -0
  57. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/bot.py +0 -0
  58. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/bot_manager.py +0 -0
  59. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/config.py +0 -0
  60. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/http_listener.py +0 -0
  61. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/template_engine.py +0 -0
  62. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/bots/virtual_client.py +0 -0
  63. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/__init__.py +0 -0
  64. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/_helpers.py +0 -0
  65. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/agent.py +0 -0
  66. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/bot.py +0 -0
  67. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/channel.py +0 -0
  68. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/mesh.py +0 -0
  69. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/server.py +0 -0
  70. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/cli/skills.py +0 -0
  71. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/__init__.py +0 -0
  72. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/__init__.py +0 -0
  73. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/config.py +0 -0
  74. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/ipc.py +0 -0
  75. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/irc_transport.py +0 -0
  76. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/message_buffer.py +0 -0
  77. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/skill/SKILL.md +0 -0
  78. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/skill/__init__.py +0 -0
  79. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/socket_server.py +0 -0
  80. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/acp/webhook.py +0 -0
  81. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/__init__.py +0 -0
  82. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/__main__.py +0 -0
  83. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/config.py +0 -0
  84. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/daemon.py +0 -0
  85. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/ipc.py +0 -0
  86. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/irc_transport.py +0 -0
  87. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/message_buffer.py +0 -0
  88. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/skill/SKILL.md +0 -0
  89. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/skill/__init__.py +0 -0
  90. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/socket_server.py +0 -0
  91. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/claude/webhook.py +0 -0
  92. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/__init__.py +0 -0
  93. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/config.py +0 -0
  94. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/ipc.py +0 -0
  95. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/irc_transport.py +0 -0
  96. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/message_buffer.py +0 -0
  97. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/skill/SKILL.md +0 -0
  98. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/skill/__init__.py +0 -0
  99. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/socket_server.py +0 -0
  100. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/codex/webhook.py +0 -0
  101. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/__init__.py +0 -0
  102. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/config.py +0 -0
  103. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/ipc.py +0 -0
  104. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/irc_transport.py +0 -0
  105. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/message_buffer.py +0 -0
  106. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/skill/SKILL.md +0 -0
  107. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/skill/__init__.py +0 -0
  108. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/socket_server.py +0 -0
  109. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/clients/copilot/webhook.py +0 -0
  110. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/__init__.py +0 -0
  111. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/app.py +0 -0
  112. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/client.py +0 -0
  113. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/commands.py +0 -0
  114. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/widgets/__init__.py +0 -0
  115. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/widgets/chat.py +0 -0
  116. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/widgets/info_panel.py +0 -0
  117. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/console/widgets/sidebar.py +0 -0
  118. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/credentials.py +0 -0
  119. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/learn_prompt.py +0 -0
  120. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/mesh_config.py +0 -0
  121. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/__init__.py +0 -0
  122. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/model.py +0 -0
  123. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/overview/web/style.css +0 -0
  124. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/pidfile.py +0 -0
  125. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/__init__.py +0 -0
  126. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/commands.py +0 -0
  127. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/federation.md +0 -0
  128. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/history.md +0 -0
  129. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/icons.md +0 -0
  130. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/rooms.md +0 -0
  131. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/tags.md +0 -0
  132. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/extensions/threads.md +0 -0
  133. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/message.py +0 -0
  134. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/protocol-index.md +0 -0
  135. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/protocol/replies.py +0 -0
  136. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/__init__.py +0 -0
  137. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/__main__.py +0 -0
  138. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/channel.py +0 -0
  139. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/config.py +0 -0
  140. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/remote_client.py +0 -0
  141. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/room_store.py +0 -0
  142. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/rooms_util.py +0 -0
  143. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skill.py +0 -0
  144. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skills/__init__.py +0 -0
  145. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skills/history.py +0 -0
  146. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skills/icon.py +0 -0
  147. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/skills/threads.py +0 -0
  148. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/server/thread_store.py +0 -0
  149. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/culture/skills/culture/SKILL.md +0 -0
  150. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/agent-lifecycle.md +0 -0
  151. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/agentic-self-learn.md +0 -0
  152. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/agent-client.md +0 -0
  153. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/agent-harness-spec.md +0 -0
  154. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/design.md +0 -0
  155. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/harness-conformance.md +0 -0
  156. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/index.md +0 -0
  157. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/layer1-core-irc.md +0 -0
  158. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/layer2-attention.md +0 -0
  159. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/layer3-skills.md +0 -0
  160. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/layer4-federation.md +0 -0
  161. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/layer5-agent-harness.md +0 -0
  162. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/server-architecture.md +0 -0
  163. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/architecture/threads.md +0 -0
  164. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/channel-polling.md +0 -0
  165. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/acp/overview.md +0 -0
  166. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/configuration.md +0 -0
  167. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/context-management.md +0 -0
  168. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/irc-tools.md +0 -0
  169. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/overview.md +0 -0
  170. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/setup.md +0 -0
  171. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/supervisor.md +0 -0
  172. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/claude/webhooks.md +0 -0
  173. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/configuration.md +0 -0
  174. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/context-management.md +0 -0
  175. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/irc-tools.md +0 -0
  176. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/overview.md +0 -0
  177. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/setup.md +0 -0
  178. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/supervisor.md +0 -0
  179. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/codex/webhooks.md +0 -0
  180. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/configuration.md +0 -0
  181. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/context-management.md +0 -0
  182. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/irc-tools.md +0 -0
  183. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/overview.md +0 -0
  184. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/setup.md +0 -0
  185. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/supervisor.md +0 -0
  186. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/clients/copilot/webhooks.md +0 -0
  187. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/culture-cli.md +0 -0
  188. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/getting-started.md +0 -0
  189. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/index.md +0 -0
  190. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/SECURITY.md +0 -0
  191. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/bots.md +0 -0
  192. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/ci.md +0 -0
  193. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/cli.md +0 -0
  194. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/docs-site.md +0 -0
  195. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/index.md +0 -0
  196. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/ops-tooling.md +0 -0
  197. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/overview.md +0 -0
  198. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/operations/publishing.md +0 -0
  199. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  200. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/rooms.md +0 -0
  201. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/server-rename.md +0 -0
  202. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  203. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  204. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  205. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  206. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  207. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  208. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  209. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  210. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  211. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  212. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  213. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  214. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  215. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  216. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  217. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  218. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  219. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  220. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  221. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  222. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  223. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  224. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/01-pair-programming.md +0 -0
  225. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/02-code-review-ensemble.md +0 -0
  226. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/03-cross-server-delegation.md +0 -0
  227. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/04-knowledge-propagation.md +0 -0
  228. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/05-the-observer.md +0 -0
  229. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/06-cross-server-ops.md +0 -0
  230. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/07-supervisor-intervention.md +0 -0
  231. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/08-apps-as-agents.md +0 -0
  232. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/09-research-swarm.md +0 -0
  233. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases/10-agent-lifecycle.md +0 -0
  234. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/use-cases-index.md +0 -0
  235. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/docs/what-is-culture.md +0 -0
  236. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/README.md +0 -0
  237. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/config.py +0 -0
  238. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/daemon.py +0 -0
  239. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/ipc.py +0 -0
  240. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/irc_transport.py +0 -0
  241. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/message_buffer.py +0 -0
  242. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/skill/SKILL.md +0 -0
  243. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/socket_server.py +0 -0
  244. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/packages/agent-harness/webhook.py +0 -0
  245. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  246. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  247. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  248. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  249. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/sonar-project.properties +0 -0
  250. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/__init__.py +0 -0
  251. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/conftest.py +0 -0
  252. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_acp_daemon.py +0 -0
  253. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_agent_runner.py +0 -0
  254. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_archive.py +0 -0
  255. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_bot.py +0 -0
  256. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_bot_config.py +0 -0
  257. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_bot_manager.py +0 -0
  258. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_bots_integration.py +0 -0
  259. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_channel.py +0 -0
  260. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_codex_daemon.py +0 -0
  261. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_connection.py +0 -0
  262. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_console_client.py +0 -0
  263. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_console_commands.py +0 -0
  264. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_console_connection.py +0 -0
  265. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_console_icons.py +0 -0
  266. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_console_integration.py +0 -0
  267. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_copilot_daemon.py +0 -0
  268. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_daemon.py +0 -0
  269. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_daemon_config.py +0 -0
  270. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_daemon_ipc.py +0 -0
  271. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_discovery.py +0 -0
  272. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_federation.py +0 -0
  273. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_history.py +0 -0
  274. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_http_listener.py +0 -0
  275. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_integration_layer5.py +0 -0
  276. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_ipc.py +0 -0
  277. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_irc_transport.py +0 -0
  278. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_link_reconnect.py +0 -0
  279. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_mention_alias.py +0 -0
  280. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_mention_target_cleanup.py +0 -0
  281. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_mentions.py +0 -0
  282. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_mesh_config.py +0 -0
  283. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_message.py +0 -0
  284. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_message_buffer.py +0 -0
  285. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_messaging.py +0 -0
  286. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_modes.py +0 -0
  287. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_overview_cli.py +0 -0
  288. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_overview_collector.py +0 -0
  289. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_overview_model.py +0 -0
  290. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_overview_renderer.py +0 -0
  291. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_overview_web.py +0 -0
  292. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_persistence.py +0 -0
  293. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_pidfile.py +0 -0
  294. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_poll_loop.py +0 -0
  295. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_room_persistence.py +0 -0
  296. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_rooms.py +0 -0
  297. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_rooms_federation.py +0 -0
  298. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_rooms_integration.py +0 -0
  299. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_server_icon_skill.py +0 -0
  300. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_setup_update_cli.py +0 -0
  301. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_skill_client.py +0 -0
  302. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_skills.py +0 -0
  303. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_socket_server.py +0 -0
  304. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_supervisor.py +0 -0
  305. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_template_engine.py +0 -0
  306. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_thread_buffer.py +0 -0
  307. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_threads.py +0 -0
  308. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_virtual_client.py +0 -0
  309. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_wait_for_port.py +0 -0
  310. {agentirc_cli-4.3.2 → agentirc_cli-4.3.3}/tests/test_webhook.py +0 -0
@@ -4,6 +4,13 @@ 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.3.3] - 2026-04-07
8
+
9
+
10
+ ### Changed
11
+
12
+ - Reduced cognitive complexity in 40 functions across 25 files to meet SonarCloud threshold (≤15)
13
+
7
14
  ## [4.3.2] - 2026-04-07
8
15
 
9
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 4.3.2
3
+ Version: 4.3.3
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
@@ -220,6 +220,48 @@ class ACPAgentRunner:
220
220
  self._process.stdin.write(line.encode())
221
221
  await self._process.stdin.drain()
222
222
 
223
+ def _dispatch_jsonrpc_message(self, msg: dict) -> bool:
224
+ """Route a JSON-RPC response to its pending future.
225
+
226
+ Returns True if the message was a response (handled here),
227
+ False if it should be treated as a notification.
228
+ """
229
+ if "id" in msg and ("result" in msg or "error" in msg):
230
+ req_id = msg["id"]
231
+ future = self._pending.pop(req_id, None)
232
+ if future and not future.done():
233
+ future.set_result(msg)
234
+ return True
235
+ return False
236
+
237
+ async def _cleanup_process(self) -> None:
238
+ """Wait for process exit, fail pending futures, cancel companion tasks, fire on_exit."""
239
+ returncode = -1
240
+ if self._process:
241
+ try:
242
+ returncode = await asyncio.wait_for(self._process.wait(), timeout=5)
243
+ except asyncio.TimeoutError:
244
+ try:
245
+ self._process.kill()
246
+ except ProcessLookupError:
247
+ pass
248
+
249
+ # Fail any still-pending requests
250
+ for future in self._pending.values():
251
+ if not future.done():
252
+ future.set_exception(ConnectionError("Process exited"))
253
+ self._pending.clear()
254
+
255
+ self._running = False
256
+
257
+ # Cancel companion tasks so they don't outlive the process
258
+ for task in (self._task, self._stderr_task):
259
+ if task and not task.done():
260
+ task.cancel()
261
+
262
+ if not self._stopping and self.on_exit:
263
+ await self.on_exit(returncode)
264
+
223
265
  async def _read_loop(self) -> None:
224
266
  """Read JSON-RPC messages from stdout."""
225
267
  if not self._process or not self._process.stdout:
@@ -234,16 +276,9 @@ class ACPAgentRunner:
234
276
  except json.JSONDecodeError:
235
277
  continue
236
278
 
237
- # Response to a request
238
- if "id" in msg and ("result" in msg or "error" in msg):
239
- req_id = msg["id"]
240
- future = self._pending.pop(req_id, None)
241
- if future and not future.done():
242
- future.set_result(msg)
243
-
244
- # Notification from server
245
- elif "method" in msg:
246
- await self._handle_notification(msg)
279
+ if not self._dispatch_jsonrpc_message(msg):
280
+ if "method" in msg:
281
+ await self._handle_notification(msg)
247
282
 
248
283
  except asyncio.CancelledError:
249
284
  raise
@@ -252,32 +287,7 @@ class ACPAgentRunner:
252
287
  except Exception:
253
288
  logger.exception("ACP read loop error")
254
289
  finally:
255
- # Wait for process to fully exit and notify daemon for crash recovery
256
- returncode = -1
257
- if self._process:
258
- try:
259
- returncode = await asyncio.wait_for(self._process.wait(), timeout=5)
260
- except asyncio.TimeoutError:
261
- try:
262
- self._process.kill()
263
- except ProcessLookupError:
264
- pass
265
-
266
- # Fail any still-pending requests
267
- for future in self._pending.values():
268
- if not future.done():
269
- future.set_exception(ConnectionError("Process exited"))
270
- self._pending.clear()
271
-
272
- self._running = False
273
-
274
- # Cancel companion tasks so they don't outlive the process
275
- for task in (self._task, self._stderr_task):
276
- if task and not task.done():
277
- task.cancel()
278
-
279
- if not self._stopping and self.on_exit:
280
- await self.on_exit(returncode)
290
+ await self._cleanup_process()
281
291
 
282
292
  async def _stderr_loop(self) -> None:
283
293
  """Log stderr output from the ACP agent process."""
@@ -281,27 +281,33 @@ class ACPDaemon:
281
281
  while True:
282
282
  try:
283
283
  await asyncio.sleep(interval)
284
- if self._paused or not self._agent_runner or not self._agent_runner.is_running():
285
- continue
286
- for channel in self.agent.channels:
287
- msgs = self._buffer.read(channel)
288
- if not msgs:
289
- continue
290
- lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
291
- prompt = (
292
- f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
293
- f"{lines}\n\n"
294
- f"Respond naturally if any messages need your attention."
295
- )
296
- self._mention_targets.append(channel)
297
- task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
298
- self._background_tasks.add(task)
299
- task.add_done_callback(self._background_tasks.discard)
284
+ self._process_poll_cycle()
300
285
  except asyncio.CancelledError:
301
286
  raise
302
287
  except Exception:
303
288
  logger.exception("Poll loop error")
304
289
 
290
+ def _process_poll_cycle(self) -> None:
291
+ if self._paused or not self._agent_runner or not self._agent_runner.is_running():
292
+ return
293
+ for channel in self.agent.channels:
294
+ self._send_channel_poll(channel)
295
+
296
+ def _send_channel_poll(self, channel) -> None:
297
+ msgs = self._buffer.read(channel)
298
+ if not msgs:
299
+ return
300
+ lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
301
+ prompt = (
302
+ f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
303
+ f"{lines}\n\n"
304
+ f"Respond naturally if any messages need your attention."
305
+ )
306
+ self._mention_targets.append(channel)
307
+ task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
308
+ self._background_tasks.add(task)
309
+ task.add_done_callback(self._background_tasks.discard)
310
+
305
311
  async def _graceful_shutdown(self) -> None:
306
312
  """Trigger a graceful shutdown, signaling any waiting stop event."""
307
313
  logger.info("Graceful shutdown requested for %s", self.agent.nick)
@@ -83,14 +83,7 @@ class SkillClient:
83
83
  msg = decode_message(line)
84
84
  if msg is None:
85
85
  continue
86
- msg_type = msg.get("type")
87
- if msg_type == MSG_TYPE_RESPONSE:
88
- req_id = msg.get("id", "")
89
- future = self._pending.pop(req_id, None)
90
- if future is not None and not future.done():
91
- future.set_result(msg)
92
- elif msg_type == MSG_TYPE_WHISPER:
93
- self.pending_whispers.append(msg)
86
+ self._dispatch_message(msg)
94
87
  except (asyncio.IncompleteReadError, ConnectionError, OSError):
95
88
  pass
96
89
  finally:
@@ -100,6 +93,17 @@ class SkillClient:
100
93
  future.set_exception(ConnectionError("Connection lost"))
101
94
  self._pending.clear()
102
95
 
96
+ def _dispatch_message(self, msg: dict[str, Any]) -> None:
97
+ """Route a decoded message to the appropriate handler."""
98
+ msg_type = msg.get("type")
99
+ if msg_type == MSG_TYPE_RESPONSE:
100
+ req_id = msg.get("id", "")
101
+ future = self._pending.pop(req_id, None)
102
+ if future is not None and not future.done():
103
+ future.set_result(msg)
104
+ elif msg_type == MSG_TYPE_WHISPER:
105
+ self.pending_whispers.append(msg)
106
+
103
107
  # ------------------------------------------------------------------
104
108
  # Request dispatch
105
109
  # ------------------------------------------------------------------
@@ -182,6 +186,86 @@ def _sock_path_from_env() -> str:
182
186
  return os.path.join(runtime_dir, f"culture-{nick}.sock")
183
187
 
184
188
 
189
+ def _parse_ask_timeout(remaining: list[str]) -> tuple[int, list[str]]:
190
+ """Extract --timeout N from args, returning (timeout, filtered_args)."""
191
+ if "--timeout" in remaining:
192
+ idx = remaining.index("--timeout")
193
+ if idx + 1 >= len(remaining):
194
+ print("ERROR: --timeout requires a value", file=sys.stderr)
195
+ sys.exit(2)
196
+ try:
197
+ timeout = int(remaining[idx + 1])
198
+ except ValueError:
199
+ print(
200
+ f"ERROR: --timeout value must be an integer, got {remaining[idx + 1]!r}",
201
+ file=sys.stderr,
202
+ )
203
+ sys.exit(2)
204
+ if timeout <= 0:
205
+ print("ERROR: --timeout must be positive", file=sys.stderr)
206
+ sys.exit(2)
207
+ remaining = remaining[:idx] + remaining[idx + 2 :]
208
+ else:
209
+ timeout = 30
210
+ return timeout, remaining
211
+
212
+
213
+ async def _cmd_send(client: SkillClient, args: list[str]) -> dict[str, Any]:
214
+ channel = args[1]
215
+ message = " ".join(args[2:])
216
+ return await client.irc_send(channel, message)
217
+
218
+
219
+ async def _cmd_read(client: SkillClient, args: list[str]) -> dict[str, Any]:
220
+ channel = args[1]
221
+ limit = int(args[2]) if len(args) > 2 else 50
222
+ return await client.irc_read(channel, limit=limit)
223
+
224
+
225
+ async def _cmd_ask(client: SkillClient, args: list[str]) -> dict[str, Any]:
226
+ channel = args[1]
227
+ timeout, remaining = _parse_ask_timeout(args[2:])
228
+ question = " ".join(remaining)
229
+ return await client.irc_ask(channel, question, timeout=timeout)
230
+
231
+
232
+ async def _cmd_join(client: SkillClient, args: list[str]) -> dict[str, Any]:
233
+ return await client.irc_join(args[1])
234
+
235
+
236
+ async def _cmd_part(client: SkillClient, args: list[str]) -> dict[str, Any]:
237
+ return await client.irc_part(args[1])
238
+
239
+
240
+ async def _cmd_channels(client: SkillClient, args: list[str]) -> dict[str, Any]:
241
+ return await client.irc_channels()
242
+
243
+
244
+ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
245
+ return await client.irc_who(args[1])
246
+
247
+
248
+ async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
249
+ return await client.compact()
250
+
251
+
252
+ async def _cmd_clear(client: SkillClient, args: list[str]) -> dict[str, Any]:
253
+ return await client.clear()
254
+
255
+
256
+ _SUBCOMMANDS: dict[str, Any] = {
257
+ "send": _cmd_send,
258
+ "read": _cmd_read,
259
+ "ask": _cmd_ask,
260
+ "join": _cmd_join,
261
+ "part": _cmd_part,
262
+ "channels": _cmd_channels,
263
+ "who": _cmd_who,
264
+ "compact": _cmd_compact,
265
+ "clear": _cmd_clear,
266
+ }
267
+
268
+
185
269
  async def _main(args: list[str]) -> None:
186
270
  """CLI entry point. First arg is the subcommand."""
187
271
  if not args:
@@ -195,59 +279,16 @@ async def _main(args: list[str]) -> None:
195
279
  sock_path = _sock_path_from_env()
196
280
  subcommand = args[0]
197
281
 
282
+ handler = _SUBCOMMANDS.get(subcommand)
283
+ if handler is None:
284
+ print(f"ERROR: Unknown subcommand: {subcommand!r}", file=sys.stderr)
285
+ sys.exit(1)
286
+
198
287
  client = SkillClient(sock_path)
199
288
  await client.connect()
200
289
 
201
290
  try:
202
- if subcommand == "send":
203
- # send <channel> <message...>
204
- channel = args[1]
205
- message = " ".join(args[2:])
206
- result = await client.irc_send(channel, message)
207
-
208
- elif subcommand == "read":
209
- # read <channel> [limit]
210
- channel = args[1]
211
- limit = int(args[2]) if len(args) > 2 else 50
212
- result = await client.irc_read(channel, limit=limit)
213
-
214
- elif subcommand == "ask":
215
- # ask <channel> [--timeout N] <question...>
216
- channel = args[1]
217
- remaining = args[2:]
218
- if "--timeout" in remaining:
219
- idx = remaining.index("--timeout")
220
- timeout = int(remaining[idx + 1])
221
- remaining = remaining[:idx] + remaining[idx + 2 :]
222
- else:
223
- timeout = 30
224
- question = " ".join(remaining)
225
- result = await client.irc_ask(channel, question, timeout=timeout)
226
-
227
- elif subcommand == "join":
228
- channel = args[1]
229
- result = await client.irc_join(channel)
230
-
231
- elif subcommand == "part":
232
- channel = args[1]
233
- result = await client.irc_part(channel)
234
-
235
- elif subcommand == "channels":
236
- result = await client.irc_channels()
237
-
238
- elif subcommand == "who":
239
- target = args[1]
240
- result = await client.irc_who(target)
241
-
242
- elif subcommand == "compact":
243
- result = await client.compact()
244
-
245
- elif subcommand == "clear":
246
- result = await client.clear()
247
-
248
- else:
249
- print(f"ERROR: Unknown subcommand: {subcommand!r}", file=sys.stderr)
250
- sys.exit(1)
291
+ result = await handler(client, args)
251
292
 
252
293
  # Print result as JSON
253
294
  print(json.dumps(result, indent=2))
@@ -117,6 +117,13 @@ def _format_window(window: list[dict[str, Any]], task: str) -> str:
117
117
  return "\n".join(lines)
118
118
 
119
119
 
120
+ def _extract_verdict_text(response) -> str:
121
+ """Extract text from an SDK response message."""
122
+ if isinstance(response, AssistantMessage):
123
+ return "".join(block.text for block in response.content if isinstance(block, TextBlock))
124
+ return ""
125
+
126
+
120
127
  def make_sdk_evaluate_fn(
121
128
  model: str = "claude-sonnet-4-6",
122
129
  thinking: str | None = None,
@@ -137,10 +144,7 @@ def make_sdk_evaluate_fn(
137
144
  if thinking:
138
145
  opts.effort = thinking
139
146
  async for message in query(prompt=prompt, options=opts):
140
- if isinstance(message, AssistantMessage):
141
- for block in message.content:
142
- if isinstance(block, TextBlock):
143
- result_text += block.text
147
+ result_text += _extract_verdict_text(message)
144
148
  return SupervisorVerdict.parse(result_text)
145
149
 
146
150
  return evaluate
@@ -115,6 +115,18 @@ class AgentRunner:
115
115
  opts.resume = self._session_id
116
116
  return opts
117
117
 
118
+ def _handle_result_message(self, msg: ResultMessage) -> None:
119
+ """Handle a ResultMessage — track session and log errors."""
120
+ self._session_id = msg.session_id
121
+ if msg.is_error:
122
+ logger.warning("SDK session error: %s", msg.result)
123
+
124
+ async def _handle_assistant_message(self, msg: AssistantMessage) -> None:
125
+ """Handle an AssistantMessage — convert and fire callback."""
126
+ if self.on_message:
127
+ msg_dict = self._assistant_to_dict(msg)
128
+ await self.on_message(msg_dict)
129
+
118
130
  async def _run_loop(self) -> None:
119
131
  """Main session loop: run turns, process prompt queue between turns."""
120
132
  try:
@@ -132,13 +144,9 @@ class AgentRunner:
132
144
  options=self._make_options(),
133
145
  ):
134
146
  if isinstance(message, ResultMessage):
135
- self._session_id = message.session_id
136
- if message.is_error:
137
- logger.warning("SDK session error: %s", message.result)
147
+ self._handle_result_message(message)
138
148
  elif isinstance(message, AssistantMessage):
139
- if self.on_message:
140
- msg_dict = self._assistant_to_dict(message)
141
- await self.on_message(msg_dict)
149
+ await self._handle_assistant_message(message)
142
150
  except Exception:
143
151
  logger.exception("SDK session turn error")
144
152
  if not self._stopping and self.on_exit:
@@ -83,14 +83,7 @@ class SkillClient:
83
83
  msg = decode_message(line)
84
84
  if msg is None:
85
85
  continue
86
- msg_type = msg.get("type")
87
- if msg_type == MSG_TYPE_RESPONSE:
88
- req_id = msg.get("id", "")
89
- future = self._pending.pop(req_id, None)
90
- if future is not None and not future.done():
91
- future.set_result(msg)
92
- elif msg_type == MSG_TYPE_WHISPER:
93
- self.pending_whispers.append(msg)
86
+ self._dispatch_message(msg)
94
87
  except (asyncio.IncompleteReadError, ConnectionError, OSError):
95
88
  pass
96
89
  finally:
@@ -100,6 +93,17 @@ class SkillClient:
100
93
  future.set_exception(ConnectionError("Connection lost"))
101
94
  self._pending.clear()
102
95
 
96
+ def _dispatch_message(self, msg: dict[str, Any]) -> None:
97
+ """Route a decoded message to the appropriate handler."""
98
+ msg_type = msg.get("type")
99
+ if msg_type == MSG_TYPE_RESPONSE:
100
+ req_id = msg.get("id", "")
101
+ future = self._pending.pop(req_id, None)
102
+ if future is not None and not future.done():
103
+ future.set_result(msg)
104
+ elif msg_type == MSG_TYPE_WHISPER:
105
+ self.pending_whispers.append(msg)
106
+
103
107
  # ------------------------------------------------------------------
104
108
  # Request dispatch
105
109
  # ------------------------------------------------------------------
@@ -182,6 +186,86 @@ def _sock_path_from_env() -> str:
182
186
  return os.path.join(runtime_dir, f"culture-{nick}.sock")
183
187
 
184
188
 
189
+ def _parse_ask_timeout(remaining: list[str]) -> tuple[int, list[str]]:
190
+ """Extract --timeout N from args, returning (timeout, filtered_args)."""
191
+ if "--timeout" in remaining:
192
+ idx = remaining.index("--timeout")
193
+ if idx + 1 >= len(remaining):
194
+ print("ERROR: --timeout requires a value", file=sys.stderr)
195
+ sys.exit(2)
196
+ try:
197
+ timeout = int(remaining[idx + 1])
198
+ except ValueError:
199
+ print(
200
+ f"ERROR: --timeout value must be an integer, got {remaining[idx + 1]!r}",
201
+ file=sys.stderr,
202
+ )
203
+ sys.exit(2)
204
+ if timeout <= 0:
205
+ print("ERROR: --timeout must be positive", file=sys.stderr)
206
+ sys.exit(2)
207
+ remaining = remaining[:idx] + remaining[idx + 2 :]
208
+ else:
209
+ timeout = 30
210
+ return timeout, remaining
211
+
212
+
213
+ async def _cmd_send(client: SkillClient, args: list[str]) -> dict[str, Any]:
214
+ channel = args[1]
215
+ message = " ".join(args[2:])
216
+ return await client.irc_send(channel, message)
217
+
218
+
219
+ async def _cmd_read(client: SkillClient, args: list[str]) -> dict[str, Any]:
220
+ channel = args[1]
221
+ limit = int(args[2]) if len(args) > 2 else 50
222
+ return await client.irc_read(channel, limit=limit)
223
+
224
+
225
+ async def _cmd_ask(client: SkillClient, args: list[str]) -> dict[str, Any]:
226
+ channel = args[1]
227
+ timeout, remaining = _parse_ask_timeout(args[2:])
228
+ question = " ".join(remaining)
229
+ return await client.irc_ask(channel, question, timeout=timeout)
230
+
231
+
232
+ async def _cmd_join(client: SkillClient, args: list[str]) -> dict[str, Any]:
233
+ return await client.irc_join(args[1])
234
+
235
+
236
+ async def _cmd_part(client: SkillClient, args: list[str]) -> dict[str, Any]:
237
+ return await client.irc_part(args[1])
238
+
239
+
240
+ async def _cmd_channels(client: SkillClient, args: list[str]) -> dict[str, Any]:
241
+ return await client.irc_channels()
242
+
243
+
244
+ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
245
+ return await client.irc_who(args[1])
246
+
247
+
248
+ async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
249
+ return await client.compact()
250
+
251
+
252
+ async def _cmd_clear(client: SkillClient, args: list[str]) -> dict[str, Any]:
253
+ return await client.clear()
254
+
255
+
256
+ _SUBCOMMANDS: dict[str, Any] = {
257
+ "send": _cmd_send,
258
+ "read": _cmd_read,
259
+ "ask": _cmd_ask,
260
+ "join": _cmd_join,
261
+ "part": _cmd_part,
262
+ "channels": _cmd_channels,
263
+ "who": _cmd_who,
264
+ "compact": _cmd_compact,
265
+ "clear": _cmd_clear,
266
+ }
267
+
268
+
185
269
  async def _main(args: list[str]) -> None:
186
270
  """CLI entry point. First arg is the subcommand."""
187
271
  if not args:
@@ -195,59 +279,16 @@ async def _main(args: list[str]) -> None:
195
279
  sock_path = _sock_path_from_env()
196
280
  subcommand = args[0]
197
281
 
282
+ handler = _SUBCOMMANDS.get(subcommand)
283
+ if handler is None:
284
+ print(f"ERROR: Unknown subcommand: {subcommand!r}", file=sys.stderr)
285
+ sys.exit(1)
286
+
198
287
  client = SkillClient(sock_path)
199
288
  await client.connect()
200
289
 
201
290
  try:
202
- if subcommand == "send":
203
- # send <channel> <message...>
204
- channel = args[1]
205
- message = " ".join(args[2:])
206
- result = await client.irc_send(channel, message)
207
-
208
- elif subcommand == "read":
209
- # read <channel> [limit]
210
- channel = args[1]
211
- limit = int(args[2]) if len(args) > 2 else 50
212
- result = await client.irc_read(channel, limit=limit)
213
-
214
- elif subcommand == "ask":
215
- # ask <channel> [--timeout N] <question...>
216
- channel = args[1]
217
- remaining = args[2:]
218
- if "--timeout" in remaining:
219
- idx = remaining.index("--timeout")
220
- timeout = int(remaining[idx + 1])
221
- remaining = remaining[:idx] + remaining[idx + 2 :]
222
- else:
223
- timeout = 30
224
- question = " ".join(remaining)
225
- result = await client.irc_ask(channel, question, timeout=timeout)
226
-
227
- elif subcommand == "join":
228
- channel = args[1]
229
- result = await client.irc_join(channel)
230
-
231
- elif subcommand == "part":
232
- channel = args[1]
233
- result = await client.irc_part(channel)
234
-
235
- elif subcommand == "channels":
236
- result = await client.irc_channels()
237
-
238
- elif subcommand == "who":
239
- target = args[1]
240
- result = await client.irc_who(target)
241
-
242
- elif subcommand == "compact":
243
- result = await client.compact()
244
-
245
- elif subcommand == "clear":
246
- result = await client.clear()
247
-
248
- else:
249
- print(f"ERROR: Unknown subcommand: {subcommand!r}", file=sys.stderr)
250
- sys.exit(1)
291
+ result = await handler(client, args)
251
292
 
252
293
  # Print result as JSON
253
294
  print(json.dumps(result, indent=2))
@@ -117,6 +117,13 @@ def _format_window(window: list[dict[str, Any]], task: str) -> str:
117
117
  return "\n".join(lines)
118
118
 
119
119
 
120
+ def _extract_verdict_text(response) -> str:
121
+ """Extract text from an SDK response message."""
122
+ if isinstance(response, AssistantMessage):
123
+ return "".join(block.text for block in response.content if isinstance(block, TextBlock))
124
+ return ""
125
+
126
+
120
127
  def make_sdk_evaluate_fn(
121
128
  model: str = "claude-sonnet-4-6",
122
129
  thinking: str | None = None,
@@ -137,10 +144,7 @@ def make_sdk_evaluate_fn(
137
144
  if thinking:
138
145
  opts.effort = thinking
139
146
  async for message in query(prompt=prompt, options=opts):
140
- if isinstance(message, AssistantMessage):
141
- for block in message.content:
142
- if isinstance(block, TextBlock):
143
- result_text += block.text
147
+ result_text += _extract_verdict_text(message)
144
148
  return SupervisorVerdict.parse(result_text)
145
149
 
146
150
  return evaluate