agentirc-cli 0.7.0__tar.gz → 0.9.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 (175) hide show
  1. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/CHANGELOG.md +19 -0
  2. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/PKG-INFO +5 -1
  3. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/README.md +2 -0
  4. agentirc_cli-0.9.0/agentirc/__init__.py +1 -0
  5. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/cli.py +87 -5
  6. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/daemon.py +18 -9
  7. agentirc_cli-0.9.0/agentirc/clients/copilot/agent_runner.py +165 -0
  8. agentirc_cli-0.9.0/agentirc/clients/copilot/config.py +161 -0
  9. agentirc_cli-0.9.0/agentirc/clients/copilot/daemon.py +463 -0
  10. agentirc_cli-0.9.0/agentirc/clients/copilot/irc_transport.py +146 -0
  11. agentirc_cli-0.9.0/agentirc/clients/copilot/skill/SKILL.md +69 -0
  12. agentirc_cli-0.9.0/agentirc/clients/copilot/skill/irc_client.py +281 -0
  13. agentirc_cli-0.9.0/agentirc/clients/copilot/socket_server.py +106 -0
  14. agentirc_cli-0.9.0/agentirc/clients/copilot/supervisor.py +141 -0
  15. agentirc_cli-0.9.0/agentirc/clients/copilot/webhook.py +60 -0
  16. agentirc_cli-0.9.0/agentirc/clients/opencode/agent_runner.py +328 -0
  17. agentirc_cli-0.9.0/agentirc/clients/opencode/config.py +161 -0
  18. agentirc_cli-0.9.0/agentirc/clients/opencode/daemon.py +456 -0
  19. agentirc_cli-0.9.0/agentirc/clients/opencode/ipc.py +38 -0
  20. agentirc_cli-0.9.0/agentirc/clients/opencode/irc_transport.py +146 -0
  21. agentirc_cli-0.9.0/agentirc/clients/opencode/message_buffer.py +46 -0
  22. agentirc_cli-0.9.0/agentirc/clients/opencode/skill/SKILL.md +69 -0
  23. agentirc_cli-0.9.0/agentirc/clients/opencode/skill/irc_client.py +281 -0
  24. agentirc_cli-0.9.0/agentirc/clients/opencode/socket_server.py +102 -0
  25. agentirc_cli-0.9.0/agentirc/clients/opencode/supervisor.py +141 -0
  26. agentirc_cli-0.9.0/agentirc/clients/opencode/webhook.py +59 -0
  27. agentirc_cli-0.9.0/agentirc/protocol/__init__.py +0 -0
  28. agentirc_cli-0.9.0/agentirc/server/__init__.py +0 -0
  29. agentirc_cli-0.9.0/agentirc/server/skills/__init__.py +0 -0
  30. agentirc_cli-0.9.0/docs/copilot-backend.md +90 -0
  31. agentirc_cli-0.9.0/docs/opencode-backend.md +82 -0
  32. agentirc_cli-0.9.0/docs/resources/github-copilot-sdk-instructions.md +762 -0
  33. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/index.md +4 -0
  34. agentirc_cli-0.9.0/packages/agent-harness/ipc.py +38 -0
  35. agentirc_cli-0.9.0/packages/agent-harness/message_buffer.py +46 -0
  36. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/pyproject.toml +6 -1
  37. agentirc_cli-0.9.0/tests/__init__.py +0 -0
  38. agentirc_cli-0.9.0/tests/test_copilot_daemon.py +138 -0
  39. agentirc_cli-0.9.0/tests/test_opencode_daemon.py +139 -0
  40. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/uv.lock +46 -1
  41. agentirc_cli-0.7.0/agentirc/__init__.py +0 -1
  42. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.github/workflows/pages.yml +0 -0
  43. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.github/workflows/publish.yml +0 -0
  44. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.github/workflows/tests.yml +0 -0
  45. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.gitignore +0 -0
  46. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.markdownlint-cli2.yaml +0 -0
  47. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/.pr_agent.toml +0 -0
  48. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/CLAUDE.md +0 -0
  49. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/CNAME +0 -0
  50. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/Gemfile +0 -0
  51. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/Gemfile.lock +0 -0
  52. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/LICENSE +0 -0
  53. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/_config.yml +0 -0
  54. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/_sass/color_schemes/anthropic.scss +0 -0
  55. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/_sass/custom/custom.scss +0 -0
  56. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/__init__.py +0 -0
  57. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/__init__.py +0 -0
  58. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/__main__.py +0 -0
  59. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/agent_runner.py +0 -0
  60. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/config.py +0 -0
  61. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/daemon.py +0 -0
  62. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/ipc.py +0 -0
  63. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/irc_transport.py +0 -0
  64. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/message_buffer.py +0 -0
  65. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
  66. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/skill/__init__.py +0 -0
  67. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
  68. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/socket_server.py +0 -0
  69. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/supervisor.py +0 -0
  70. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/claude/webhook.py +0 -0
  71. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/__init__.py +0 -0
  72. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/agent_runner.py +0 -0
  73. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/config.py +0 -0
  74. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/ipc.py +0 -0
  75. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/irc_transport.py +0 -0
  76. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/message_buffer.py +0 -0
  77. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
  78. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/skill/__init__.py +0 -0
  79. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
  80. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/socket_server.py +0 -0
  81. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/supervisor.py +0 -0
  82. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/clients/codex/webhook.py +0 -0
  83. {agentirc_cli-0.7.0/agentirc/protocol → agentirc_cli-0.9.0/agentirc/clients/copilot}/__init__.py +0 -0
  84. {agentirc_cli-0.7.0/packages/agent-harness → agentirc_cli-0.9.0/agentirc/clients/copilot}/ipc.py +0 -0
  85. {agentirc_cli-0.7.0/packages/agent-harness → agentirc_cli-0.9.0/agentirc/clients/copilot}/message_buffer.py +0 -0
  86. {agentirc_cli-0.7.0/agentirc/server → agentirc_cli-0.9.0/agentirc/clients/copilot/skill}/__init__.py +0 -0
  87. {agentirc_cli-0.7.0/agentirc/server/skills → agentirc_cli-0.9.0/agentirc/clients/opencode}/__init__.py +0 -0
  88. {agentirc_cli-0.7.0/tests → agentirc_cli-0.9.0/agentirc/clients/opencode/skill}/__init__.py +0 -0
  89. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/observer.py +0 -0
  90. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/pidfile.py +0 -0
  91. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/commands.py +0 -0
  92. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/extensions/federation.md +0 -0
  93. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/extensions/history.md +0 -0
  94. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/message.py +0 -0
  95. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/protocol-index.md +0 -0
  96. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/protocol/replies.py +0 -0
  97. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/__main__.py +0 -0
  98. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/channel.py +0 -0
  99. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/client.py +0 -0
  100. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/config.py +0 -0
  101. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/ircd.py +0 -0
  102. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/remote_client.py +0 -0
  103. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/server_link.py +0 -0
  104. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/skill.py +0 -0
  105. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/agentirc/server/skills/history.py +0 -0
  106. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/agent-client.md +0 -0
  107. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/agent-harness-spec.md +0 -0
  108. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/ci.md +0 -0
  109. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/cli.md +0 -0
  110. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/configuration.md +0 -0
  111. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/context-management.md +0 -0
  112. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/irc-tools.md +0 -0
  113. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/overview.md +0 -0
  114. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/setup.md +0 -0
  115. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/supervisor.md +0 -0
  116. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/clients/claude/webhooks.md +0 -0
  117. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/codex-backend.md +0 -0
  118. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/design.md +0 -0
  119. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/docs-site.md +0 -0
  120. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/getting-started.md +0 -0
  121. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/layer1-core-irc.md +0 -0
  122. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/layer2-attention.md +0 -0
  123. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/layer3-skills.md +0 -0
  124. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/layer4-federation.md +0 -0
  125. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/layer5-agent-harness.md +0 -0
  126. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/publishing.md +0 -0
  127. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/server-architecture.md +0 -0
  128. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  129. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  130. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  131. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  132. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/01-pair-programming.md +0 -0
  133. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
  134. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/03-research-deep-dive.md +0 -0
  135. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/04-agent-delegation.md +0 -0
  136. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/05-benchmark-swarm.md +0 -0
  137. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/06-cross-server-ops.md +0 -0
  138. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/07-knowledge-pipeline.md +0 -0
  139. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/08-supervisor-intervention.md +0 -0
  140. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases/09-apps-as-agents.md +0 -0
  141. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/docs/use-cases-index.md +0 -0
  142. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/README.md +0 -0
  143. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/config.py +0 -0
  144. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/daemon.py +0 -0
  145. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/irc_transport.py +0 -0
  146. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/skill/SKILL.md +0 -0
  147. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/skill/irc_client.py +0 -0
  148. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/socket_server.py +0 -0
  149. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/packages/agent-harness/webhook.py +0 -0
  150. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  151. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  152. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
  153. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/conftest.py +0 -0
  154. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_agent_runner.py +0 -0
  155. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_channel.py +0 -0
  156. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_codex_daemon.py +0 -0
  157. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_connection.py +0 -0
  158. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_daemon.py +0 -0
  159. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_daemon_config.py +0 -0
  160. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_discovery.py +0 -0
  161. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_federation.py +0 -0
  162. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_history.py +0 -0
  163. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_integration_layer5.py +0 -0
  164. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_ipc.py +0 -0
  165. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_irc_transport.py +0 -0
  166. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_mentions.py +0 -0
  167. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_message.py +0 -0
  168. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_message_buffer.py +0 -0
  169. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_messaging.py +0 -0
  170. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_modes.py +0 -0
  171. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_skill_client.py +0 -0
  172. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_skills.py +0 -0
  173. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_socket_server.py +0 -0
  174. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_supervisor.py +0 -0
  175. {agentirc_cli-0.7.0 → agentirc_cli-0.9.0}/tests/test_webhook.py +0 -0
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.9.0] - 2026-03-25
8
+
9
+
10
+ ### Added
11
+
12
+ - GitHub Copilot agent harness (Phase 4) using github-copilot-sdk
13
+
14
+ ## [0.8.0] - 2026-03-24
15
+
16
+
17
+ ### Added
18
+
19
+ - OpenCode agent harness (Phase 3) — opencode acp over ACP/JSON-RPC/stdio
20
+
21
+
22
+ ### Changed
23
+
24
+ - CLI now supports --agent opencode for init, start, and skills install
25
+
7
26
  ## [0.7.0] - 2026-03-24
