agentirc-cli 8.1.0__tar.gz → 8.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/CHANGELOG.md +32 -0
  2. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/PKG-INFO +5 -1
  3. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_config.culture.yml +3 -0
  4. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_data/sites.yml +1 -0
  5. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_includes/head_custom.html +1 -0
  6. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/client.py +268 -144
  7. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/config.py +15 -0
  8. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/ircd.py +49 -20
  9. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/server_link.py +161 -42
  10. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/config.py +10 -0
  11. agentirc_cli-8.3.0/culture/protocol/extensions/tracing.md +37 -0
  12. agentirc_cli-8.3.0/culture/telemetry/__init__.py +26 -0
  13. agentirc_cli-8.3.0/culture/telemetry/context.py +121 -0
  14. agentirc_cli-8.3.0/culture/telemetry/tracing.py +114 -0
  15. agentirc_cli-8.3.0/docs/agentirc/otelcol-template.yaml +25 -0
  16. agentirc_cli-8.3.0/docs/agentirc/telemetry.md +101 -0
  17. agentirc_cli-8.3.0/docs/reference/architecture/index.md +27 -0
  18. agentirc_cli-8.3.0/docs/reference/architecture/subsites.md +111 -0
  19. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/server/config.md +26 -0
  20. agentirc_cli-8.3.0/docs/superpowers/plans/2026-04-24-otel-foundation.md +2159 -0
  21. agentirc_cli-8.3.0/docs/superpowers/plans/2026-04-25-otel-federation.md +229 -0
  22. agentirc_cli-8.3.0/docs/superpowers/specs/2026-04-24-otel-observability-design.md +334 -0
  23. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/pyproject.toml +5 -1
  24. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/sitemap.html +1 -0
  25. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/conftest.py +26 -0
  26. agentirc_cli-8.3.0/tests/telemetry/__init__.py +0 -0
  27. agentirc_cli-8.3.0/tests/telemetry/_fakes.py +26 -0
  28. agentirc_cli-8.3.0/tests/telemetry/test_config.py +26 -0
  29. agentirc_cli-8.3.0/tests/telemetry/test_config_load.py +36 -0
  30. agentirc_cli-8.3.0/tests/telemetry/test_context.py +147 -0
  31. agentirc_cli-8.3.0/tests/telemetry/test_dispatch_span.py +45 -0
  32. agentirc_cli-8.3.0/tests/telemetry/test_emit_event_span.py +42 -0
  33. agentirc_cli-8.3.0/tests/telemetry/test_federation_propagation.py +141 -0
  34. agentirc_cli-8.3.0/tests/telemetry/test_outbound_inject.py +70 -0
  35. agentirc_cli-8.3.0/tests/telemetry/test_parse_error.py +33 -0
  36. agentirc_cli-8.3.0/tests/telemetry/test_privmsg_span.py +45 -0
  37. agentirc_cli-8.3.0/tests/telemetry/test_s2s_dispatch_span.py +156 -0
  38. agentirc_cli-8.3.0/tests/telemetry/test_s2s_relay_span.py +114 -0
  39. agentirc_cli-8.3.0/tests/telemetry/test_s2s_session_span.py +64 -0
  40. agentirc_cli-8.3.0/tests/telemetry/test_server_init.py +29 -0
  41. agentirc_cli-8.3.0/tests/telemetry/test_server_link_inject.py +124 -0
  42. agentirc_cli-8.3.0/tests/telemetry/test_session_span.py +105 -0
  43. agentirc_cli-8.3.0/tests/telemetry/test_tracing.py +49 -0
  44. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_federation.py +47 -0
  45. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/uv.lock +185 -1
  46. agentirc_cli-8.1.0/docs/reference/architecture/index.md +0 -13
  47. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.claude/agents/doc-test-alignment.md +0 -0
  48. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.claude/skills/pr-review/SKILL.md +0 -0
  49. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.claude/skills/run-tests/SKILL.md +0 -0
  50. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  51. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.flake8 +0 -0
  52. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.github/workflows/docs-check.yml +0 -0
  53. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.github/workflows/publish.yml +0 -0
  54. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.github/workflows/security-checks.yml +0 -0
  55. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.github/workflows/tests.yml +0 -0
  56. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.gitignore +0 -0
  57. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.markdownlint-cli2.yaml +0 -0
  58. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.pr_agent.toml +0 -0
  59. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.pre-commit-config.yaml +0 -0
  60. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/.pylintrc +0 -0
  61. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/CLAUDE.md +0 -0
  62. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/Gemfile +0 -0
  63. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/Gemfile.lock +0 -0
  64. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/LICENSE +0 -0
  65. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/README.md +0 -0
  66. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/SECURITY.md +0 -0
  67. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_config.base.yml +0 -0
  68. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_sass/color_schemes/anthropic.scss +0 -0
  69. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_sass/color_schemes/dark-terminal.scss +0 -0
  70. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/_sass/custom/custom.scss +0 -0
  71. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/IMG_3183.png +0 -0
  72. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/apple-touch-icon.png +0 -0
  73. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/favicon-16x16.png +0 -0
  74. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/favicon-32x32.png +0 -0
  75. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/favicon.ico +0 -0
  76. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/og-agentirc.png +0 -0
  77. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/assets/images/og-culture.png +0 -0
  78. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/__init__.py +0 -0
  79. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/__main__.py +0 -0
  80. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/CLAUDE.md +0 -0
  81. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/__init__.py +0 -0
  82. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/__main__.py +0 -0
  83. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/channel.py +0 -0
  84. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/docs/agentirc-architecture.md +0 -0
  85. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/docs/agentirc-features.md +0 -0
  86. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/docs/agentirc-skill.md +0 -0
  87. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/docs/agentirc.md +0 -0
  88. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/events.py +0 -0
  89. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/history_store.py +0 -0
  90. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/remote_client.py +0 -0
  91. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/room_store.py +0 -0
  92. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/rooms_util.py +0 -0
  93. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skill.py +0 -0
  94. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skills/__init__.py +0 -0
  95. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skills/history.py +0 -0
  96. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skills/icon.py +0 -0
  97. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skills/rooms.py +0 -0
  98. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/skills/threads.py +0 -0
  99. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/agentirc/thread_store.py +0 -0
  100. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/aio.py +0 -0
  101. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/__init__.py +0 -0
  102. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/bot.py +0 -0
  103. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/bot_manager.py +0 -0
  104. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/config.py +0 -0
  105. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/filter_dsl.py +0 -0
  106. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/http_listener.py +0 -0
  107. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/system/__init__.py +0 -0
  108. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/system/welcome/__init__.py +0 -0
  109. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/system/welcome/bot.yaml +0 -0
  110. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/system/welcome/handler.py +0 -0
  111. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/template_engine.py +0 -0
  112. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/bots/virtual_client.py +0 -0
  113. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/__init__.py +0 -0
  114. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/_passthrough.py +0 -0
  115. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/afi.py +0 -0
  116. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/agent.py +0 -0
  117. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/bot.py +0 -0
  118. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/channel.py +0 -0
  119. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/devex.py +0 -0
  120. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/introspect.py +0 -0
  121. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/mesh.py +0 -0
  122. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/server.py +0 -0
  123. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/__init__.py +0 -0
  124. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/constants.py +0 -0
  125. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/display.py +0 -0
  126. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/formatting.py +0 -0
  127. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/ipc.py +0 -0
  128. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/mesh.py +0 -0
  129. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/shared/process.py +0 -0
  130. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/cli/skills.py +0 -0
  131. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/__init__.py +0 -0
  132. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/__init__.py +0 -0
  133. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/agent_runner.py +0 -0
  134. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/config.py +0 -0
  135. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/culture.yaml +0 -0
  136. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/daemon.py +0 -0
  137. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/ipc.py +0 -0
  138. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/irc_transport.py +0 -0
  139. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/message_buffer.py +0 -0
  140. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/skill/SKILL.md +0 -0
  141. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/skill/__init__.py +0 -0
  142. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/skill/irc_client.py +0 -0
  143. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/socket_server.py +0 -0
  144. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/supervisor.py +0 -0
  145. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/acp/webhook.py +0 -0
  146. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/__init__.py +0 -0
  147. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/__main__.py +0 -0
  148. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/agent_runner.py +0 -0
  149. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/config.py +0 -0
  150. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/culture.yaml +0 -0
  151. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/daemon.py +0 -0
  152. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/ipc.py +0 -0
  153. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/irc_transport.py +0 -0
  154. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/message_buffer.py +0 -0
  155. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/skill/SKILL.md +0 -0
  156. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/skill/__init__.py +0 -0
  157. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/skill/irc_client.py +0 -0
  158. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/socket_server.py +0 -0
  159. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/supervisor.py +0 -0
  160. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/claude/webhook.py +0 -0
  161. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/__init__.py +0 -0
  162. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/agent_runner.py +0 -0
  163. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/config.py +0 -0
  164. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/culture.yaml +0 -0
  165. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/daemon.py +0 -0
  166. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/ipc.py +0 -0
  167. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/irc_transport.py +0 -0
  168. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/message_buffer.py +0 -0
  169. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/skill/SKILL.md +0 -0
  170. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/skill/__init__.py +0 -0
  171. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/skill/irc_client.py +0 -0
  172. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/socket_server.py +0 -0
  173. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/supervisor.py +0 -0
  174. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/codex/webhook.py +0 -0
  175. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/__init__.py +0 -0
  176. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/agent_runner.py +0 -0
  177. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/config.py +0 -0
  178. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/culture.yaml +0 -0
  179. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/daemon.py +0 -0
  180. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/ipc.py +0 -0
  181. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/irc_transport.py +0 -0
  182. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/message_buffer.py +0 -0
  183. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/skill/SKILL.md +0 -0
  184. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/skill/__init__.py +0 -0
  185. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/skill/irc_client.py +0 -0
  186. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/socket_server.py +0 -0
  187. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/supervisor.py +0 -0
  188. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/clients/copilot/webhook.py +0 -0
  189. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/__init__.py +0 -0
  190. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/app.py +0 -0
  191. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/client.py +0 -0
  192. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/commands.py +0 -0
  193. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/status.py +0 -0
  194. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/widgets/__init__.py +0 -0
  195. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/widgets/chat.py +0 -0
  196. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/widgets/info_panel.py +0 -0
  197. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/console/widgets/sidebar.py +0 -0
  198. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/constants.py +0 -0
  199. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/credentials.py +0 -0
  200. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/formatting.py +0 -0
  201. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/learn_prompt.py +0 -0
  202. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/mesh_config.py +0 -0
  203. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/observer.py +0 -0
  204. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/__init__.py +0 -0
  205. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/collector.py +0 -0
  206. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/model.py +0 -0
  207. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/renderer_text.py +0 -0
  208. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/renderer_web.py +0 -0
  209. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/overview/web/style.css +0 -0
  210. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/persistence.py +0 -0
  211. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/pidfile.py +0 -0
  212. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/__init__.py +0 -0
  213. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/commands.py +0 -0
  214. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/events.md +0 -0
  215. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/federation.md +0 -0
  216. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/history.md +0 -0
  217. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/icons.md +0 -0
  218. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/rooms.md +0 -0
  219. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/tags.md +0 -0
  220. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/extensions/threads.md +0 -0
  221. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/message.py +0 -0
  222. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/protocol-index.md +0 -0
  223. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/protocol/replies.py +0 -0
  224. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/culture/skills/culture/SKILL.md +0 -0
  225. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/README.md +0 -0
  226. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/agentirc/architecture-overview.md +0 -0
  227. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/agentirc/bots.md +0 -0
  228. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/agentirc/events.md +0 -0
  229. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/agentirc/index.md +0 -0
  230. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/agentirc/why-agentirc.md +0 -0
  231. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/agent-lifecycle.md +0 -0
  232. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/choose-a-harness.md +0 -0
  233. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/features.md +0 -0
  234. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/index.md +0 -0
  235. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/mental-model.md +0 -0
  236. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/operate.md +0 -0
  237. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/patterns.md +0 -0
  238. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/quickstart.md +0 -0
  239. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/reflective-development.md +0 -0
  240. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/vision-patterns-index.md +0 -0
  241. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/vision.md +0 -0
  242. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/culture/what-is-culture.md +0 -0
  243. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/architecture/agent-harness-spec.md +0 -0
  244. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/architecture/layers.md +0 -0
  245. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/architecture/threads.md +0 -0
  246. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/cli/afi.md +0 -0
  247. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/cli/commands.md +0 -0
  248. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/cli/devex.md +0 -0
  249. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/cli/index.md +0 -0
  250. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/console.md +0 -0
  251. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/harnesses/acp.md +0 -0
  252. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/harnesses/claude.md +0 -0
  253. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/harnesses/codex.md +0 -0
  254. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/harnesses/copilot.md +0 -0
  255. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/harnesses/index.md +0 -0
  256. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/index.md +0 -0
  257. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/server/architecture.md +0 -0
  258. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/server/deployment.md +0 -0
  259. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/server/index.md +0 -0
  260. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/reference/server/security.md +0 -0
  261. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  262. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/resources/positioning.md +0 -0
  263. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/federation.md +0 -0
  264. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/harnesses.md +0 -0
  265. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/humans-and-agents.md +0 -0
  266. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/index.md +0 -0
  267. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/persistence.md +0 -0
  268. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/concepts/rooms.md +0 -0
  269. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/demos/magic-demo.md +0 -0
  270. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/guides/first-session.md +0 -0
  271. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/guides/index.md +0 -0
  272. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/guides/join-as-human.md +0 -0
  273. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/guides/local-setup.md +0 -0
  274. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/guides/multi-machine.md +0 -0
  275. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/01-pair-programming.md +0 -0
  276. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
  277. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
  278. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
  279. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/05-the-observer.md +0 -0
  280. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
  281. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
  282. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
  283. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/09-research-swarm.md +0 -0
  284. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
  285. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/shared/use-cases-index.md +0 -0
  286. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  287. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  288. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  289. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  290. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  291. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  292. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  293. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  294. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  295. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  296. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
  297. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-15-mesh-events.md +0 -0
  298. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-18-culture-dev-positioning.md +0 -0
  299. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/plans/2026-04-22-agex-integration.md +0 -0
  300. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  301. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  302. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  303. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  304. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  305. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  306. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  307. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  308. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  309. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  310. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  311. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  312. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  313. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  314. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  315. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  316. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
  317. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-15-mesh-events-design.md +0 -0
  318. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-17-sites-repositioning-design.md +0 -0
  319. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-18-culture-dev-positioning-design.md +0 -0
  320. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/docs/superpowers/specs/2026-04-22-agex-integration-design.md +0 -0
  321. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/favicon.ico +0 -0
  322. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/README.md +0 -0
  323. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/config.py +0 -0
  324. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/culture.yaml +0 -0
  325. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/daemon.py +0 -0
  326. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/ipc.py +0 -0
  327. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/irc_transport.py +0 -0
  328. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/message_buffer.py +0 -0
  329. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/skill/SKILL.md +0 -0
  330. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/skill/irc_client.py +0 -0
  331. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/socket_server.py +0 -0
  332. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/packages/agent-harness/webhook.py +0 -0
  333. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  334. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  335. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  336. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  337. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/robots.txt +0 -0
  338. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/sitemap-agentirc.html +0 -0
  339. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/sitemap-main.html +0 -0
  340. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/sonar-project.properties +0 -0
  341. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/__init__.py +0 -0
  342. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_acp_daemon.py +0 -0
  343. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_agent_runner.py +0 -0
  344. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_archive.py +0 -0
  345. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_bot.py +0 -0
  346. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_bot_config.py +0 -0
  347. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_bot_config_fires_event_toplevel.py +0 -0
  348. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_bot_manager.py +0 -0
  349. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_bots_integration.py +0 -0
  350. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_channel.py +0 -0
  351. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_channel_cli.py +0 -0
  352. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_cli_afi.py +0 -0
  353. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_cli_devex.py +0 -0
  354. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_cli_introspect.py +0 -0
  355. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_cli_passthrough.py +0 -0
  356. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_codex_daemon.py +0 -0
  357. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_connection.py +0 -0
  358. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_chat_markdown.py +0 -0
  359. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_client.py +0 -0
  360. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_commands.py +0 -0
  361. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_connection.py +0 -0
  362. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_fixes_224_227.py +0 -0
  363. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_icons.py +0 -0
  364. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_integration.py +0 -0
  365. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_console_status.py +0 -0
  366. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_copilot_daemon.py +0 -0
  367. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_credentials.py +0 -0
  368. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_culture_config.py +0 -0
  369. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_daemon.py +0 -0
  370. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_daemon_config.py +0 -0
  371. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_daemon_ipc.py +0 -0
  372. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_discovery.py +0 -0
  373. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_display.py +0 -0
  374. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_basic.py +0 -0
  375. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_bot_chain.py +0 -0
  376. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_bot_trigger.py +0 -0
  377. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_cap_fallback.py +0 -0
  378. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_catalog.py +0 -0
  379. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_federation.py +0 -0
  380. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_history.py +0 -0
  381. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_lifecycle.py +0 -0
  382. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_events_reserved_nick.py +0 -0
  383. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_filter_dsl.py +0 -0
  384. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_history.py +0 -0
  385. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_http_listener.py +0 -0
  386. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_integration_layer5.py +0 -0
  387. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_ipc.py +0 -0
  388. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_irc_transport.py +0 -0
  389. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_irc_transport_tags.py +0 -0
  390. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_learn_prompt.py +0 -0
  391. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_link_reconnect.py +0 -0
  392. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_manifest_config.py +0 -0
  393. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mention_alias.py +0 -0
  394. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mention_target_cleanup.py +0 -0
  395. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mention_warning.py +0 -0
  396. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mentions.py +0 -0
  397. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mesh_config.py +0 -0
  398. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_mesh_readiness.py +0 -0
  399. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_message.py +0 -0
  400. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_message_buffer.py +0 -0
  401. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_message_tags.py +0 -0
  402. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_messaging.py +0 -0
  403. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_migrate_cli.py +0 -0
  404. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_modes.py +0 -0
  405. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_overview_cli.py +0 -0
  406. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_overview_collector.py +0 -0
  407. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_overview_model.py +0 -0
  408. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_overview_renderer.py +0 -0
  409. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_overview_web.py +0 -0
  410. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_persistence.py +0 -0
  411. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_persistence_timeout.py +0 -0
  412. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_pidfile.py +0 -0
  413. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_poll_loop.py +0 -0
  414. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_register_cli.py +0 -0
  415. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_room_persistence.py +0 -0
  416. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_rooms.py +0 -0
  417. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_rooms_federation.py +0 -0
  418. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_rooms_integration.py +0 -0
  419. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_server_icon_skill.py +0 -0
  420. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_setup_update_cli.py +0 -0
  421. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_skill_client.py +0 -0
  422. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_skill_docs.py +0 -0
  423. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_skills.py +0 -0
  424. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_socket_server.py +0 -0
  425. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_supervisor.py +0 -0
  426. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_template_engine.py +0 -0
  427. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_thread_buffer.py +0 -0
  428. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_threads.py +0 -0
  429. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_virtual_client.py +0 -0
  430. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_wait_for_port.py +0 -0
  431. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_webhook.py +0 -0
  432. {agentirc_cli-8.1.0 → agentirc_cli-8.3.0}/tests/test_welcome_bot.py +0 -0
