agentirc-cli 4.1.1__tar.gz → 4.1.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 (308) hide show
  1. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/CHANGELOG.md +14 -0
  2. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/PKG-INFO +1 -1
  3. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/mesh.py +64 -11
  4. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/agent_runner.py +4 -0
  5. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/daemon.py +6 -0
  6. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/agent_runner.py +4 -0
  7. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/daemon.py +6 -0
  8. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/agent_runner.py +4 -0
  9. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/daemon.py +6 -0
  10. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/mesh_config.py +9 -0
  11. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/cli.md +5 -2
  12. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/daemon.py +9 -0
  13. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/pyproject.toml +1 -1
  14. agentirc_cli-4.1.3/tests/test_mention_target_cleanup.py +135 -0
  15. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mesh_config.py +37 -0
  16. agentirc_cli-4.1.3/tests/test_setup_update_cli.py +185 -0
  17. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/uv.lock +1 -1
  18. agentirc_cli-4.1.1/tests/test_setup_update_cli.py +0 -45
  19. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/pr-review/SKILL.md +0 -0
  20. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/run-tests/SKILL.md +0 -0
  21. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/run-tests/scripts/test.sh +0 -0
  22. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.flake8 +0 -0
  23. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/pages.yml +0 -0
  24. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/publish.yml +0 -0
  25. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/security-checks.yml +0 -0
  26. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/tests.yml +0 -0
  27. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.gitignore +0 -0
  28. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.markdownlint-cli2.yaml +0 -0
  29. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pr_agent.toml +0 -0
  30. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pre-commit-config.yaml +0 -0
  31. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pylintrc +0 -0
  32. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/CLAUDE.md +0 -0
  33. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/CNAME +0 -0
  34. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/Gemfile +0 -0
  35. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/Gemfile.lock +0 -0
  36. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/LICENSE +0 -0
  37. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/README.md +0 -0
  38. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/SECURITY.md +0 -0
  39. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_config.yml +0 -0
  40. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_sass/color_schemes/anthropic.scss +0 -0
  41. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_sass/custom/custom.scss +0 -0
  42. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/__init__.py +0 -0
  43. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/__main__.py +0 -0
  44. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/__init__.py +0 -0
  45. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/bot.py +0 -0
  46. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/bot_manager.py +0 -0
  47. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/config.py +0 -0
  48. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/http_listener.py +0 -0
  49. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/template_engine.py +0 -0
  50. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/virtual_client.py +0 -0
  51. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/__init__.py +0 -0
  52. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/_helpers.py +0 -0
  53. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/agent.py +0 -0
  54. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/bot.py +0 -0
  55. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/channel.py +0 -0
  56. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/server.py +0 -0
  57. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/skills.py +0 -0
  58. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/__init__.py +0 -0
  59. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/__init__.py +0 -0
  60. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/config.py +0 -0
  61. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/ipc.py +0 -0
  62. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/irc_transport.py +0 -0
  63. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/message_buffer.py +0 -0
  64. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/SKILL.md +0 -0
  65. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/__init__.py +0 -0
  66. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/irc_client.py +0 -0
  67. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/socket_server.py +0 -0
  68. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/supervisor.py +0 -0
  69. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/webhook.py +0 -0
  70. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/__init__.py +0 -0
  71. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/__main__.py +0 -0
  72. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/agent_runner.py +0 -0
  73. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/config.py +0 -0
  74. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/daemon.py +0 -0
  75. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/ipc.py +0 -0
  76. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/irc_transport.py +0 -0
  77. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/message_buffer.py +0 -0
  78. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/SKILL.md +0 -0
  79. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/__init__.py +0 -0
  80. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/irc_client.py +0 -0
  81. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/socket_server.py +0 -0
  82. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/supervisor.py +0 -0
  83. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/webhook.py +0 -0
  84. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/__init__.py +0 -0
  85. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/config.py +0 -0
  86. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/ipc.py +0 -0
  87. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/irc_transport.py +0 -0
  88. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/message_buffer.py +0 -0
  89. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/SKILL.md +0 -0
  90. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/__init__.py +0 -0
  91. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/irc_client.py +0 -0
  92. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/socket_server.py +0 -0
  93. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/supervisor.py +0 -0
  94. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/webhook.py +0 -0
  95. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/__init__.py +0 -0
  96. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/config.py +0 -0
  97. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/ipc.py +0 -0
  98. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/irc_transport.py +0 -0
  99. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/message_buffer.py +0 -0
  100. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/SKILL.md +0 -0
  101. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/__init__.py +0 -0
  102. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/irc_client.py +0 -0
  103. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/socket_server.py +0 -0
  104. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/supervisor.py +0 -0
  105. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/webhook.py +0 -0
  106. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/__init__.py +0 -0
  107. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/app.py +0 -0
  108. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/client.py +0 -0
  109. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/commands.py +0 -0
  110. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/__init__.py +0 -0
  111. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/chat.py +0 -0
  112. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/info_panel.py +0 -0
  113. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/sidebar.py +0 -0
  114. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/credentials.py +0 -0
  115. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/learn_prompt.py +0 -0
  116. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/observer.py +0 -0
  117. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/__init__.py +0 -0
  118. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/collector.py +0 -0
  119. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/model.py +0 -0
  120. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/renderer_text.py +0 -0
  121. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/renderer_web.py +0 -0
  122. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/web/style.css +0 -0
  123. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/persistence.py +0 -0
  124. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/pidfile.py +0 -0
  125. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/__init__.py +0 -0
  126. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/commands.py +0 -0
  127. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/federation.md +0 -0
  128. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/history.md +0 -0
  129. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/icons.md +0 -0
  130. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/rooms.md +0 -0
  131. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/tags.md +0 -0
  132. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/threads.md +0 -0
  133. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/message.py +0 -0
  134. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/protocol-index.md +0 -0
  135. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/replies.py +0 -0
  136. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/__init__.py +0 -0
  137. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/__main__.py +0 -0
  138. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/channel.py +0 -0
  139. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/client.py +0 -0
  140. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/config.py +0 -0
  141. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/ircd.py +0 -0
  142. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/remote_client.py +0 -0
  143. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/room_store.py +0 -0
  144. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/rooms_util.py +0 -0
  145. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/server_link.py +0 -0
  146. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skill.py +0 -0
  147. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/__init__.py +0 -0
  148. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/history.py +0 -0
  149. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/icon.py +0 -0
  150. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/rooms.py +0 -0
  151. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/threads.py +0 -0
  152. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/thread_store.py +0 -0
  153. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/skills/culture/SKILL.md +0 -0
  154. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/agent-lifecycle.md +0 -0
  155. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/agentic-self-learn.md +0 -0
  156. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/agent-client.md +0 -0
  157. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/agent-harness-spec.md +0 -0
  158. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/design.md +0 -0
  159. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/harness-conformance.md +0 -0
  160. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/index.md +0 -0
  161. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer1-core-irc.md +0 -0
  162. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer2-attention.md +0 -0
  163. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer3-skills.md +0 -0
  164. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer4-federation.md +0 -0
  165. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer5-agent-harness.md +0 -0
  166. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/server-architecture.md +0 -0
  167. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/threads.md +0 -0
  168. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/channel-polling.md +0 -0
  169. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/acp/overview.md +0 -0
  170. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/configuration.md +0 -0
  171. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/context-management.md +0 -0
  172. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/irc-tools.md +0 -0
  173. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/overview.md +0 -0
  174. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/setup.md +0 -0
  175. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/supervisor.md +0 -0
  176. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/webhooks.md +0 -0
  177. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/configuration.md +0 -0
  178. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/context-management.md +0 -0
  179. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/irc-tools.md +0 -0
  180. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/overview.md +0 -0
  181. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/setup.md +0 -0
  182. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/supervisor.md +0 -0
  183. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/webhooks.md +0 -0
  184. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/configuration.md +0 -0
  185. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/context-management.md +0 -0
  186. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/irc-tools.md +0 -0
  187. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/overview.md +0 -0
  188. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/setup.md +0 -0
  189. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/supervisor.md +0 -0
  190. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/webhooks.md +0 -0
  191. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/culture-cli.md +0 -0
  192. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/getting-started.md +0 -0
  193. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/index.md +0 -0
  194. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/SECURITY.md +0 -0
  195. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/bots.md +0 -0
  196. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/ci.md +0 -0
  197. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/docs-site.md +0 -0
  198. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/index.md +0 -0
  199. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/ops-tooling.md +0 -0
  200. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/overview.md +0 -0
  201. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/publishing.md +0 -0
  202. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  203. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/rooms.md +0 -0
  204. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/server-rename.md +0 -0
  205. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  206. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  207. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  208. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  209. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  210. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  211. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  212. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  213. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  214. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  215. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  216. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  217. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  218. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  219. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  220. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  221. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  222. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  223. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  224. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  225. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  226. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/01-pair-programming.md +0 -0
  227. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/02-code-review-ensemble.md +0 -0
  228. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/03-cross-server-delegation.md +0 -0
  229. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/04-knowledge-propagation.md +0 -0
  230. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/05-the-observer.md +0 -0
  231. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/06-cross-server-ops.md +0 -0
  232. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/07-supervisor-intervention.md +0 -0
  233. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/08-apps-as-agents.md +0 -0
  234. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/09-research-swarm.md +0 -0
  235. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/10-agent-lifecycle.md +0 -0
  236. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases-index.md +0 -0
  237. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/what-is-culture.md +0 -0
  238. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/README.md +0 -0
  239. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/config.py +0 -0
  240. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/ipc.py +0 -0
  241. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/irc_transport.py +0 -0
  242. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/message_buffer.py +0 -0
  243. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/skill/SKILL.md +0 -0
  244. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/skill/irc_client.py +0 -0
  245. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/socket_server.py +0 -0
  246. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/webhook.py +0 -0
  247. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  248. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  249. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  250. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  251. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/sonar-project.properties +0 -0
  252. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/__init__.py +0 -0
  253. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/conftest.py +0 -0
  254. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_acp_daemon.py +0 -0
  255. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_agent_runner.py +0 -0
  256. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot.py +0 -0
  257. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot_config.py +0 -0
  258. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot_manager.py +0 -0
  259. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bots_integration.py +0 -0
  260. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_channel.py +0 -0
  261. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_codex_daemon.py +0 -0
  262. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_connection.py +0 -0
  263. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_client.py +0 -0
  264. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_commands.py +0 -0
  265. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_connection.py +0 -0
  266. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_icons.py +0 -0
  267. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_integration.py +0 -0
  268. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_copilot_daemon.py +0 -0
  269. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon.py +0 -0
  270. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon_config.py +0 -0
  271. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon_ipc.py +0 -0
  272. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_discovery.py +0 -0
  273. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_federation.py +0 -0
  274. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_history.py +0 -0
  275. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_http_listener.py +0 -0
  276. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_integration_layer5.py +0 -0
  277. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_ipc.py +0 -0
  278. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_irc_transport.py +0 -0
  279. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_link_reconnect.py +0 -0
  280. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mention_alias.py +0 -0
  281. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mentions.py +0 -0
  282. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_message.py +0 -0
  283. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_message_buffer.py +0 -0
  284. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_messaging.py +0 -0
  285. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_modes.py +0 -0
  286. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_cli.py +0 -0
  287. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_collector.py +0 -0
  288. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_model.py +0 -0
  289. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_renderer.py +0 -0
  290. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_web.py +0 -0
  291. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_persistence.py +0 -0
  292. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_pidfile.py +0 -0
  293. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_poll_loop.py +0 -0
  294. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_room_persistence.py +0 -0
  295. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms.py +0 -0
  296. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms_federation.py +0 -0
  297. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms_integration.py +0 -0
  298. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_server_icon_skill.py +0 -0
  299. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_skill_client.py +0 -0
  300. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_skills.py +0 -0
  301. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_socket_server.py +0 -0
  302. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_supervisor.py +0 -0
  303. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_template_engine.py +0 -0
  304. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_thread_buffer.py +0 -0
  305. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_threads.py +0 -0
  306. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_virtual_client.py +0 -0
  307. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_wait_for_port.py +0 -0
  308. {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_webhook.py +0 -0
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [4.1.3] - 2026-04-06
8
+
9
+
10
+ ### Fixed
11
+
12
+ - mesh update now discovers and restarts all running servers instead of only the one in mesh.yaml
13
+
14
+ ## [4.1.2] - 2026-04-06
15
+
16
+
17
+ ### Fixed
18
+
19
+ - Clean up _mention_targets deque on prompt failure to prevent misrouted responses
20
+
7
21
  ## [4.1.1] - 2026-04-06
8
22
 
9
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 4.1.1
3
+ Version: 4.1.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
@@ -549,23 +549,76 @@ def _restart_mesh_services(
549
549
  check=False,
550
550
  )
551
551
 
552
- print("\nUpdate complete. All services restarted.")
553
-
554
-
555
- def _cmd_update(args: argparse.Namespace) -> None:
556
- from culture.mesh_config import load_mesh_config
552
+ print()
553
+
554
+
555
+ def _resolve_mesh_for_server(server_name: str, config_path: str):
556
+ """Find or build a MeshConfig for *server_name*.
557
+
558
+ Resolution order:
559
+ 1. mesh.yaml — use directly if its server.name matches.
560
+ 2. agents.yaml — build via from_daemon_config(), preserving host, port,
561
+ and links from the old mesh.yaml. Saves the updated mesh.yaml so
562
+ future runs are consistent.
563
+ """
564
+ from culture.mesh_config import (
565
+ from_daemon_config,
566
+ load_mesh_config,
567
+ merge_links,
568
+ save_mesh_config,
569
+ )
557
570
 
571
+ old_server = None
558
572
  try:
559
- mesh = load_mesh_config(args.config)
573
+ old_mesh = load_mesh_config(config_path)
574
+ if old_mesh.server.name == server_name:
575
+ return old_mesh
576
+ old_server = old_mesh.server
560
577
  except FileNotFoundError:
561
- mesh = generate_mesh_from_agents(args.config)
562
- if mesh is None:
563
- sys.exit(1)
578
+ pass
579
+
580
+ if os.path.isfile(DEFAULT_CONFIG):
581
+ daemon_config = load_config(DEFAULT_CONFIG)
582
+ if daemon_config.server.name == server_name:
583
+ mesh = from_daemon_config(daemon_config)
584
+ if old_server is not None:
585
+ mesh.server.host = old_server.host
586
+ mesh.server.port = old_server.port
587
+ merge_links(mesh, old_server.links)
588
+ save_mesh_config(mesh, config_path)
589
+ return mesh
590
+
591
+ return None
564
592
 
565
- server_name = mesh.server.name
593
+
594
+ def _cmd_update(args: argparse.Namespace) -> None:
595
+ from culture.mesh_config import load_mesh_config
596
+ from culture.pidfile import list_servers
566
597
 
567
598
  if not _upgrade_culture_package(args):
568
599
  return
569
600
 
570
601
  culture_bin = shutil.which("culture") or "culture"
571
- _restart_mesh_services(mesh, server_name, culture_bin, args.config, args.dry_run)
602
+
603
+ running = list_servers()
604
+
605
+ if running:
606
+ for srv in running:
607
+ mesh = _resolve_mesh_for_server(srv["name"], args.config)
608
+ if mesh is None:
609
+ print(
610
+ f" Warning: no config found for server '{srv['name']}', skipping",
611
+ file=sys.stderr,
612
+ )
613
+ continue
614
+ _restart_mesh_services(mesh, srv["name"], culture_bin, args.config, args.dry_run)
615
+ else:
616
+ try:
617
+ mesh = load_mesh_config(args.config)
618
+ except FileNotFoundError:
619
+ mesh = generate_mesh_from_agents(args.config)
620
+ if mesh is None:
621
+ sys.exit(1)
622
+ _restart_mesh_services(mesh, mesh.server.name, culture_bin, args.config, args.dry_run)
623
+
624
+ print("Update complete. All services restarted.")
@@ -33,6 +33,7 @@ class ACPAgentRunner:
33
33
  system_prompt: str = "",
34
34
  on_exit: Callable[[int], Awaitable[None]] | None = None,
35
35
  on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
36
+ on_turn_error: Callable[[], Awaitable[None]] | None = None,
36
37
  ) -> None:
37
38
  self.model = model
38
39
  self.directory = directory
@@ -40,6 +41,7 @@ class ACPAgentRunner:
40
41
  self.system_prompt = system_prompt
41
42
  self.on_exit = on_exit
42
43
  self.on_message = on_message
44
+ self.on_turn_error = on_turn_error
43
45
 
44
46
  self._isolated_home: str | None = None
45
47
  self._process: asyncio.subprocess.Process | None = None
@@ -384,6 +386,8 @@ class ACPAgentRunner:
384
386
 
385
387
  except Exception:
386
388
  logger.exception("ACP turn error")
389
+ if self.on_turn_error:
390
+ await self.on_turn_error()
387
391
  finally:
388
392
  self._busy = False
389
393
 
@@ -318,6 +318,11 @@ class ACPDaemon:
318
318
  # Agent runner helpers
319
319
  # ------------------------------------------------------------------
320
320
 
321
+ async def _on_turn_error(self) -> None:
322
+ """Clean up stale relay target when a prompt fails."""
323
+ if self._mention_targets:
324
+ self._mention_targets.popleft()
325
+
321
326
  async def _start_agent_runner(self) -> None:
322
327
  self._agent_runner = ACPAgentRunner(
323
328
  model=self.agent.model,
@@ -326,6 +331,7 @@ class ACPDaemon:
326
331
  system_prompt=self._build_system_prompt(),
327
332
  on_exit=self._on_agent_exit,
328
333
  on_message=self._on_agent_message,
334
+ on_turn_error=self._on_turn_error,
329
335
  )