8
27
 
9
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: IRC protocol chatrooms for AI agents (and humans allowed)
5
5
  Project-URL: Homepage, https://github.com/OriNachum/agentirc
6
6
  Author: Ori Nachum
@@ -15,6 +15,8 @@ Requires-Python: >=3.12
15
15
  Requires-Dist: anthropic>=0.40
16
16
  Requires-Dist: claude-agent-sdk>=0.1
17
17
  Requires-Dist: pyyaml>=6.0
18
+ Provides-Extra: copilot
19
+ Requires-Dist: github-copilot-sdk; extra == 'copilot'
18
20
  Description-Content-Type: text/markdown
19
21
 
20
22
  <!-- markdownlint-disable MD033 MD041 -->
@@ -31,8 +33,10 @@ IRC Protocol ChatRooms for Agents (And humans allowed)
31
33
  <img src="https://img.shields.io/badge/protocol-IRC_RFC_2812-D97706?style=flat&labelColor=2D2B27" alt="IRC RFC 2812">
32
34
  <img src="https://img.shields.io/badge/license-MIT-D97706?style=flat&labelColor=2D2B27" alt="MIT License">
33
35
  <a href="https://github.com/OriNachum/agentirc/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/OriNachum/agentirc/tests.yml?style=flat&label=tests&labelColor=2D2B27" alt="Tests"></a>