@@ -4,6 +4,38 @@ 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
+ ## [8.3.0] - 2026-04-25
8
+
9
+ ### Added
10
+
11
+ - `irc.s2s.session` span over ServerLink connection lifetime.
12
+ - `irc.s2s.<VERB>` per-verb spans on inbound federation messages with traceparent extraction and the inbound mitigation rules from `culture/protocol/extensions/tracing.md`.
13
+ - `irc.s2s.relay` span on outbound relay enforcing the re-sign-per-hop rule.
14
+ - `irc.client.session` span over Client connection lifetime (#290).
15
+ - `irc.join` and `irc.part` spans (#290).
16
+ - Public `culture.telemetry.context_from_traceparent` and `culture.telemetry.current_traceparent` helpers.
17
+ - Single traceparent injection choke point at `ServerLink.send_raw`.
18
+ - End-to-end propagation tests proving one `trace_id` spans federated client → server → relay → server hops.
19
+
20
+ ### Changed
21
+
22
+ - `Client._dispatch` span name and `irc.command` attribute now uppercase, matching `ServerLink._dispatch` convention.
23
+
24
+ ### Fixed
25
+
26
+ - `_replay_event` uses the hasattr-guarded comparison so string-typed federated `event.type` no longer skips the typed fast path. (#291)
27
+
28
+ ## [8.2.0] - 2026-04-24
29
+
30
+ ### Added
31
+
32
+ - OpenTelemetry foundation: `culture/telemetry/` package with TracerProvider bootstrap, W3C trace context extract/inject helpers for IRCv3 tags, and `TelemetryConfig` block in `server.yaml`.
33
+ - Protocol extension: `culture.dev/traceparent` and `culture.dev/tracestate` IRCv3 tags (`culture/protocol/extensions/tracing.md`).
34
+ - Server-side tracing: `IRCd.emit_event`, `Client._dispatch`, `Client._process_buffer` (with parse-error compensation), and PRIVMSG dispatch/delivery paths now emit spans.
35
+ - Outbound traceparent injection on `Client.send` and `Client.send_raw` when a span is active.
36
+ - Operator docs at `docs/agentirc/telemetry.md` and starter collector config at `docs/agentirc/otelcol-template.yaml`; `docs/reference/server/config.md` documents the new `telemetry` block.
37
+ - Dependencies: `opentelemetry-api`, `opentelemetry-sdk`, `opentelemetry-exporter-otlp-proto-grpc`, `opentelemetry-semantic-conventions`.
38
+
7
39
  ## [8.1.0] - 2026-04-23
8
40
 
9
41
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 8.1.0
3
+ Version: 8.3.0
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
@@ -18,6 +18,10 @@ Requires-Dist: aiohttp>=3.9
18
18
  Requires-Dist: anthropic>=0.40
19
19
  Requires-Dist: claude-agent-sdk>=0.1
20
20
  Requires-Dist: mistune>=3.0
21
+ Requires-Dist: opentelemetry-api>=1.25
22
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.25
23
+ Requires-Dist: opentelemetry-sdk>=1.25
24
+ Requires-Dist: opentelemetry-semantic-conventions>=0.46b0
21
25
  Requires-Dist: pyyaml>=6.0
22
26
  Requires-Dist: textual>=1.0
23
27
  Provides-Extra: copilot
@@ -36,6 +36,8 @@ aux_links:
36
36
  - "/agentirc/"
37
37
  "Agent Experience":
38
38
  - "https://culture.dev/agex/"
39
+ "Agent First Interop":
40
+ - "https://culture.dev/afi/"
39
41
  "Citation CLI":
40
42
  - "/citation-cli/"
41
43
  "GitHub":
@@ -49,5 +51,6 @@ footer_content: >-
49
51
  Inspectable CLI via <a href="/reference/cli/devex/">culture devex</a>
50
52
  (explain / overview / learn at every level).
51
53
  Agent Experience via <a href="https://culture.dev/agex/">agex-cli</a>.
54
+ Agent First Interop via <a href="https://culture.dev/afi/">afi-cli</a>.
52
55
  Code distribution via <a href="/citation-cli/">Citation CLI</a>.
53
56
  Source on <a href="https://github.com/agentculture/culture">GitHub</a>.
@@ -1,3 +1,4 @@
1
+ afi: https://culture.dev/afi/
1
2
  agentirc: https://culture.dev/agentirc/
2
3
  agex: https://culture.dev/agex/
3
4
  citation_cli: https://culture.dev/citation-cli/
@@ -4,4 +4,5 @@
4
4
  <link rel="icon" type="image/png" sizes="16x16" href="{{ '/assets/images/favicon-16x16.png' | relative_url }}">
5
5
  <link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/images/apple-touch-icon.png' | relative_url }}">
6
6
  <link rel="related" href="{{ site.data.sites.agex }}" title="Agent Experience">
7
+ <link rel="related" href="{{ site.data.sites.afi }}" title="Agent First Interop">
7
8
  <link rel="related" href="{{ site.data.sites.citation_cli }}" title="Citation CLI">
@@ -6,12 +6,34 @@ import logging
6
6
  import re
7
7
  from typing import TYPE_CHECKING
8
8
 
9
+ from opentelemetry import trace as _otel_trace
10
+ from opentelemetry.context import Context as _OtelContext
11
+ from opentelemetry.trace import Span as _OtelSpan
12
+
9
13
  from culture.agentirc.channel import Channel
10
14
  from culture.agentirc.skill import Event, EventType
11
15
  from culture.aio import maybe_await
12
16
  from culture.constants import SYSTEM_USER_PREFIX
13
17
  from culture.protocol import replies
14
18
  from culture.protocol.message import Message
19
+ from culture.telemetry.context import TRACEPARENT_TAG as _TP_TAG_NAME
20
+ from culture.telemetry.context import (
21
+ context_from_traceparent,
22
+ current_traceparent,
23
+ extract_traceparent_from_tags,
24
+ )
25
+ from culture.telemetry.context import inject_traceparent as _inject_traceparent
26
+
27
+ # OTEL instrumentation name (must match `_CULTURE_TRACER_NAME` in
28
+ # culture/telemetry/tracing.py so trace consumers see one consistent value).
29
+ _TRACER_NAME = "culture.agentirc"
30
+ # Span attribute keys, defined once so a future rename / sanitization layer
31
+ # has one edit point.
32
+ _ATTR_BODY = "irc.message.body"
33
+ _ATTR_SIZE = "irc.message.size"
34
+ _ATTR_NICK = "irc.client.nick"
35
+ _ATTR_CHANNEL = "irc.channel"
36
+
15
37
 
16
38
  if TYPE_CHECKING:
17
39
  from culture.agentirc.ircd import IRCd
@@ -39,12 +61,21 @@ class Client:
39
61
  self.caps: set[str] = set()
40
62
  self.modes: set[str] = set()
41
63
  self.icon: str | None = None
64
+ self._session_span: _OtelSpan | None = None
42
65
 
43
66
  @property
44
67
  def prefix(self) -> str:
45
68
  return f"{self.nick}!{self.user}@{self.host}"
46
69
 
47
70
  async def send(self, message: Message) -> None:
71
+ # Only inject trace context for clients that negotiated IRCv3
72
+ # message-tags; otherwise older clients would see an unexpected @-tag
73
+ # block and `send_tagged`'s tag-stripping for non-capable clients
74
+ # would be undone here.
75
+ if "message-tags" in self.caps:
76
+ tp = current_traceparent()
77
+ if tp is not None:
78
+ _inject_traceparent(message, traceparent=tp, tracestate=None)
48
79
  try:
49
80
  self.writer.write(message.format().encode("utf-8"))
50
81
  await self.writer.drain()
@@ -55,7 +86,15 @@ class Client:
55
86
  """Write a pre-formatted IRC line to the client socket.
56
87
 
57
88
  Appends CRLF internally, matching ServerLink.send_raw convention.
89
+ Injects `culture.dev/traceparent` as an IRCv3 tag when a span is active
90
+ AND the client negotiated the `message-tags` capability.
58
91
  """
92
+ if "message-tags" in self.caps:
93
+ tp = current_traceparent()
94
+ if tp is not None:
95
+ # send_raw takes a pre-formatted line without an existing tag
96
+ # block; prefix a fresh @tag.
97
+ line = f"@{_TP_TAG_NAME}={tp} {line}"
59
98
  try:
60
99
  self.writer.write(f"{line}\r\n".encode("utf-8"))
61
100
  await self.writer.drain()
@@ -84,46 +123,94 @@ class Client:
84
123
 
85
124
  async def _process_buffer(self, buffer: str) -> str:
86
125
  """Parse and dispatch all complete lines from buffer, return remainder."""
87
- while "\n" in buffer:
88
- line, buffer = buffer.split("\n", 1)
89
- if line.strip():
90
- msg = Message.parse(line)
126
+ # Per-call get_tracer: test fixture swaps provider between tests.
127
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
128
+ "irc.client.process_buffer"
129
+ ) as span:
130
+ while "\n" in buffer:
131
+ line, buffer = buffer.split("\n", 1)
132
+ if not line.strip():
133
+ continue
134
+ try:
135
+ msg = Message.parse(line)
136
+ except Exception as exc: # noqa: BLE001 -- widen for any parser failure
137
+ span.add_event(
138
+ "irc.parse_error",
139
+ attributes={
140
+ "line_preview": line[:64],
141
+ "error": type(exc).__name__,
142
+ },
143
+ )
144
+ continue
91
145
  if msg.command:
92
146
  await self._dispatch(msg)
93
- return buffer
147
+ return buffer
94
148
 
95
149
  async def handle(self, initial_msg: str | None = None) -> None:
96
- buffer = ""
97
- if initial_msg:
98
- buffer = initial_msg.replace("\r\n", "\n").replace("\r", "\n")
99
- buffer = await self._process_buffer(buffer)
100
- while True:
101
- data = await self.reader.read(4096)
102
- if not data:
103
- break
104
- buffer += data.decode("utf-8", errors="replace")
105
- # Cap buffer to prevent unbounded memory growth (512 bytes per RFC 2812)
106
- if len(buffer) > 8192:
107
- buffer = buffer[-4096:]
108
- # Normalize all line endings to \n for simpler parsing
109
- buffer = buffer.replace("\r\n", "\n").replace("\r", "\n")
110
- buffer = await self._process_buffer(buffer)
150
+ peer_info = self.writer.get_extra_info("peername")
151
+ remote_addr = f"{peer_info[0]}:{peer_info[1]}" if peer_info else ""
152
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
153
+ "irc.client.session",
154
+ attributes={"irc.client.remote_addr": remote_addr},
155
+ ) as span:
156
+ self._session_span = span
157
+ try:
158
+ buffer = ""
159
+ if initial_msg:
160
+ buffer = initial_msg.replace("\r\n", "\n").replace("\r", "\n")
161
+ buffer = await self._process_buffer(buffer)
162
+ while True:
163
+ data = await self.reader.read(4096)
164
+ if not data:
165
+ break
166
+ buffer += data.decode("utf-8", errors="replace")
167
+ # Cap buffer to prevent unbounded memory growth (512 bytes per RFC 2812)
168
+ if len(buffer) > 8192:
169
+ buffer = buffer[-4096:]
170
+ # Normalize all line endings to \n for simpler parsing
171
+ buffer = buffer.replace("\r\n", "\n").replace("\r", "\n")
172
+ buffer = await self._process_buffer(buffer)
173
+ except (ConnectionError, asyncio.IncompleteReadError):
174
+ pass
111
175
 
112
176
  async def _dispatch(self, msg: Message) -> None:
113
- handler = getattr(self, f"_handle_{msg.command.lower()}", None)
114
- if handler:
115
- await maybe_await(handler(msg))
177
+ extract = extract_traceparent_from_tags(msg, peer=None)
178
+ if extract.status == "valid":
179
+ parent_ctx: _OtelContext | None = context_from_traceparent(extract.traceparent)
116
180
  else:
117
- skill = self.server.get_skill_for_command(msg.command)
118
- if skill and self._registered:
119
- try:
120
- await skill.on_command(self, msg)
121
- except Exception:
122
- logging.getLogger(__name__).exception(
123
- "Skill %s failed on command %s", skill.name, msg.command
124
- )
181
+ parent_ctx = _OtelContext() # force root: detach from session span
182
+
183
+ verb = msg.command.upper()
184
+ attrs = {
185
+ "irc.command": verb,
186
+ "irc.prefix_nick": (msg.prefix.split("!")[0] if msg.prefix else ""),
187
+ "culture.trace.origin": "local" if extract.status == "missing" else "remote",
188
+ }
189
+ if extract.status in ("malformed", "too_long"):
190
+ attrs["culture.trace.dropped_reason"] = extract.status
191
+
192
+ # Per-call get_tracer: test fixture swaps provider between tests.
193
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
194
+ f"irc.command.{verb}",
195
+ context=parent_ctx,
196
+ attributes=attrs,
197
+ ):
198
+ handler = getattr(self, f"_handle_{msg.command.lower()}", None)
199
+ if handler:
200
+ await maybe_await(handler(msg))
125
201
  else:
