agentirc-cli 0.18.0__tar.gz → 0.20.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.
- agentirc_cli-0.20.0/.flake8 +19 -0
- agentirc_cli-0.20.0/.github/workflows/security-checks.yml +99 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.gitignore +6 -0
- agentirc_cli-0.20.0/.pre-commit-config.yaml +42 -0
- agentirc_cli-0.20.0/.pylintrc +77 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CHANGELOG.md +36 -1
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/PKG-INFO +1 -1
- agentirc_cli-0.20.0/SECURITY.md +39 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/daemon.py +89 -2
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/irc_transport.py +12 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/message_buffer.py +20 -1
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/daemon.py +89 -2
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/irc_transport.py +12 -0
- {agentirc_cli-0.18.0/agentirc/clients/codex → agentirc_cli-0.20.0/agentirc/clients/claude}/message_buffer.py +20 -1
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/daemon.py +89 -2
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/irc_transport.py +12 -0
- {agentirc_cli-0.18.0/agentirc/clients/claude → agentirc_cli-0.20.0/agentirc/clients/codex}/message_buffer.py +20 -1
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/daemon.py +89 -2
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/irc_transport.py +12 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/message_buffer.py +20 -1
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/commands.py +8 -0
- agentirc_cli-0.20.0/agentirc/protocol/extensions/threads.md +296 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/ircd.py +2 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/server_link.py +90 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skill.py +3 -0
- agentirc_cli-0.20.0/agentirc/server/skills/threads.py +598 -0
- agentirc_cli-0.20.0/agentirc/server/thread_store.py +50 -0
- agentirc_cli-0.20.0/docs/SECURITY.md +120 -0
- agentirc_cli-0.20.0/docs/superpowers/plans/2026-04-02-conversation-threads.md +1885 -0
- agentirc_cli-0.20.0/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +326 -0
- agentirc_cli-0.20.0/docs/threads.md +200 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/daemon.py +96 -2
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/irc_transport.py +12 -0
- agentirc_cli-0.20.0/packages/agent-harness/message_buffer.py +65 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/pyproject.toml +40 -1
- agentirc_cli-0.20.0/sonar-project.properties +27 -0
- agentirc_cli-0.20.0/tests/test_thread_buffer.py +56 -0
- agentirc_cli-0.20.0/tests/test_threads.py +383 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/uv.lock +901 -1
- agentirc_cli-0.18.0/packages/agent-harness/message_buffer.py +0 -46
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/.pr_agent.toml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CLAUDE.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/CNAME +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/Gemfile +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/Gemfile.lock +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/LICENSE +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/README.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_config.yml +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/__main__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/cli.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/supervisor.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/acp/webhook.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/__main__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/supervisor.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/claude/webhook.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/supervisor.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/codex/webhook.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/clients/copilot/webhook.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/credentials.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/learn_prompt.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/mesh_config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/observer.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/collector.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/model.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/renderer_text.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/renderer_web.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/overview/web/style.css +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/persistence.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/pidfile.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/federation.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/history.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/extensions/tags.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/message.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/protocol-index.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/protocol/replies.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/__main__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/channel.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/remote_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/room_store.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/rooms_util.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/history.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/server/skills/rooms.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/agentirc/skills/agentirc/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agent-client.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agent-harness-spec.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/ci.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/cli.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/codex-backend.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/copilot-backend.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/design.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/docs-site.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/getting-started.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/grow-your-agent.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/harness-conformance.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer1-core-irc.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer2-attention.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer3-skills.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer4-federation.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/layer5-agent-harness.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/ops-tooling.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/publishing.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/rooms.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/server-architecture.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases/10-grow-your-agent.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/docs/use-cases-index.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/index.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/skills/agentirc/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/__init__.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/conftest.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_channel.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_connection.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_discovery.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_federation.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_history.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_ipc.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_mentions.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_message.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_messaging.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_modes.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_model.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_overview_web.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_persistence.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_skill_client.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_skills.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_socket_server.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_supervisor.py +0 -0
- {agentirc_cli-0.18.0 → agentirc_cli-0.20.0}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
max-line-length = 100
|
|
3
|
+
exclude =
|
|
4
|
+
.git,
|
|
5
|
+
__pycache__,
|
|
6
|
+
.venv,
|
|
7
|
+
dist,
|
|
8
|
+
build,
|
|
9
|
+
.eggs,
|
|
10
|
+
packages
|
|
11
|
+
extend-ignore =
|
|
12
|
+
E203,
|
|
13
|
+
W503,
|
|
14
|
+
S101
|
|
15
|
+
per-file-ignores =
|
|
16
|
+
agentirc/credentials.py:S603,S607
|
|
17
|
+
agentirc/persistence.py:S603,S607
|
|
18
|
+
agentirc/cli.py:S603,S607
|
|
19
|
+
tests/*:S101
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Security Checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 0 * * 0' # Weekly on Sunday at midnight
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
security-scans:
|
|
17
|
+
name: Security Scans
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- uses: astral-sh/setup-uv@v4
|
|
25
|
+
|
|
26
|
+
- run: uv python install 3.12
|
|
27
|
+
|
|
28
|
+
- run: uv sync
|
|
29
|
+
|
|
30
|
+
- name: Run Bandit
|
|
31
|
+
run: uv run bandit -r agentirc/ -f json -o bandit-results.json -c pyproject.toml
|
|
32
|
+
continue-on-error: true
|
|
33
|
+
|
|
34
|
+
- name: Run Pylint
|
|
35
|
+
run: uv run pylint agentirc/ --rcfile=.pylintrc --output-format=json:pylint-results.json,text
|
|
36
|
+
continue-on-error: true
|
|
37
|
+
|
|
38
|
+
- name: Run Safety dependency check
|
|
39
|
+
run: uv run safety check --full-report --output json > safety-results.json
|
|
40
|
+
continue-on-error: true
|
|
41
|
+
|
|
42
|
+
- name: Upload Security Results
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: security-results
|
|
46
|
+
path: |
|
|
47
|
+
bandit-results.json
|
|
48
|
+
pylint-results.json
|
|
49
|
+
safety-results.json
|
|
50
|
+
|
|
51
|
+
- name: Run test coverage
|
|
52
|
+
run: |
|
|
53
|
+
uv run pytest --cov=agentirc --cov-report=xml:coverage.xml --cov-report=term -v
|
|
54
|
+
continue-on-error: true
|
|
55
|
+
|
|
56
|
+
- name: SonarCloud Scan
|
|
57
|
+
uses: SonarSource/sonarqube-scan-action@v7
|
|
58
|
+
env:
|
|
59
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
60
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
61
|
+
with:
|
|
62
|
+
args: >
|
|
63
|
+
-Dsonar.projectKey=OriNachum_AgentIRC
|
|
64
|
+
-Dsonar.organization=${{ github.repository_owner }}
|
|
65
|
+
-Dsonar.python.coverage.reportPaths=coverage.xml
|
|
66
|
+
-Dsonar.python.bandit.reportPaths=bandit-results.json
|
|
67
|
+
-Dsonar.python.pylint.reportPaths=pylint-results.json
|
|
68
|
+
|
|
69
|
+
dependency-review:
|
|
70
|
+
name: Dependency Review
|
|
71
|
+
if: github.event_name == 'pull_request'
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
contents: read
|
|
75
|
+
pull-requests: write
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
|
|
79
|
+
- name: Dependency Review
|
|
80
|
+
uses: actions/dependency-review-action@v4
|
|
81
|
+
with:
|
|
82
|
+
fail-on-severity: high
|
|
83
|
+
|
|
84
|
+
codeql-analysis:
|
|
85
|
+
name: CodeQL Analysis
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
security-events: write
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
|
|
93
|
+
- name: Initialize CodeQL
|
|
94
|
+
uses: github/codeql-action/init@v3
|
|
95
|
+
with:
|
|
96
|
+
languages: python
|
|
97
|
+
|
|
98
|
+
- name: Perform CodeQL Analysis
|
|
99
|
+
uses: github/codeql-action/analyze@v3
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.4.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
- id: check-ast
|
|
10
|
+
- id: detect-private-key
|
|
11
|
+
|
|
12
|
+
- repo: local
|
|
13
|
+
hooks:
|
|
14
|
+
- id: flake8
|
|
15
|
+
name: flake8
|
|
16
|
+
entry: uv run flake8 --config=.flake8
|
|
17
|
+
language: system
|
|
18
|
+
types: [python]
|
|
19
|
+
|
|
20
|
+
- id: isort
|
|
21
|
+
name: isort
|
|
22
|
+
entry: uv run isort
|
|
23
|
+
language: system
|
|
24
|
+
types: [python]
|
|
25
|
+
|
|
26
|
+
- id: black
|
|
27
|
+
name: black
|
|
28
|
+
entry: uv run black
|
|
29
|
+
language: system
|
|
30
|
+
types: [python]
|
|
31
|
+
|
|
32
|
+
- id: bandit
|
|
33
|
+
name: bandit
|
|
34
|
+
entry: uv run bandit -c pyproject.toml
|
|
35
|
+
language: system
|
|
36
|
+
types: [python]
|
|
37
|
+
|
|
38
|
+
- id: pylint
|
|
39
|
+
name: pylint
|
|
40
|
+
entry: uv run pylint --rcfile=.pylintrc
|
|
41
|
+
language: system
|
|
42
|
+
types: [python]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[MASTER]
|
|
2
|
+
ignore=CVS
|
|
3
|
+
persistent=yes
|
|
4
|
+
load-plugins=
|
|
5
|
+
|
|
6
|
+
[MESSAGES CONTROL]
|
|
7
|
+
disable=
|
|
8
|
+
C0114, # missing-module-docstring
|
|
9
|
+
C0115, # missing-class-docstring
|
|
10
|
+
C0116, # missing-function-docstring
|
|
11
|
+
C0301, # line-too-long (handled by black)
|
|
12
|
+
C0303, # trailing-whitespace
|
|
13
|
+
R0801, # duplicate-code (assimilai pattern: backends share identical files)
|
|
14
|
+
R0903, # too-few-public-methods
|
|
15
|
+
R0913, # too-many-arguments
|
|
16
|
+
W0511, # fixme
|
|
17
|
+
W0718, # broad-exception-caught (async server/daemon must catch broadly)
|
|
18
|
+
W0719, # broad-exception-raised
|
|
19
|
+
W1202, # logging-format-interpolation
|
|
20
|
+
W1203, # logging-fstring-interpolation
|
|
21
|
+
|
|
22
|
+
[REPORTS]
|
|
23
|
+
output-format=text
|
|
24
|
+
reports=yes
|
|
25
|
+
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
|
26
|
+
|
|
27
|
+
[VARIABLES]
|
|
28
|
+
init-import=no
|
|
29
|
+
dummy-variables-rgx=_$|dummy
|
|
30
|
+
|
|
31
|
+
[FORMAT]
|
|
32
|
+
max-line-length=100
|
|
33
|
+
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
|
34
|
+
single-line-if-stmt=no
|
|
35
|
+
indent-string=' '
|
|
36
|
+
max-module-lines=1000
|
|
37
|
+
|
|
38
|
+
[SIMILARITIES]
|
|
39
|
+
min-similarity-lines=8
|
|
40
|
+
ignore-comments=yes
|
|
41
|
+
ignore-docstrings=yes
|
|
42
|
+
ignore-imports=yes
|
|
43
|
+
|
|
44
|
+
[BASIC]
|
|
45
|
+
good-names=i,j,k,ex,Run,_,e,f,fd,ws,ts,ok,ch,ip,op
|
|
46
|
+
bad-names=foo,bar,baz,toto,tutu,tata
|
|
47
|
+
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
|
48
|
+
variable-rgx=[a-z_][a-z0-9_]{1,30}$
|
|
49
|
+
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
|
50
|
+
attr-rgx=[a-z_][a-z0-9_]{1,30}$
|
|
51
|
+
argument-rgx=[a-z_][a-z0-9_]{1,30}$
|
|
52
|
+
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
|
53
|
+
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
|
54
|
+
no-docstring-rgx=^_
|
|
55
|
+
|
|
56
|
+
[DESIGN]
|
|
57
|
+
max-args=10
|
|
58
|
+
ignored-argument-names=_.*
|
|
59
|
+
max-locals=15
|
|
60
|
+
max-returns=6
|
|
61
|
+
max-branches=12
|
|
62
|
+
max-statements=50
|
|
63
|
+
max-parents=7
|
|
64
|
+
max-attributes=15
|
|
65
|
+
min-public-methods=2
|
|
66
|
+
max-public-methods=20
|
|
67
|
+
|
|
68
|
+
[CLASSES]
|
|
69
|
+
defining-attr-methods=__init__,__new__,setUp
|
|
70
|
+
valid-classmethod-first-arg=cls
|
|
71
|
+
valid-metaclass-classmethod-first-arg=mcs
|
|
72
|
+
|
|
73
|
+
[IMPORTS]
|
|
74
|
+
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
|
75
|
+
|
|
76
|
+
[EXCEPTIONS]
|
|
77
|
+
overgeneral-exceptions=builtins.BaseException
|
|
@@ -4,11 +4,46 @@ 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.
|
|
7
|
+
## [0.20.0] - 2026-04-03
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- Bandit SAST security scanning
|
|
13
|
+
- Pylint static code analysis
|
|
14
|
+
- Safety dependency vulnerability scanning
|
|
15
|
+
- CodeQL semantic analysis (GitHub-native)
|
|
16
|
+
- SonarCloud code quality and security integration
|
|
17
|
+
- Pre-commit hooks (flake8+bandit+bugbear, isort, black, pylint, detect-private-key)
|
|
18
|
+
- Security CI workflow (security-checks.yml)
|
|
19
|
+
- Dependency Review on PRs (fails on high severity)
|
|
20
|
+
- SECURITY.md vulnerability disclosure policy
|
|
21
|
+
- docs/SECURITY.md contributor security guidelines
|
|
22
|
+
- Code coverage enforcement in CI
|
|
23
|
+
|
|
24
|
+
## [0.19.0] - 2026-04-03
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Conversation threads — inline sub-conversations with [thread:name] prefix
|
|
30
|
+
- Breakout channel promotion from threads
|
|
31
|
+
- Thread-scoped agent context on @mention
|
|
32
|
+
- S2S federation for thread messages
|
|
33
|
+
- JSON persistence for threads across restarts
|
|
34
|
+
- Thread support in all 4 agent backends (claude, codex, copilot, acp)
|
|
35
|
+
|
|
36
|
+
## [0.18.0] - 2026-04-03
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Conversation threads — inline sub-conversations with [thread:name] prefix
|
|
42
|
+
- Breakout channel promotion from threads
|
|
43
|
+
- Thread-scoped agent context on @mention
|
|
44
|
+
- S2S federation for thread messages
|
|
45
|
+
- JSON persistence for threads across restarts
|
|
46
|
+
- Thread support in all 4 agent backends (claude, codex, copilot, acp)
|
|
12
47
|
- S2S link auto-reconnect with exponential backoff (5s to 120s)
|
|
13
48
|
- Declarative mesh.yaml configuration for multi-machine setup
|
|
14
49
|
- Cross-platform auto-start persistence (systemd, launchd, Windows schtasks)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentirc-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: 🌱 The space your agents deserve — an autonomous agent mesh where AI agents live, collaborate, and grow
|
|
5
5
|
Project-URL: Homepage, https://github.com/OriNachum/agentirc
|
|
6
6
|
Author: Ori Nachum
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security vulnerability in AgentIRC, please report it responsibly.
|
|
6
|
+
|
|
7
|
+
**Do not** open a public GitHub issue for security vulnerabilities.
|
|
8
|
+
|
|
9
|
+
### How to Report
|
|
10
|
+
|
|
11
|
+
Please report security issues privately using one of the following methods:
|
|
12
|
+
|
|
13
|
+
- **GitHub Security Advisories**: [Report a vulnerability privately](../../security/advisories/new)
|
|
14
|
+
- **Email**: Contact the maintainer directly
|
|
15
|
+
|
|
16
|
+
Include:
|
|
17
|
+
|
|
18
|
+
- A description of the vulnerability
|
|
19
|
+
- Steps to reproduce the issue
|
|
20
|
+
- The potential impact
|
|
21
|
+
|
|
22
|
+
### Response Timeline
|
|
23
|
+
|
|
24
|
+
- **Acknowledgment**: Within 48 hours
|
|
25
|
+
- **Fix timeline**: Within 7 days of acknowledgment
|
|
26
|
+
- **Disclosure**: Coordinated with the reporter after a fix is available
|
|
27
|
+
|
|
28
|
+
## Security Measures
|
|
29
|
+
|
|
30
|
+
This project uses automated security scanning:
|
|
31
|
+
|
|
32
|
+
- **Bandit** — Python security vulnerability detection
|
|
33
|
+
- **Pylint** — Static code analysis
|
|
34
|
+
- **CodeQL** — GitHub-native semantic analysis
|
|
35
|
+
- **SonarCloud** — Comprehensive code quality and security
|
|
36
|
+
- **Safety** — Dependency vulnerability scanning
|
|
37
|
+
- **Dependency Review** — PR-level dependency checks
|
|
38
|
+
|
|
39
|
+
See [docs/SECURITY.md](docs/SECURITY.md) for full details on the security toolchain and contributor guidelines.
|
|
@@ -273,7 +273,7 @@ class ACPDaemon:
|
|
|
273
273
|
def _on_mention(self, target: str, sender: str, text: str) -> None:
|
|
274
274
|
"""Called by IRCTransport when the agent is @mentioned or DM'd.
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
When the mention is inside a thread, provides thread-scoped context.
|
|
277
277
|
"""
|
|
278
278
|
if self._paused:
|
|
279
279
|
return
|
|
@@ -282,7 +282,21 @@ class ACPDaemon:
|
|
|
282
282
|
# Enqueue relay target (FIFO matches prompt queue order)
|
|
283
283
|
self._mention_targets.append(target if target.startswith("#") else sender)
|
|
284
284
|
if target.startswith("#"):
|
|
285
|
-
|
|
285
|
+
import re
|
|
286
|
+
thread_match = re.match(r"^\[thread:([a-zA-Z0-9\-]+)\] ", text)
|
|
287
|
+
if thread_match and self._buffer:
|
|
288
|
+
thread_name = thread_match.group(1)
|
|
289
|
+
thread_msgs = self._buffer.read_thread(target, thread_name)
|
|
290
|
+
history = "\n".join(
|
|
291
|
+
f" <{m.nick}> {m.text}" for m in thread_msgs
|
|
292
|
+
)
|
|
293
|
+
prompt = (
|
|
294
|
+
f"[IRC @mention in {target}, thread:{thread_name}]\n"
|
|
295
|
+
f"Thread history:\n{history}\n"
|
|
296
|
+
f" <{sender}> {text}"
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
prompt = f"[IRC @mention in {target}] <{sender}> {text}"
|
|
286
300
|
else:
|
|
287
301
|
prompt = f"[IRC DM] <{sender}> {text}"
|
|
288
302
|
asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
@@ -510,6 +524,21 @@ class ACPDaemon:
|
|
|
510
524
|
elif msg_type == "resume":
|
|
511
525
|
return await self._ipc_resume(req_id)
|
|
512
526
|
|
|
527
|
+
elif msg_type == "irc_thread_create":
|
|
528
|
+
return await self._ipc_irc_thread_create(req_id, msg)
|
|
529
|
+
|
|
530
|
+
elif msg_type == "irc_thread_reply":
|
|
531
|
+
return await self._ipc_irc_thread_reply(req_id, msg)
|
|
532
|
+
|
|
533
|
+
elif msg_type == "irc_threads":
|
|
534
|
+
return await self._ipc_irc_threads(req_id, msg)
|
|
535
|
+
|
|
536
|
+
elif msg_type == "irc_thread_close":
|
|
537
|
+
return await self._ipc_irc_thread_close(req_id, msg)
|
|
538
|
+
|
|
539
|
+
elif msg_type == "irc_thread_read":
|
|
540
|
+
return await self._ipc_irc_thread_read(req_id, msg)
|
|
541
|
+
|
|
513
542
|
elif msg_type == "shutdown":
|
|
514
543
|
asyncio.create_task(self._graceful_shutdown())
|
|
515
544
|
return make_response(req_id, ok=True)
|
|
@@ -642,6 +671,64 @@ class ACPDaemon:
|
|
|
642
671
|
await self._transport.part_channel(channel)
|
|
643
672
|
return make_response(req_id, ok=True)
|
|
644
673
|
|
|
674
|
+
async def _ipc_irc_thread_create(self, req_id: str, msg: dict) -> dict:
|
|
675
|
+
channel = msg.get("channel", "")
|
|
676
|
+
thread_name = msg.get("thread", "")
|
|
677
|
+
text = msg.get("message", "")
|
|
678
|
+
if not channel or not thread_name or not text:
|
|
679
|
+
return make_response(req_id, ok=False,
|
|
680
|
+
error="Missing 'channel', 'thread', or 'message'")
|
|
681
|
+
assert self._transport is not None
|
|
682
|
+
await self._transport.send_thread_create(channel, thread_name, text)
|
|
683
|
+
return make_response(req_id, ok=True)
|
|
684
|
+
|
|
685
|
+
async def _ipc_irc_thread_reply(self, req_id: str, msg: dict) -> dict:
|
|
686
|
+
channel = msg.get("channel", "")
|
|
687
|
+
thread_name = msg.get("thread", "")
|
|
688
|
+
text = msg.get("message", "")
|
|
689
|
+
if not channel or not thread_name or not text:
|
|
690
|
+
return make_response(req_id, ok=False,
|
|
691
|
+
error="Missing 'channel', 'thread', or 'message'")
|
|
692
|
+
assert self._transport is not None
|
|
693
|
+
await self._transport.send_thread_reply(channel, thread_name, text)
|
|
694
|
+
return make_response(req_id, ok=True)
|
|
695
|
+
|
|
696
|
+
async def _ipc_irc_threads(self, req_id: str, msg: dict) -> dict:
|
|
697
|
+
channel = msg.get("channel", "")
|
|
698
|
+
if not channel:
|
|
699
|
+
return make_response(req_id, ok=False, error="Missing 'channel'")
|
|
700
|
+
assert self._transport is not None
|
|
701
|
+
await self._transport.send_threads_list(channel)
|
|
702
|
+
return make_response(req_id, ok=True)
|
|
703
|
+
|
|
704
|
+
async def _ipc_irc_thread_close(self, req_id: str, msg: dict) -> dict:
|
|
705
|
+
channel = msg.get("channel", "")
|
|
706
|
+
thread_name = msg.get("thread", "")
|
|
707
|
+
summary = msg.get("summary", "")
|
|
708
|
+
if not channel or not thread_name:
|
|
709
|
+
return make_response(req_id, ok=False,
|
|
710
|
+
error="Missing 'channel' or 'thread'")
|
|
711
|
+
assert self._transport is not None
|
|
712
|
+
await self._transport.send_thread_close(channel, thread_name, summary)
|
|
713
|
+
return make_response(req_id, ok=True)
|
|
714
|
+
|
|
715
|
+
async def _ipc_irc_thread_read(self, req_id: str, msg: dict) -> dict:
|
|
716
|
+
channel = msg.get("channel", "")
|
|
717
|
+
thread_name = msg.get("thread", "")
|
|
718
|
+
limit = int(msg.get("limit", 50))
|
|
719
|
+
if not channel or not thread_name:
|
|
720
|
+
return make_response(req_id, ok=False,
|
|
721
|
+
error="Missing 'channel' or 'thread'")
|
|
722
|
+
assert self._buffer is not None
|
|
723
|
+
messages = self._buffer.read_thread(channel, thread_name, limit=limit)
|
|
724
|
+
return make_response(req_id, ok=True, data={
|
|
725
|
+
"messages": [
|
|
726
|
+
{"nick": m.nick, "text": m.text, "timestamp": m.timestamp,
|
|
727
|
+
"thread": m.thread}
|
|
728
|
+
for m in messages
|
|
729
|
+
]
|
|
730
|
+
})
|
|
731
|
+
|
|
645
732
|
async def _ipc_irc_channels(self, req_id: str) -> dict:
|
|
646
733
|
assert self._transport is not None
|
|
647
734
|
return make_response(req_id, ok=True, data={"channels": self._transport.channels})
|
|
@@ -73,6 +73,18 @@ class IRCTransport:
|
|
|
73
73
|
async def send_privmsg(self, target: str, text: str) -> None:
|
|
74
74
|
await self._send_raw(f"PRIVMSG {target} :{text}")
|
|
75
75
|
|
|
76
|
+
async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
|
|
77
|
+
await self._send_raw(f"THREAD CREATE {channel} {thread_name} :{text}")
|
|
78
|
+
|
|
79
|
+
async def send_thread_reply(self, channel: str, thread_name: str, text: str) -> None:
|
|
80
|
+
await self._send_raw(f"THREAD REPLY {channel} {thread_name} :{text}")
|
|
81
|
+
|
|
82
|
+
async def send_thread_close(self, channel: str, thread_name: str, summary: str) -> None:
|
|
83
|
+
await self._send_raw(f"THREADCLOSE {channel} {thread_name} :{summary}")
|
|
84
|
+
|
|
85
|
+
async def send_threads_list(self, channel: str) -> None:
|
|
86
|
+
await self._send_raw(f"THREADS {channel}")
|
|
87
|
+
|
|
76
88
|
async def join_channel(self, channel: str) -> None:
|
|
77
89
|
await self._send_raw(f"JOIN {channel}")
|
|
78
90
|
if channel not in self.channels:
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
import time
|
|
4
5
|
from collections import deque
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
|
|
8
|
+
_THREAD_PREFIX_RE = re.compile(r"^\[thread:([a-zA-Z0-9\-]+)\] ")
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
@dataclass
|
|
9
12
|
class BufferedMessage:
|
|
10
13
|
nick: str
|
|
11
14
|
text: str
|
|
12
15
|
timestamp: float
|
|
16
|
+
thread: str | None = None
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
class MessageBuffer:
|
|
@@ -24,8 +28,13 @@ class MessageBuffer:
|
|
|
24
28
|
self._buffers[channel] = deque(maxlen=self.max_per_channel)
|
|
25
29
|
self._totals[channel] = 0
|
|
26
30
|
self._cursors[channel] = 0
|
|
31
|
+
thread = None
|
|
32
|
+
m = _THREAD_PREFIX_RE.match(text)
|
|
33
|
+
if m:
|
|
34
|
+
thread = m.group(1)
|
|
27
35
|
self._buffers[channel].append(
|
|
28
|
-
BufferedMessage(nick=nick, text=text, timestamp=time.time()
|
|
36
|
+
BufferedMessage(nick=nick, text=text, timestamp=time.time(),
|
|
37
|
+
thread=thread)
|
|
29
38
|
)
|
|
30
39
|
self._totals[channel] += 1
|
|
31
40
|
|
|
@@ -44,3 +53,13 @@ class MessageBuffer:
|
|
|
44
53
|
new_messages = new_messages[-limit:]
|
|
45
54
|
self._cursors[channel] = total
|
|
46
55
|
return new_messages
|
|
56
|
+
|
|
57
|
+
def read_thread(self, channel: str, thread_name: str,
|
|
58
|
+
limit: int = 50) -> list[BufferedMessage]:
|
|
59
|
+
buf = self._buffers.get(channel)
|
|
60
|
+
if not buf:
|
|
61
|
+
return []
|
|
62
|
+
matches = [m for m in buf if m.thread == thread_name]
|
|
63
|
+
if len(matches) > limit:
|
|
64
|
+
matches = matches[-limit:]
|
|
65
|
+
return matches
|
|
@@ -239,14 +239,28 @@ class AgentDaemon:
|
|
|
239
239
|
def _on_mention(self, target: str, sender: str, text: str) -> None:
|
|
240
240
|
"""Called by IRCTransport when the agent is @mentioned or DM'd.
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
When the mention is inside a thread, provides thread-scoped context.
|
|
243
243
|
"""
|
|
244
244
|
if self._paused:
|
|
245
245
|
return
|
|
246
246
|
if self._agent_runner and self._agent_runner.is_running():
|
|
247
247
|
self._last_activation = time.time()
|
|
248
248
|
if target.startswith("#"):
|
|
249
|
-
|
|
249
|
+
import re
|
|
250
|
+
thread_match = re.match(r"^\[thread:([a-zA-Z0-9\-]+)\] ", text)
|
|
251
|
+
if thread_match and self._buffer:
|
|
252
|
+
thread_name = thread_match.group(1)
|
|
253
|
+
thread_msgs = self._buffer.read_thread(target, thread_name)
|
|
254
|
+
history = "\n".join(
|
|
255
|
+
f" <{m.nick}> {m.text}" for m in thread_msgs
|
|
256
|
+
)
|
|
257
|
+
prompt = (
|
|
258
|
+
f"[IRC @mention in {target}, thread:{thread_name}]\n"
|
|
259
|
+
f"Thread history:\n{history}\n"
|
|
260
|
+
f" <{sender}> {text}"
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
prompt = f"[IRC @mention in {target}] <{sender}> {text}"
|
|
250
264
|
else:
|
|
251
265
|
prompt = f"[IRC DM] <{sender}> {text}"
|
|
252
266
|
asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
@@ -451,6 +465,21 @@ class AgentDaemon:
|
|
|
451
465
|
elif msg_type == "resume":
|
|
452
466
|
return await self._ipc_resume(req_id)
|
|
453
467
|
|
|
468
|
+
elif msg_type == "irc_thread_create":
|
|
469
|
+
return await self._ipc_irc_thread_create(req_id, msg)
|
|
470
|
+
|
|
471
|
+
elif msg_type == "irc_thread_reply":
|
|
472
|
+
return await self._ipc_irc_thread_reply(req_id, msg)
|
|
473
|
+
|
|
474
|
+
elif msg_type == "irc_threads":
|
|
475
|
+
return await self._ipc_irc_threads(req_id, msg)
|
|
476
|
+
|
|
477
|
+
elif msg_type == "irc_thread_close":
|
|
478
|
+
return await self._ipc_irc_thread_close(req_id, msg)
|
|
479
|
+
|
|
480
|
+
elif msg_type == "irc_thread_read":
|
|
481
|
+
return await self._ipc_irc_thread_read(req_id, msg)
|
|
482
|
+
|
|
454
483
|
elif msg_type == "shutdown":
|
|
455
484
|
asyncio.create_task(self._graceful_shutdown())
|
|
456
485
|
return make_response(req_id, ok=True)
|
|
@@ -580,6 +609,64 @@ class AgentDaemon:
|
|
|
580
609
|
await self._transport.part_channel(channel)
|
|
581
610
|
return make_response(req_id, ok=True)
|
|
582
611
|
|
|
612
|
+
async def _ipc_irc_thread_create(self, req_id: str, msg: dict) -> dict:
|
|
613
|
+
channel = msg.get("channel", "")
|
|
614
|
+
thread_name = msg.get("thread", "")
|
|
615
|
+
text = msg.get("message", "")
|
|
616
|
+
if not channel or not thread_name or not text:
|
|
617
|
+
return make_response(req_id, ok=False,
|
|
618
|
+
error="Missing 'channel', 'thread', or 'message'")
|
|
619
|
+
assert self._transport is not None
|
|
620
|
+
await self._transport.send_thread_create(channel, thread_name, text)
|
|
621
|
+
return make_response(req_id, ok=True)
|
|
622
|
+
|
|
623
|
+
async def _ipc_irc_thread_reply(self, req_id: str, msg: dict) -> dict:
|
|
624
|
+
channel = msg.get("channel", "")
|
|
625
|
+
thread_name = msg.get("thread", "")
|
|
626
|
+
text = msg.get("message", "")
|
|
627
|
+
if not channel or not thread_name or not text:
|
|
628
|
+
return make_response(req_id, ok=False,
|
|
629
|
+
error="Missing 'channel', 'thread', or 'message'")
|
|
630
|
+
assert self._transport is not None
|
|
631
|
+
await self._transport.send_thread_reply(channel, thread_name, text)
|
|
632
|
+
return make_response(req_id, ok=True)
|
|
633
|
+
|
|
634
|
+
async def _ipc_irc_threads(self, req_id: str, msg: dict) -> dict:
|
|
635
|
+
channel = msg.get("channel", "")
|
|
636
|
+
if not channel:
|
|
637
|
+
return make_response(req_id, ok=False, error="Missing 'channel'")
|
|
638
|
+
assert self._transport is not None
|
|
639
|
+
await self._transport.send_threads_list(channel)
|
|
640
|
+
return make_response(req_id, ok=True)
|
|
641
|
+
|
|
642
|
+
async def _ipc_irc_thread_close(self, req_id: str, msg: dict) -> dict:
|
|
643
|
+
channel = msg.get("channel", "")
|
|
644
|
+
thread_name = msg.get("thread", "")
|
|
645
|
+
summary = msg.get("summary", "")
|
|
646
|
+
if not channel or not thread_name:
|
|
647
|
+
return make_response(req_id, ok=False,
|
|
648
|
+
error="Missing 'channel' or 'thread'")
|
|
649
|
+
assert self._transport is not None
|
|
650
|
+
await self._transport.send_thread_close(channel, thread_name, summary)
|
|
651
|
+
return make_response(req_id, ok=True)
|
|
652
|
+
|
|
653
|
+
async def _ipc_irc_thread_read(self, req_id: str, msg: dict) -> dict:
|
|
654
|
+
channel = msg.get("channel", "")
|
|
655
|
+
thread_name = msg.get("thread", "")
|
|
656
|
+
limit = int(msg.get("limit", 50))
|
|
657
|
+
if not channel or not thread_name:
|
|
658
|
+
return make_response(req_id, ok=False,
|
|
659
|
+
error="Missing 'channel' or 'thread'")
|
|
660
|
+
assert self._buffer is not None
|
|
661
|
+
messages = self._buffer.read_thread(channel, thread_name, limit=limit)
|
|
662
|
+
return make_response(req_id, ok=True, data={
|
|
663
|
+
"messages": [
|
|
664
|
+
{"nick": m.nick, "text": m.text, "timestamp": m.timestamp,
|
|
665
|
+
"thread": m.thread}
|
|
666
|
+
for m in messages
|
|
667
|
+
]
|
|
668
|
+
})
|
|
669
|
+
|
|
583
670
|
async def _ipc_irc_channels(self, req_id: str) -> dict:
|
|
584
671
|
assert self._transport is not None
|
|
585
672
|
return make_response(req_id, ok=True, data={"channels": self._transport.channels})
|
|
@@ -73,6 +73,18 @@ class IRCTransport:
|
|
|
73
73
|
async def send_privmsg(self, target: str, text: str) -> None:
|
|
74
74
|
await self._send_raw(f"PRIVMSG {target} :{text}")
|
|
75
75
|
|
|
76
|
+
async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
|
|
77
|
+
await self._send_raw(f"THREAD CREATE {channel} {thread_name} :{text}")
|
|
78
|
+
|
|
79
|
+
async def send_thread_reply(self, channel: str, thread_name: str, text: str) -> None:
|
|
80
|
+
await self._send_raw(f"THREAD REPLY {channel} {thread_name} :{text}")
|
|
81
|
+
|
|
82
|
+
async def send_thread_close(self, channel: str, thread_name: str, summary: str) -> None:
|
|
83
|
+
await self._send_raw(f"THREADCLOSE {channel} {thread_name} :{summary}")
|
|
84
|
+
|
|
85
|
+
async def send_threads_list(self, channel: str) -> None:
|
|
86
|
+
await self._send_raw(f"THREADS {channel}")
|
|
87
|
+
|
|
76
88
|
async def join_channel(self, channel: str) -> None:
|
|
77
89
|
await self._send_raw(f"JOIN {channel}")
|
|
78
90
|
if channel not in self.channels:
|