36
+ <a href="https://github.com/OriNachum/AgentIRC/stargazers"><img src="https://img.shields.io/github/stars/OriNachum/AgentIRC?style=flat&label=%E2%AD%90%20stars&labelColor=2D2B27&color=D97706" alt="GitHub Stars"></a>
34
37
 
35
38
  <br><br>
39
+ <sub>If you find AgentIRC useful, <a href="https://github.com/OriNachum/AgentIRC/stargazers">give it a ⭐</a> — it helps others discover the project.</sub>
36
40
 
37
41
  <img width="800" alt="AgentIRC" src="https://github.com/user-attachments/assets/41401b9d-1da2-483b-b21f-3769d388f74d" />
38
42
 
@@ -12,8 +12,10 @@ IRC Protocol ChatRooms for Agents (And humans allowed)
12
12
  <img src="https://img.shields.io/badge/protocol-IRC_RFC_2812-D97706?style=flat&labelColor=2D2B27" alt="IRC RFC 2812">
13
13
  <img src="https://img.shields.io/badge/license-MIT-D97706?style=flat&labelColor=2D2B27" alt="MIT License">
14
14
  <a href="https://github.com/OriNachum/agentirc/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/OriNachum/agentirc/tests.yml?style=flat&label=tests&labelColor=2D2B27" alt="Tests"></a>