126
- await self.send_numeric(replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command")
202
+ skill = self.server.get_skill_for_command(msg.command)
203
+ if skill and self._registered:
204
+ try:
205
+ await skill.on_command(self, msg)
206
+ except Exception:
207
+ logging.getLogger(__name__).exception(
208
+ "Skill %s failed on command %s", skill.name, msg.command
209
+ )
210
+ else:
211
+ await self.send_numeric(
212
+ replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command"
213
+ )
127
214
 
128
215
  async def _handle_ping(self, msg: Message) -> None:
129
216
  token = msg.params[0] if msg.params else ""
@@ -205,6 +292,8 @@ class Client:
205
292
 
206
293
  self.nick = nick
207
294
  self.server.clients[nick] = self
295
+ if self._session_span is not None:
296
+ self._session_span.set_attribute(_ATTR_NICK, nick)
208
297
  await self._try_register()
209
298
 
210
299
  async def _handle_user(self, msg: Message) -> None:
@@ -256,45 +345,49 @@ class Client:
256
345
  return
257
346
 
258
347
  channel_name = msg.params[0]
259
- if not channel_name.startswith("#"):
260
- return
348
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
349
+ "irc.join",
350
+ attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
351
+ ):
352
+ if not channel_name.startswith("#"):
353
+ return
261
354
 