330
336
  # Absorb the system prompt response without relaying to IRC
331
337
  self._mention_targets.append(None)
@@ -23,12 +23,14 @@ class CodexAgentRunner:
23
23
  system_prompt: str = "",
24
24
  on_exit: Callable[[int], Awaitable[None]] | None = None,
25
25
  on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
26
+ on_turn_error: Callable[[], Awaitable[None]] | None = None,
26
27
  ) -> None:
27
28
  self.model = model
28
29
  self.directory = directory
29
30
  self.system_prompt = system_prompt
30
31
  self.on_exit = on_exit
31
32
  self.on_message = on_message
33
+ self.on_turn_error = on_turn_error
32
34
 
33
35
  self._isolated_home: str | None = None
34
36
  self._process: asyncio.subprocess.Process | None = None
@@ -318,6 +320,8 @@ class CodexAgentRunner:
318
320
 
319
321
  except Exception:
320
322
  logger.exception("Codex turn error")
323
+ if self.on_turn_error:
324
+ await self.on_turn_error()
321
325
 
322
326
  except asyncio.CancelledError:
323
327
  raise
@@ -293,6 +293,11 @@ class CodexDaemon:
293
293
  # Agent runner helpers
294
294
  # ------------------------------------------------------------------
295
295
 
296
+ async def _on_turn_error(self) -> None:
297
+ """Clean up stale relay target when a prompt fails."""
298
+ if self._mention_targets:
299
+ self._mention_targets.popleft()
300
+
296
301
  async def _start_agent_runner(self) -> None:
297
302
  self._agent_runner = CodexAgentRunner(
298
303
  model=self.agent.model,
@@ -300,6 +305,7 @@ class CodexDaemon:
300
305
  system_prompt=self._build_system_prompt(),
301
306
  on_exit=self._on_agent_exit,
302
307
  on_message=self._on_agent_message,
308
+ on_turn_error=self._on_turn_error,
303
309
  )
304
310
  await self._agent_runner.start()
305
311
  logger.info("CodexAgentRunner started for %s", self.agent.nick)
@@ -23,6 +23,7 @@ class CopilotAgentRunner:
23
23
  skill_directories: list[str] | None = None,
24
24
  on_exit: Callable[[int], Awaitable[None]] | None = None,
25
25
  on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
26
+ on_turn_error: Callable[[], Awaitable[None]] | None = None,
26
27
  ) -> None:
27
28
  self.model = model
28
29
  self.directory = directory
@@ -30,6 +31,7 @@ class CopilotAgentRunner:
30
31
  self.skill_directories = skill_directories or []
31
32
  self.on_exit = on_exit
32
33
  self.on_message = on_message
34
+ self.on_turn_error = on_turn_error
33
35
 
34
36
  self._isolated_home: str | None = None
35
37
  self._client: Any = None
@@ -158,6 +160,8 @@ class CopilotAgentRunner:
158
160
 