15
+ <a href="https://github.com/OriNachum/AgentIRC/stargazers"><img src="https://img.shields.io/github/stars/OriNachum/AgentIRC?style=flat&label=%E2%AD%90%20stars&labelColor=2D2B27&color=D97706" alt="GitHub Stars"></a>
15
16
 
16
17
  <br><br>
18
+ <sub>If you find AgentIRC useful, <a href="https://github.com/OriNachum/AgentIRC/stargazers">give it a ⭐</a> — it helps others discover the project.</sub>
17
19
 
18
20
  <img width="800" alt="AgentIRC" src="https://github.com/user-attachments/assets/41401b9d-1da2-483b-b21f-3769d388f74d" />
19
21
 
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -98,7 +98,7 @@ def main() -> None:
98
98
  init_parser = sub.add_parser("init", help="Register an agent for the current directory")
99
99
  init_parser.add_argument("--server", default=None, help="Server name prefix")
100
100
  init_parser.add_argument("--nick", default=None, help="Agent suffix (after server-)")
101
- init_parser.add_argument("--agent", default="claude", choices=["claude", "codex"], help="Agent backend")
101
+ init_parser.add_argument("--agent", default="claude", choices=["claude", "codex", "opencode", "copilot"], help="Agent backend")
102
102
  init_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
103
103
 
104
104
  # -- start subcommand --------------------------------------------------
@@ -137,8 +137,8 @@ def main() -> None:
137
137
  skills_sub = skills_parser.add_subparsers(dest="skills_command")
138
138
  skills_install = skills_sub.add_parser("install", help="Install IRC skill for an agent")
139
139
  skills_install.add_argument(
140
- "target", choices=["claude", "codex", "all"],
141
- help="Target agent: claude, codex, or all",
140
+ "target", choices=["claude", "codex", "opencode", "copilot", "all"],
141
+ help="Target agent: claude, codex, opencode, copilot, or all",
142
142
  )
143
143
 
144
144
  args = parser.parse_args()
@@ -355,6 +355,22 @@ def _cmd_init(args: argparse.Namespace) -> None:
355
355
  directory=os.getcwd(),
356
356
  channels=["#general"],
357
357
  )
358
+ elif args.agent == "opencode":
359
+ from agentirc.clients.opencode.config import AgentConfig as OpenCodeAgentConfig
360
+ agent = OpenCodeAgentConfig(
361
+ nick=full_nick,
362
+ agent="opencode",
363
+ directory=os.getcwd(),
364
+ channels=["#general"],
365
+ )
366
+ elif args.agent == "copilot":
367
+ from agentirc.clients.copilot.config import AgentConfig as CopilotAgentConfig
368
+ agent = CopilotAgentConfig(
369
+ nick=full_nick,
370
+ agent="copilot",
371
+ directory=os.getcwd(),
372
+ channels=["#general"],
373
+ )
358
374
  else:
359
375
  agent = AgentConfig(
360
376
  nick=full_nick,
@@ -435,6 +451,32 @@ async def _run_single_agent(config: DaemonConfig, agent: AgentConfig) -> None:
435
451
  agents=config.agents,
436
452
  )
437
453
  daemon = CodexDaemon(codex_config, agent)
454
+ elif backend == "opencode":
455
+ from agentirc.clients.opencode.daemon import OpenCodeDaemon
456
+ from agentirc.clients.opencode.config import (
457
+ DaemonConfig as OpenCodeDaemonConfig,
458
+ )
459
+ # Re-load config through OpenCode module for correct supervisor defaults
460
+ opencode_config = OpenCodeDaemonConfig(
461
+ server=config.server,
462
+ webhooks=config.webhooks,
463
+ buffer_size=config.buffer_size,
464
+ agents=config.agents,
465
+ )
466
+ daemon = OpenCodeDaemon(opencode_config, agent)
467
+ elif backend == "copilot":
468
+ from agentirc.clients.copilot.daemon import CopilotDaemon
469
+ from agentirc.clients.copilot.config import (
470
+ DaemonConfig as CopilotDaemonConfig,
471
+ )
472
+ # Re-load config through Copilot module for correct supervisor defaults
473
+ copilot_config = CopilotDaemonConfig(
474
+ server=config.server,
475
+ webhooks=config.webhooks,
476
+ buffer_size=config.buffer_size,
477
+ agents=config.agents,
478
+ )
479
+ daemon = CopilotDaemon(copilot_config, agent)
438
480
  else:
439
481
  from agentirc.clients.claude.daemon import AgentDaemon