262
- # Block joins to archived rooms
263
- existing = self.server.channels.get(channel_name)
264
- if existing and existing.archived:
265
- await self.send(
266
- Message(
267
- prefix=self.server.config.name,
268
- command="NOTICE",
269
- params=[self.nick, f"{channel_name} is archived and cannot be joined"],
355
+ # Block joins to archived rooms
356
+ existing = self.server.channels.get(channel_name)
357
+ if existing and existing.archived:
358
+ await self.send(
359
+ Message(
360
+ prefix=self.server.config.name,
361
+ command="NOTICE",
362
+ params=[self.nick, f"{channel_name} is archived and cannot be joined"],
363
+ )
270
364
  )
271
- )
272
- return
365
+ return
273
366
 
274
- channel = self.server.get_or_create_channel(channel_name)
275
- if self in channel.members:
276
- return
367
+ channel = self.server.get_or_create_channel(channel_name)
368
+ if self in channel.members:
369
+ return
277
370
 
278
- channel.add(self)
279
- self.channels.add(channel)
371
+ channel.add(self)
372
+ self.channels.add(channel)
280
373
 
281
- # Notify all channel members (including self)
282
- join_msg = Message(prefix=self.prefix, command="JOIN", params=[channel_name])
283
- for member in list(channel.members):
284
- await member.send(join_msg)
374
+ # Notify all channel members (including self)
375
+ join_msg = Message(prefix=self.prefix, command="JOIN", params=[channel_name])
376
+ for member in list(channel.members):
377
+ await member.send(join_msg)
285
378
 
286
- # Send topic if set
287
- if channel.topic:
288
- await self.send_numeric(replies.RPL_TOPIC, channel_name, channel.topic)
379
+ # Send topic if set
380
+ if channel.topic:
381
+ await self.send_numeric(replies.RPL_TOPIC, channel_name, channel.topic)
289
382
 
290
- # Send names list
291
- await self._send_names(channel)
383
+ # Send names list
384
+ await self._send_names(channel)
292
385
 
293
- # Emit event AFTER delivering all join-related numerics (topic, NAMES)
294
- # so that the event PRIVMSG doesn't interleave with 353/366 in client buffers.
295
- await self.server.emit_event(
296
- Event(type=EventType.JOIN, channel=channel_name, nick=self.nick)
297
- )
386
+ # Emit event AFTER delivering all join-related numerics (topic, NAMES)
387
+ # so that the event PRIVMSG doesn't interleave with 353/366 in client buffers.
388
+ await self.server.emit_event(
389
+ Event(type=EventType.JOIN, channel=channel_name, nick=self.nick)
390
+ )
298
391
 
299
392
  async def _handle_part(self, msg: Message) -> None:
300
393
  if not msg.params:
@@ -302,36 +395,40 @@ class Client:
302
395
  return
303
396
 
304
397
  channel_name = msg.params[0]
305
- reason = msg.params[1] if len(msg.params) > 1 else ""
306
-
307
- channel = self.server.channels.get(channel_name)
308
- if not channel or self not in channel.members:
309
- await self.send_numeric(
310
- replies.ERR_NOTONCHANNEL,
311
- channel_name,
312
- replies.MSG_NOTONCHANNEL,
313
- )
314
- return
398
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
399
+ "irc.part",
400
+ attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
401
+ ):
402
+ reason = msg.params[1] if len(msg.params) > 1 else ""
403
+
404
+ channel = self.server.channels.get(channel_name)
405
+ if not channel or self not in channel.members:
406
+ await self.send_numeric(
407
+ replies.ERR_NOTONCHANNEL,
408
+ channel_name,
409
+ replies.MSG_NOTONCHANNEL,
410
+ )
411
+ return
315
412
 