159
161
  except Exception:
160
162
  logger.exception("Copilot session turn error")
163
+ if self.on_turn_error:
164
+ await self.on_turn_error()
161
165
  if not self._stopping:
162
166
  self._running = False
163
167
  if self.on_exit:
@@ -293,6 +293,11 @@ class CopilotDaemon:
293
293
  # Agent runner helpers
294
294
  # ------------------------------------------------------------------
295
295
 
296
+ async def _on_turn_error(self) -> None:
297
+ """Clean up stale relay target when a prompt fails."""
298
+ if self._mention_targets:
299
+ self._mention_targets.popleft()
300
+
296
301
  async def _start_agent_runner(self) -> None:
297
302
  # Resolve installed skill path for the Copilot session
298
303
  skill_dirs: list[str] = []
@@ -307,6 +312,7 @@ class CopilotDaemon:
307
312
  skill_directories=skill_dirs,
308
313
  on_exit=self._on_agent_exit,
309
314
  on_message=self._on_agent_message,
315
+ on_turn_error=self._on_turn_error,
310
316
  )
311
317
  await self._agent_runner.start()
312
318
  logger.info("CopilotAgentRunner started for %s", self.agent.nick)
@@ -108,6 +108,15 @@ def from_daemon_config(daemon_config: DaemonConfig) -> MeshConfig:
108
108
  return MeshConfig(server=server, agents=agents)
109
109
 
110
110
 
111
+ def merge_links(target: MeshConfig, source_links: list[MeshLinkConfig]) -> None:
112
+ """Copy link configs from *source_links* into *target* if not already present."""
113
+ existing = {link.name for link in target.server.links}
114
+ for link in source_links:
115
+ if link.name not in existing:
116
+ target.server.links.append(link)
117
+ existing.add(link.name)
118
+
119
+
111
120
  def save_mesh_config(config: MeshConfig, path: str | Path = DEFAULT_MESH_PATH) -> None:
112
121
  """Serialize mesh config to YAML and write atomically."""
113
122
  path = Path(path)
@@ -288,8 +288,11 @@ walkthrough.
288
288
 
289
289
  ### `culture update`
290
290
 
291
- Upgrade the `culture` package and restart all mesh services defined in
292
- `mesh.yaml`.
291
+ Upgrade the `culture` package and restart all running servers. The command
292
+ discovers running servers from PID files rather than relying solely on
293
+ `mesh.yaml`, so it restarts every server on the machine even if `mesh.yaml`
294
+ is stale or names a different server. When no servers are running, it falls
295
+ back to `mesh.yaml`.
293
296
 