440
482
  daemon = AgentDaemon(config, agent)
@@ -737,9 +779,45 @@ def _install_skill_codex() -> None:
737
779
  print(f"Installed Codex skill: {dest}")
738
780
 
739
781
 
782
+ def _get_bundled_opencode_skill_path() -> str:
783
+ """Return the path to the bundled OpenCode SKILL.md in the installed package."""
784
+ import agentirc
785
+ return os.path.join(os.path.dirname(agentirc.__file__), "clients", "opencode", "skill", "SKILL.md")
786
+
787
+
788
+ def _install_skill_opencode() -> None:
789
+ """Install IRC skill for OpenCode."""
790
+ src = _get_bundled_opencode_skill_path()
791
+ dest_dir = os.path.expanduser("~/.opencode/skills/agentirc-irc")
792
+ dest = os.path.join(dest_dir, "SKILL.md")
793
+
794
+ os.makedirs(dest_dir, exist_ok=True)
795
+ import shutil
796
+ shutil.copy2(src, dest)
797
+ print(f"Installed OpenCode skill: {dest}")
798
+
799
+
800
+ def _get_bundled_copilot_skill_path() -> str:
801
+ """Return the path to the bundled Copilot SKILL.md in the installed package."""
802
+ import agentirc
803
+ return os.path.join(os.path.dirname(agentirc.__file__), "clients", "copilot", "skill", "SKILL.md")
804
+
805
+
806
+ def _install_skill_copilot() -> None:
807
+ """Install IRC skill for GitHub Copilot."""
808
+ src = _get_bundled_copilot_skill_path()
809
+ dest_dir = os.path.expanduser("~/.copilot_skills/agentirc-irc")
810
+ dest = os.path.join(dest_dir, "SKILL.md")
811
+
812
+ os.makedirs(dest_dir, exist_ok=True)
813
+ import shutil
814
+ shutil.copy2(src, dest)
815
+ print(f"Installed Copilot skill: {dest}")
816
+
817
+
740
818
  def _cmd_skills(args: argparse.Namespace) -> None:
741
819
  if not hasattr(args, "skills_command") or args.skills_command != "install":
742
- print("Usage: agentirc skills install <claude|codex|all>", file=sys.stderr)
820
+ print("Usage: agentirc skills install <claude|codex|opencode|copilot|all>", file=sys.stderr)
743
821
  sys.exit(1)
744
822
 
745
823
  target = args.target
@@ -748,7 +826,11 @@ def _cmd_skills(args: argparse.Namespace) -> None:
748
826
  _install_skill_claude()
749
827
  if target in ("codex", "all"):
750
828
  _install_skill_codex()
829
+ if target in ("opencode", "all"):
830
+ _install_skill_opencode()
831
+ if target in ("copilot", "all"):
832
+ _install_skill_copilot()
751
833
 
752
834
  if target == "all":
753
- print("\nSkills installed for both Claude Code and Codex.")
835
+ print("\nSkills installed for Claude Code, Codex, OpenCode, and Copilot.")
754
836
  print(f"\nSet AGENTIRC_NICK in your shell profile to enable the skill.")
@@ -10,6 +10,7 @@ import asyncio
10
10
  import logging
11
11
  import os
12
12
  import time
13
+ from collections import deque
13
14
  from typing import Any
14
15
 
15
16
  from agentirc.clients.codex.config import DaemonConfig, AgentConfig
@@ -56,8 +57,10 @@ class CodexDaemon:
56
57
  self._agent_runner: CodexAgentRunner | None = None
57
58
  self._supervisor: CodexSupervisor | None = None
58
59
 
59
- # Track mention context for relaying agent responses
60
- self._last_mention_target: str | None = None
60
+ # FIFO queue of relay targets each @mention enqueues a target,
61
+ # each agent response dequeues one, ensuring correct routing even
62
+ # when multiple mentions arrive while the agent is busy.
63
+ self._mention_targets: deque[str] = deque()
61
64
 
62
65
  # Crash-recovery state
63
66
  self._crash_times: list[float] = []
@@ -177,8 +180,8 @@ class CodexDaemon:
177
180
  Formats a prompt and enqueues it so the Codex session picks it up.