316
- part_params = [channel_name, reason] if reason else [channel_name]
317
- part_msg = Message(prefix=self.prefix, command="PART", params=part_params)
318
- for member in list(channel.members):
319
- await member.send(part_msg)
413
+ part_params = [channel_name, reason] if reason else [channel_name]
414
+ part_msg = Message(prefix=self.prefix, command="PART", params=part_params)
415
+ for member in list(channel.members):
416
+ await member.send(part_msg)
320
417
 
321
- await self.server.emit_event(
322
- Event(
323
- type=EventType.PART,
324
- channel=channel_name,
325
- nick=self.nick,
326
- data={"reason": reason},
418
+ await self.server.emit_event(
419
+ Event(
420
+ type=EventType.PART,
421
+ channel=channel_name,
422
+ nick=self.nick,
423
+ data={"reason": reason},
424
+ )
327
425
  )
328
- )
329
426
 
330
- channel.remove(self)
331
- self.channels.discard(channel)
427
+ channel.remove(self)
428
+ self.channels.discard(channel)
332
429
 
333
- if not channel.members and not channel.persistent:
334
- del self.server.channels[channel_name]
430
+ if not channel.members and not channel.persistent:
431
+ del self.server.channels[channel_name]
335
432
 
336
433
  async def _handle_topic(self, msg: Message) -> None:
337
434
  if not msg.params:
@@ -615,46 +712,64 @@ class Client:
615
712
  await self.send_numeric(replies.RPL_UMODEIS, mode_str)
616
713
 
617
714
  async def _send_to_channel(self, channel, target, relay, text, is_notice):
618
- for member in list(channel.members):
619
- if member is not self:
620
- await member.send(relay)
621
- event_data = {"text": text}
622
- if is_notice:
623
- event_data["notice"] = True
624
- await self.server.emit_event(
625
- Event(
626
- type=EventType.MESSAGE,
627
- channel=target,
628
- nick=self.nick,
629
- data=event_data,
715
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
716
+ "irc.privmsg.deliver.channel",
717
+ attributes={
718
+ "irc.channel": target,
719
+ _ATTR_BODY: text,
720
+ _ATTR_SIZE: len(text),
721
+ "irc.notice": is_notice,
722
+ },
723
+ ):
724
+ for member in list(channel.members):
725
+ if member is not self:
726
+ await member.send(relay)
727
+ event_data = {"text": text}
728
+ if is_notice:
729
+ event_data["notice"] = True
730
+ await self.server.emit_event(
731
+ Event(
732
+ type=EventType.MESSAGE,
733
+ channel=target,
734
+ nick=self.nick,
735
+ data=event_data,
736
+ )
630
737
  )
631
- )
632
738
 
633
739
  async def _send_to_client(self, target, relay, text, is_notice):
634
740
  from culture.agentirc.remote_client import RemoteClient
635
741
 
636
- recipient = self.server.get_client(target)
637
- if not recipient:
638
- return False
639
- if isinstance(recipient, RemoteClient):
640
- s2s_cmd = "SNOTICE" if is_notice else "SMSG"
641
- await recipient.link.send_raw(
642
- f":{self.server.config.name} {s2s_cmd} {target} {self.nick} :{text}"
643
- )
644
- else:
645
- await recipient.send(relay)
646
- event_data = {"text": text, "target": target}
647
- if is_notice:
648
- event_data["notice"] = True
649
- await self.server.emit_event(
650
- Event(
651
- type=EventType.MESSAGE,
652
- channel=None,
653
- nick=self.nick,
654
- data=event_data,
742
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
743
+ "irc.privmsg.deliver.dm",
744
+ attributes={
745
+ "irc.target.nick": target,
746
+ _ATTR_BODY: text,
747
+ _ATTR_SIZE: len(text),
748
+ "irc.notice": is_notice,
749
+ },
750
+ ):
751
+ recipient = self.server.get_client(target)
752
+ if not recipient:
753
+ return False
754
+ if isinstance(recipient, RemoteClient):
755
+ s2s_cmd = "SNOTICE" if is_notice else "SMSG"
756
+ await recipient.link.send_raw(
757
+ f":{self.server.config.name} {s2s_cmd} {target} {self.nick} :{text}"
758
+ )
759
+ else:
760
+ await recipient.send(relay)
761
+ event_data = {"text": text, "target": target}
762
+ if is_notice:
763
+ event_data["notice"] = True
764
+ await self.server.emit_event(
765
+ Event(
766
+ type=EventType.MESSAGE,
767
+ channel=None,
768
+ nick=self.nick,
769
+ data=event_data,
770
+ )
655
771
  )
656
- )
657
- return True
772
+ return True
658
773
 
659
774
  async def _handle_privmsg(self, msg: Message) -> None:
660
775
  if len(msg.params) < 2:
@@ -665,28 +780,37 @@ class Client:
665
780
 
666
781
  target = msg.params[0]
667
782
  text = msg.params[1]
668
- relay = Message(prefix=self.prefix, command="PRIVMSG", params=[target, text])
669
-
670
- if target.startswith("#"):
671
- channel = self.server.channels.get(target)
672
- if not channel:
673
- await self.send_numeric(
674
- replies.ERR_NOSUCHCHANNEL, target, replies.MSG_NOSUCHCHANNEL
675
- )
676
- return
677
- if self not in channel.members:
678
- await self.send_numeric(
679
- replies.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"
680
- )
681
- return
682
- await self._send_to_channel(channel, target, relay, text, False)
683
- await self._notify_mentions(target, text)
684
- else:
685
- found = await self._send_to_client(target, relay, text, False)
686
- if not found:
687
- await self.send_numeric(replies.ERR_NOSUCHNICK, target, replies.MSG_NOSUCHNICK)
688
- return
689
- await self._notify_mentions(None, text)
783
+ # Per-call get_tracer: test fixture swaps provider between tests.
784
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
785
+ "irc.privmsg.dispatch",
786
+ attributes={
787
+ "irc.target": target,
788
+ _ATTR_BODY: text,
789
+ _ATTR_SIZE: len(text),
790
+ },
791
+ ):
792
+ relay = Message(prefix=self.prefix, command="PRIVMSG", params=[target, text])
793
+
794
+ if target.startswith("#"):
795
+ channel = self.server.channels.get(target)
796
+ if not channel:
797
+ await self.send_numeric(
798
+ replies.ERR_NOSUCHCHANNEL, target, replies.MSG_NOSUCHCHANNEL
799
+ )
800
+ return
801
+ if self not in channel.members:
802
+ await self.send_numeric(
803
+ replies.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"
804
+ )
805
+ return
806
+ await self._send_to_channel(channel, target, relay, text, False)
807
+ await self._notify_mentions(target, text)
808
+ else:
809
+ found = await self._send_to_client(target, relay, text, False)
810
+ if not found:
811
+ await self.send_numeric(replies.ERR_NOSUCHNICK, target, replies.MSG_NOSUCHNICK)
812
+ return
813
+ await self._notify_mentions(None, text)
690
814
 