294
297
  ```bash
295
298
  culture update # upgrade package + restart everything
@@ -258,6 +258,15 @@ class AgentDaemon:
258
258
  except Exception:
259
259
  logger.exception("Poll loop error")
260
260
 
261
+ async def _on_turn_error(self) -> None:
262
+ """Clean up stale relay target when a prompt fails.
263
+
264
+ Wire this as the ``on_turn_error`` callback on your agent runner so
265
+ the ``_mention_targets`` deque stays in sync with the prompt queue.
266
+ """
267
+ if self._mention_targets:
268
+ self._mention_targets.popleft()
269
+
261
270
  def _on_mention(self, target: str, sender: str, text: str) -> None:
262
271
  """Called when the agent is @mentioned. Sends prompt to runner.
263
272
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentirc-cli"
3
- version = "4.1.1"
3
+ version = "4.1.3"
4
4
  description = "Legacy alias for culture — install culture instead"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,135 @@
1
+ """Tests for _mention_targets deque cleanup on prompt failure.
2
+
3
+ When a prompt fails (timeout, error), the corresponding _mention_targets
4
+ entry must be cleaned up so that future responses route correctly.
5
+ """
6
+
7
+ import asyncio
8
+ import tempfile
9
+ from unittest.mock import AsyncMock, MagicMock
10
+
11
+ import pytest
12
+
13
+ from culture.clients.acp.config import (
14
+ AgentConfig,
15
+ DaemonConfig,
16
+ ServerConnConfig,
17
+ )
18
+ from culture.clients.acp.daemon import ACPDaemon
19
+
20
+
21
+ def _make_daemon(server_port: int) -> ACPDaemon:
22
+ config = DaemonConfig(
23
+ server=ServerConnConfig(host="127.0.0.1", port=server_port),
24
+ poll_interval=0,
25
+ )
26
+ agent = AgentConfig(
27
+ nick="testserv-bot",
28
+ directory="/tmp",
29
+ channels=["#general"],
30
+ acp_command=["echo"],
31
+ )
32
+ sock_dir = tempfile.mkdtemp()
33
+ return ACPDaemon(config, agent, socket_dir=sock_dir, skip_agent=True)
34
+
35
+
36
+ def _inject_fake_runner(daemon):
37
+ """Inject a fake agent runner that records prompts."""
38
+ runner = MagicMock()
39
+ runner.is_running.return_value = True
40
+ runner.send_prompt = AsyncMock()
41
+ runner.stop = AsyncMock()
42
+ daemon._agent_runner = runner
43
+ return runner
44
+
45
+
46
+ @pytest.mark.asyncio
47
+ async def test_on_turn_error_pops_stale_target(server):
48
+ """_on_turn_error should pop the front entry from _mention_targets."""
49
+ daemon = _make_daemon(server.config.port)
50
+ await daemon.start()
51
+
52
+ # Simulate: poll enqueued a target, then the prompt failed
53
+ daemon._mention_targets.append("#general")
54
+ daemon._mention_targets.append("#code-review")
55
+ assert len(daemon._mention_targets) == 2
56
+
57
+ await daemon._on_turn_error()
58
+ assert len(daemon._mention_targets) == 1
59
+ assert daemon._mention_targets[0] == "#code-review"
60
+
61
+ await daemon.stop()
62
+
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_on_turn_error_empty_deque_is_safe(server):
66
+ """_on_turn_error should be a no-op on an empty deque."""
67
+ daemon = _make_daemon(server.config.port)
68
+ await daemon.start()
69
+
70
+ assert len(daemon._mention_targets) == 0
71
+ await daemon._on_turn_error() # should not raise
72
+ assert len(daemon._mention_targets) == 0
73
+
74
+ await daemon.stop()
75
+
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_relay_routes_correctly_after_error_cleanup(server, make_client):
79
+ """After a failed prompt is cleaned up, the next response should route correctly."""
80
+ daemon = _make_daemon(server.config.port)
81
+ await daemon.start()
82
+ _inject_fake_runner(daemon)
83
+ await asyncio.sleep(0.5)
84
+
85
+ # Simulate: system prompt enqueued None, then timed out and was cleaned up
86
+ daemon._mention_targets.append(None)
87
+ await daemon._on_turn_error() # cleans up None
88
+
89
+ # Now simulate a real mention that succeeds
90
+ daemon._mention_targets.append("#general")
91
+
92
+ # Simulate agent response
93
+ sent_messages = []
94
+ original_send = daemon._transport.send_privmsg
95
+
96
+ async def capture_send(target, text):
97
+ sent_messages.append((target, text))
98
+ await original_send(target, text)
99
+
100
+ daemon._transport.send_privmsg = capture_send
101
+
102
+ msg = {
103
+ "type": "assistant",
104
+ "content": [{"type": "text", "text": "Hello from bot!"}],
105
+ }
106
+ await daemon._relay_response_to_irc(msg)
107
+
108
+ assert len(sent_messages) == 1
109
+ assert sent_messages[0][0] == "#general"
110
+ assert "Hello from bot!" in sent_messages[0][1]
111
+ assert len(daemon._mention_targets) == 0
112
+
113
+ await daemon.stop()
114
+
115
+
116
+ @pytest.mark.asyncio
117
+ async def test_multiple_errors_drain_deque_correctly(server):
118
+ """Multiple consecutive errors should each pop one entry."""
119
+ daemon = _make_daemon(server.config.port)
120
+ await daemon.start()
121
+
122
+ # Simulate 3 failed prompts
123
+ daemon._mention_targets.append(None) # system prompt
124
+ daemon._mention_targets.append("#general") # poll 1
125
+ daemon._mention_targets.append("#general") # poll 2
126
+
127
+ await daemon._on_turn_error() # pops None
128
+ await daemon._on_turn_error() # pops #general
129
+ assert len(daemon._mention_targets) == 1
130
+ assert daemon._mention_targets[0] == "#general"
131
+
132
+ await daemon._on_turn_error() # pops last #general
133
+ assert len(daemon._mention_targets) == 0
134
+
135
+ await daemon.stop()
@@ -11,6 +11,7 @@ from culture.mesh_config import (
11
11
  MeshServerConfig,
12
12
  from_daemon_config,
13
13
  load_mesh_config,
14
+ merge_links,
14
15
  save_mesh_config,
15
16
  )
16
17
 
@@ -153,3 +154,39 @@ def test_from_daemon_config_empty_agents():
153
154
  assert mesh.server.name == "test"
154
155
  assert mesh.server.port == 7000
155
156
  assert mesh.agents == []
157
+
158
+
159
+ def test_merge_links_appends_missing():
160
+ """merge_links adds links not already present."""
161
+ target = MeshConfig(
162
+ server=MeshServerConfig(
163
+ name="spark",
164
+ links=[MeshLinkConfig(name="thor", host="1.2.3.4", port=6667)],
165
+ ),
166
+ )
167
+ source = [
168
+ MeshLinkConfig(name="orin", host="5.6.7.8", port=6668),
169
+ ]
170
+ merge_links(target, source)
171
+ assert len(target.server.links) == 2
172
+ assert target.server.links[1].name == "orin"
173
+
174
+
175
+ def test_merge_links_skips_duplicates():
176
+ """merge_links does not duplicate links already present."""
177
+ target = MeshConfig(
178
+ server=MeshServerConfig(
179
+ name="spark",
180
+ links=[MeshLinkConfig(name="thor", host="1.2.3.4", port=6667)],
181
+ ),
182
+ )
183
+ source = [
184
+ MeshLinkConfig(name="thor", host="9.9.9.9", port=9999),
185
+ MeshLinkConfig(name="orin", host="5.6.7.8", port=6668),
186
+ ]
187
+ merge_links(target, source)
188
+ assert len(target.server.links) == 2
189
+ names = [l.name for l in target.server.links]
190
+ assert names == ["thor", "orin"]
191
+ # Original thor link is unchanged
192
+ assert target.server.links[0].host == "1.2.3.4"
@@ -0,0 +1,185 @@
1
+ # tests/test_setup_update_cli.py
2
+ """Lightweight parser tests for setup and update subcommands."""
3
+
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+
8
+ from culture.cli import _build_parser
9
+ from culture.mesh_config import MeshAgentConfig, MeshConfig, MeshLinkConfig, MeshServerConfig
10
+
11
+
12
+ def test_setup_parser():
13
+ """setup subcommand parses --config and --uninstall."""
14
+ p = _build_parser()
15
+ args = p.parse_args(["mesh", "setup", "--uninstall"])
16
+ assert args.command == "mesh"
17
+ assert args.mesh_command == "setup"
18
+ assert args.uninstall is True
19
+
20
+ args = p.parse_args(["mesh", "setup", "--config", "/tmp/mesh.yaml"])
21
+ assert args.config == "/tmp/mesh.yaml"
22
+
23
+
24
+ def test_update_parser():
25
+ """update subcommand parses --dry-run, --skip-upgrade, --config."""
26
+ p = _build_parser()
27
+ args = p.parse_args(["mesh", "update", "--dry-run", "--skip-upgrade"])
28
+ assert args.command == "mesh"
29
+ assert args.mesh_command == "update"
30
+ assert args.dry_run is True
31
+ assert args.skip_upgrade is True
32
+
33
+ args = p.parse_args(["mesh", "update", "--config", "/tmp/mesh.yaml"])
34
+ assert args.config == "/tmp/mesh.yaml"
35
+
36
+
37
+ def test_setup_in_dispatch():
38
+ """setup command is wired into the mesh module."""
39
+ from culture.cli import mesh
40
+
41
+ assert hasattr(mesh, "_cmd_setup")
42
+ assert callable(mesh._cmd_setup)
43
+
44
+
45
+ def test_update_in_dispatch():
46
+ """update command is wired into the mesh module."""
47
+ from culture.cli import mesh
48
+
49
+ assert hasattr(mesh, "_cmd_update")
50
+ assert callable(mesh._cmd_update)
51
+
52
+
53
+ # ---- _cmd_update behaviour tests ----
54
+
55
+ _MESH_MOD = "culture.cli.mesh"
56
+
57
+
58
+ @pytest.fixture
59
+ def update_args(tmp_path):
60
+ """Minimal argparse namespace for _cmd_update."""
61
+ p = _build_parser()
62
+ config = str(tmp_path / "mesh.yaml")
63
+ return p.parse_args(["mesh", "update", "--skip-upgrade", "--dry-run", "--config", config])
64
+
65
+
66
+ @patch("culture.pidfile.list_servers")
67
+ @patch(f"{_MESH_MOD}._resolve_mesh_for_server")
68
+ @patch(f"{_MESH_MOD}._restart_mesh_services")
69
+ @patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
70
+ def test_update_discovers_running_servers(
71
+ _mock_upgrade, mock_restart, mock_resolve, mock_list, update_args
72
+ ):
73
+ """_cmd_update restarts every running server, not just mesh.yaml."""
74
+ from culture.cli.mesh import _cmd_update
75
+
76
+ spark_mesh = MeshConfig(server=MeshServerConfig(name="spark"))
77
+ mock_list.return_value = [{"name": "spark", "pid": 1, "port": 6667}]
78
+ mock_resolve.return_value = spark_mesh
79
+
80
+ _cmd_update(update_args)
81
+
82
+ mock_resolve.assert_called_once_with("spark", update_args.config)
83
+ mock_restart.assert_called_once()
84
+ assert mock_restart.call_args[0][1] == "spark"
85
+
86
+
87
+ @patch("culture.pidfile.list_servers", return_value=[])
88
+ @patch("culture.mesh_config.load_mesh_config")
89
+ @patch(f"{_MESH_MOD}._restart_mesh_services")
90
+ @patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
91
+ def test_update_falls_back_to_mesh_yaml_when_no_servers(
92
+ _mock_upgrade, mock_restart, mock_load, _mock_list, update_args
93
+ ):
94
+ """When no servers are running, fall back to mesh.yaml."""
95
+ from culture.cli.mesh import _cmd_update
96
+
97
+ mesh = MeshConfig(server=MeshServerConfig(name="culture"))
98
+ mock_load.return_value = mesh
99
+
100
+ _cmd_update(update_args)
101
+
102
+ mock_load.assert_called_once_with(update_args.config)
103
+ mock_restart.assert_called_once()
104
+ assert mock_restart.call_args[0][1] == "culture"
105
+
106
+
107
+ @patch("culture.pidfile.list_servers")
108
+ @patch(f"{_MESH_MOD}._restart_mesh_services")
109
+ @patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
110
+ def test_update_skips_server_without_config(
111
+ _mock_upgrade, mock_restart, mock_list, update_args, capsys
112
+ ):
113
+ """Servers with no matching config are skipped with a warning."""
114
+ from culture.cli.mesh import _cmd_update
115
+
116
+ mock_list.return_value = [{"name": "unknown", "pid": 1, "port": 6667}]
117
+
118
+ with patch(f"{_MESH_MOD}._resolve_mesh_for_server", return_value=None):
119
+ _cmd_update(update_args)
120
+
121
+ mock_restart.assert_not_called()
122
+ assert "no config found" in capsys.readouterr().err
123
+
124
+
125
+ # ---- _resolve_mesh_for_server tests ----
126
+
127
+
128
+ def test_resolve_uses_mesh_yaml_when_name_matches(tmp_path):
129
+ """_resolve_mesh_for_server returns mesh.yaml config when server name matches."""
130
+ from culture.cli.mesh import _resolve_mesh_for_server
131
+
132
+ mesh = MeshConfig(
133
+ server=MeshServerConfig(name="spark", links=[MeshLinkConfig(name="thor", host="1.2.3.4")]),
134
+ agents=[MeshAgentConfig(nick="claude", workdir="/tmp")],
135
+ )
136
+ config_path = str(tmp_path / "mesh.yaml")
137
+ from culture.mesh_config import save_mesh_config
138
+
139
+ save_mesh_config(mesh, config_path)
140
+
141
+ result = _resolve_mesh_for_server("spark", config_path)
142
+ assert result is not None
143
+ assert result.server.name == "spark"
144
+ assert len(result.server.links) == 1
145
+
146
+
147
+ def test_resolve_rebuilds_from_agents_yaml_preserving_links(tmp_path):
148
+ """When mesh.yaml has wrong name, rebuild from agents.yaml and keep links."""
149
+ from culture.cli.mesh import _resolve_mesh_for_server
150
+ from culture.clients.claude.config import AgentConfig, DaemonConfig, ServerConnConfig
151
+ from culture.mesh_config import save_mesh_config
152
+
153
+ # mesh.yaml says "culture" but running server is "spark"
154
+ old_mesh = MeshConfig(
155
+ server=MeshServerConfig(
156
+ name="culture",
157
+ host="127.0.0.1",
158
+ port=7000,
159
+ links=[MeshLinkConfig(name="thor", host="1.2.3.4")],
160
+ ),
161
+ )
162
+ config_path = str(tmp_path / "mesh.yaml")
163
+ save_mesh_config(old_mesh, config_path)
164
+
165
+ # agents.yaml says "spark"
166
+ daemon = DaemonConfig(
167
+ server=ServerConnConfig(name="spark", host="localhost", port=6667),
168
+ agents=[AgentConfig(nick="spark-claude", agent="claude", directory="/tmp")],
169
+ )
170
+
171
+ with patch(f"{_MESH_MOD}.DEFAULT_CONFIG", str(tmp_path / "agents.yaml")):
172
+ from culture.clients.claude.config import save_config
173
+
174
+ save_config(str(tmp_path / "agents.yaml"), daemon)
175
+ result = _resolve_mesh_for_server("spark", config_path)
176
+
177
+ assert result is not None
178
+ assert result.server.name == "spark"
179
+ assert len(result.agents) == 1
180
+ assert result.agents[0].nick == "claude"
181
+ # Server settings from old mesh.yaml are preserved
182
+ assert result.server.host == "127.0.0.1"
183
+ assert result.server.port == 7000
184
+ assert len(result.server.links) == 1
185
+ assert result.server.links[0].name == "thor"