178
181
  """
179
182
  if self._agent_runner and self._agent_runner.is_running():
180
- # Track where to relay the response
181
- self._last_mention_target = target if target.startswith("#") else sender
183
+ # Enqueue relay target (FIFO matches prompt queue order)
184
+ self._mention_targets.append(target if target.startswith("#") else sender)
182
185
  if target.startswith("#"):
183
186
  prompt = f"[IRC @mention in {target}] <{sender}> {text}"
184
187
  else:
@@ -187,8 +190,9 @@ class CodexDaemon:
187
190
 
188
191
  async def _on_agent_message(self, msg: dict) -> None:
189
192
  """Relay agent text to IRC and feed to supervisor."""
190
- # Relay text response to the channel/user that triggered the mention
191
- if self._transport and self._last_mention_target:
193
+ # Dequeue the relay target that corresponds to this turn
194
+ relay_target = self._mention_targets.popleft() if self._mention_targets else None
195
+ if self._transport and relay_target:
192
196
  content = msg.get("content", [])
193
197
  for item in content:
194
198
  if item.get("type") == "text":
@@ -199,7 +203,7 @@ class CodexDaemon:
199
203
  line = line.strip()
200
204
  if line:
201
205
  await self._transport.send_privmsg(
202
- self._last_mention_target, line
206
+ relay_target, line
203
207
  )
204
208
 
205
209
  if self._supervisor:
@@ -423,14 +427,19 @@ class CodexDaemon:
423
427
  path = msg.get("path", "")
424
428
  if not path:
425
429
  return make_response(req_id, ok=False, error="Missing 'path'")
430
+ new_cwd = os.path.abspath(path)
431
+ if not os.path.isdir(new_cwd):
432
+ return make_response(req_id, ok=False, error=f"Not a directory: {new_cwd}")
433
+ # Update the daemon's working directory
434
+ self.agent.directory = new_cwd
426
435
  # Check for AGENTS.md (Codex equivalent of CLAUDE.md)
427
- agents_md = os.path.join(path, "AGENTS.md")
436
+ agents_md = os.path.join(new_cwd, "AGENTS.md")
428
437
  agents_md_content = None
429
438
  if os.path.isfile(agents_md):
430
439
  with open(agents_md) as f:
431
440
  agents_md_content = f.read()
432
441
  return make_response(req_id, ok=True, data={
433
- "directory": path,
442
+ "directory": new_cwd,
434
443
  "agents_md": agents_md_content,
435
444
  })
436
445
 
@@ -0,0 +1,165 @@
1
+ """Copilot agent runner — manages a GitHub Copilot SDK session."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from typing import Any, Awaitable, Callable
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class CopilotAgentRunner:
13
+ """Manages a GitHub Copilot SDK session for the agentirc daemon."""
14
+
15
+ def __init__(
16
+ self,
17
+ model: str,
18
+ directory: str,
19
+ system_prompt: str = "",
20
+ skill_directories: list[str] | None = None,
21
+ on_exit: Callable[[int], Awaitable[None]] | None = None,
22
+ on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
23
+ ) -> None:
24
+ self.model = model
25
+ self.directory = directory
26
+ self.system_prompt = system_prompt
27
+ self.skill_directories = skill_directories or []
28
+ self.on_exit = on_exit
29
+ self.on_message = on_message
30
+
31
+ self._client: Any = None
32
+ self._session: Any = None
33
+ self._session_id: str | None = None
34
+ self._running = False
35
+ self._stopping = False
36
+ self._prompt_queue: asyncio.Queue[str] = asyncio.Queue()
37
+ self._task: asyncio.Task | None = None
38
+
39
+ def is_running(self) -> bool:
40
+ return self._running
41
+
42
+ @property
43
+ def session_id(self) -> str | None:
44
+ return self._session_id
45
+
46
+ async def start(self, initial_prompt: str = "") -> None:
47
+ """Start the Copilot client and create a session."""
48
+ self._stopping = False
49
+
50
+ # Lazy import — github-copilot-sdk is only needed at runtime
51
+ from copilot import CopilotClient, PermissionHandler, SubprocessConfig
52
+
53
+ # Create and start the CopilotClient (spawns copilot CLI process)
54
+ subprocess_config = SubprocessConfig(cwd=self.directory)
55
+ self._client = CopilotClient(config=subprocess_config)
56
+ await self._client.start()
57
+
58
+ # Create a session with model and permissions.
59
+ # Wrap in try/except so a partial start doesn't leak the CLI process.
60
+ try:
61
+ session_kwargs: dict[str, Any] = {
62
+ "on_permission_request": PermissionHandler.approve_all,
63
+ "model": self.model,
64
+ }
65
+ if self.system_prompt:
66
+ session_kwargs["system_message"] = {"content": self.system_prompt}
67
+ if self.skill_directories:
68
+ session_kwargs["skill_directories"] = self.skill_directories
69
+
70
+ self._session = await self._client.create_session(**session_kwargs)
71
+ except Exception:
72
+ await self._client.stop()
73
+ self._client = None
74
+ raise
75
+ self._session_id = getattr(self._session, "id", None)
76
+ self._running = True
77
+
78
+ logger.info(
79
+ "CopilotAgentRunner started (model=%s, session=%s)",
80
+ self.model, self._session_id,
81
+ )
82
+
83
+ # Start the prompt processing loop
84
+ self._task = asyncio.create_task(self._prompt_loop())
85
+
86
+ if initial_prompt:
87
+ await self.send_prompt(initial_prompt)
88
+
89
+ async def stop(self) -> None:
90
+ """Stop the Copilot session and client."""
91
+ self._stopping = True
92
+ self._running = False
93
+
94
+ if self._task:
95
+ self._task.cancel()
96
+ try:
97
+ await self._task
98
+ except asyncio.CancelledError:
99
+ pass
100
+ self._task = None
101
+
102
+ if self._session:
103
+ try:
104
+ await self._session.destroy()
105
+ except Exception:
106
+ logger.debug("Session destroy error (ignoring)", exc_info=True)
107
+ self._session = None
108
+
109
+ if self._client:
110
+ try:
111
+ await self._client.stop()
112
+ except Exception:
113
+ logger.debug("Client stop error (ignoring)", exc_info=True)
114
+ self._client = None
115
+
116
+ async def send_prompt(self, text: str) -> None:
117
+ """Queue a prompt for the agent."""
118
+ await self._prompt_queue.put(text)
119
+
120
+ # ------------------------------------------------------------------
121
+ # Internal: prompt processing loop
122
+ # ------------------------------------------------------------------
123
+
124
+ async def _prompt_loop(self) -> None:
125
+ """Process queued prompts one at a time using send_and_wait."""
126
+ try:
127
+ while self._running:
128
+ text = await self._prompt_queue.get()
129
+ if not self._running or self._session is None:
130
+ break
131
+
132
+ try:
133
+ response = await self._session.send_and_wait(
134
+ text, timeout=120.0
135
+ )
136
+
137
+ # Extract text from SDK response
138
+ content_text = ""
139
+ if response is not None:
140
+ if hasattr(response, "data") and hasattr(response.data, "content"):
141
+ content_text = response.data.content or ""
142
+ elif isinstance(response, dict):
143
+ content_text = response.get("data", {}).get("content", "")
144
+
145
+ if content_text and self.on_message:
146
+ msg_dict = {
147
+ "type": "assistant",
148
+ "model": self.model,
149
+ "content": [{"type": "text", "text": content_text}],
150
+ }
151
+ await self.on_message(msg_dict)
152
+
153
+ except Exception:
154
+ logger.exception("Copilot session turn error")
155
+ if not self._stopping:
156
+ self._running = False
157
+ if self.on_exit:
158
+ await self.on_exit(1)
159
+ return
160
+
161
+ except asyncio.CancelledError:
162
+ pass
163
+
164
+ if not self._stopping and self.on_exit:
165
+ await self.on_exit(0)
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ import tempfile
6
+ from dataclasses import asdict, dataclass, field
7
+ from pathlib import Path
8
+
9
+ import yaml
10
+
11
+
12
+ @dataclass
13
+ class ServerConnConfig:
14
+ """IRC server connection settings."""
15
+ name: str = "agentirc"
16
+ host: str = "localhost"
17
+ port: int = 6667
18
+
19
+
20
+ @dataclass
21
+ class SupervisorConfig:
22
+ """Supervisor sub-agent settings."""
23
+ model: str = "gpt-4.1"
24
+ window_size: int = 20
25
+ eval_interval: int = 5
26
+ escalation_threshold: int = 3
27
+
28
+
29
+ @dataclass
30
+ class WebhookConfig:
31
+ """Webhook alerting settings."""
32
+ url: str | None = None
33
+ irc_channel: str = "#alerts"
34
+ events: list[str] = field(default_factory=lambda: [
35
+ "agent_spiraling", "agent_error", "agent_question",
36
+ "agent_timeout", "agent_complete",
37
+ ])
38
+
39
+
40
+ @dataclass
41
+ class AgentConfig:
42
+ """Per-agent settings."""
43
+ nick: str = ""
44
+ agent: str = "copilot"
45
+ directory: str = "."
46
+ channels: list[str] = field(default_factory=lambda: ["#general"])
47
+ model: str = "gpt-4.1"
48
+
49
+
50
+ @dataclass
51
+ class DaemonConfig:
52
+ """Top-level daemon configuration."""
53
+ server: ServerConnConfig = field(default_factory=ServerConnConfig)
54
+ supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
55
+ webhooks: WebhookConfig = field(default_factory=WebhookConfig)
56
+ buffer_size: int = 500
57
+ agents: list[AgentConfig] = field(default_factory=list)
58
+
59
+ def get_agent(self, nick: str) -> AgentConfig | None:
60
+ for agent in self.agents:
61
+ if agent.nick == nick:
62
+ return agent
63
+ return None
64
+
65
+
66
+ def load_config(path: str | Path) -> DaemonConfig:
67
+ """Load daemon config from a YAML file."""
68
+ with open(path) as f:
69
+ raw = yaml.safe_load(f) or {}
70
+
71
+ server = ServerConnConfig(**raw.get("server", {}))
72
+ supervisor = SupervisorConfig(**raw.get("supervisor", {}))
73
+
74
+ webhooks = WebhookConfig(**raw.get("webhooks", {}))
75
+
76
+ agents = []
77
+ for agent_raw in raw.get("agents", []):
78
+ agents.append(AgentConfig(**agent_raw))
79
+
80
+ return DaemonConfig(
81
+ server=server,
82
+ supervisor=supervisor,
83
+ webhooks=webhooks,
84
+ buffer_size=raw.get("buffer_size", 500),
85
+ agents=agents,
86
+ )
87
+
88
+
89
+ def sanitize_agent_name(dirname: str) -> str:
90
+ """Sanitize a directory name into a valid agent/server name.
91
+
92
+ Lowercase, replace non-alphanumeric chars with hyphens, collapse
93
+ multiple hyphens, strip leading/trailing hyphens.
94
+ """
95
+ name = dirname.lower()
96
+ name = re.sub(r"[^a-z0-9-]", "-", name)
97
+ name = re.sub(r"-+", "-", name)
98
+ name = name.strip("-")
99
+ if not name:
100
+ raise ValueError(f"sanitized name is empty for input: {dirname!r}")
101
+ return name
102
+
103
+
104
+ def load_config_or_default(path: str | Path) -> DaemonConfig:
105
+ """Load config from path, returning a default DaemonConfig if file is missing."""
106
+ path = Path(path)
107
+ if not path.exists():
108
+ return DaemonConfig()
109
+ return load_config(path)
110
+
111
+
112
+ def save_config(path: str | Path, config: DaemonConfig) -> None:
113
+ """Serialize a DaemonConfig to YAML and write atomically."""
114
+ path = Path(path)
115
+ path.parent.mkdir(parents=True, exist_ok=True)
116
+
117
+ data = asdict(config)
118
+ yaml_str = yaml.dump(data, default_flow_style=False)
119
+
120
+ # Atomic write: write to temp file in same dir, then rename
121
+ fd, tmp_path = tempfile.mkstemp(
122
+ dir=str(path.parent), suffix=".yaml.tmp",
123
+ )
124
+ try:
125
+ with os.fdopen(fd, "w") as f:
126
+ f.write(yaml_str)
127
+ os.replace(tmp_path, str(path))
128
+ except BaseException:
129
+ # Clean up temp file on failure
130
+ try:
131
+ os.unlink(tmp_path)
132
+ except OSError:
133
+ pass
134
+ raise
135
+
136
+
137
+ def add_agent_to_config(
138
+ path: str | Path,
139
+ agent: AgentConfig,
140
+ server_name: str | None = None,
141
+ ) -> DaemonConfig:
142
+ """Add an agent to a config file, creating it if needed.
143
+
144
+ If server_name is provided, updates config.server.name.
145
+ Raises ValueError if an agent with the same nick already exists.
146
+ """
147
+ config = load_config_or_default(path)
148
+
149
+ if server_name is not None:
150
+ config.server.name = server_name
151
+
152
+ # Check for nick collision
153
+ for existing in config.agents:
154
+ if existing.nick == agent.nick:
155
+ raise ValueError(
156
+ f"agent with nick {agent.nick!r} already exists in config"
157
+ )
158
+
159
+ config.agents.append(agent)
160
+ save_config(path, config)
161
+ return config