691
815
  async def _notify_mentions(self, channel_name: str | None, text: str) -> None:
692
816
  from culture.agentirc.remote_client import RemoteClient
@@ -12,6 +12,20 @@ class LinkConfig:
12
12
  trust: str = "full" # "full" or "restricted"
13
13
 
14
14
 
15
+ @dataclass
16
+ class TelemetryConfig:
17
+ """OpenTelemetry settings. Mirrors server.yaml `telemetry:` block."""
18
+
19
+ enabled: bool = False
20
+ service_name: str = "culture.agentirc"
21
+ otlp_endpoint: str = "http://localhost:4317"
22
+ otlp_protocol: str = "grpc" # grpc | http/protobuf (only grpc supported initially)
23
+ otlp_timeout_ms: int = 5000
24
+ otlp_compression: str = "gzip" # gzip | none
25
+ traces_enabled: bool = True
26
+ traces_sampler: str = "parentbased_always_on"
27
+
28
+
15
29
  @dataclass
16
30
  class ServerConfig:
17
31
  """Configuration for a culture server instance."""
@@ -23,3 +37,4 @@ class ServerConfig:
23
37
  data_dir: str = ""
24
38
  links: list[LinkConfig] = field(default_factory=list)
25
39
  system_bots: dict = field(default_factory=dict)
40
+ telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)