agentirc-cli 4.5.2__tar.gz → 5.0.1__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-4.5.2 → agentirc_cli-5.0.1}/CHANGELOG.md +44 -6
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/PKG-INFO +1 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/mesh.py +14 -2
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/daemon.py +40 -3
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/irc_transport.py +39 -0
- {agentirc_cli-4.5.2/culture/clients/codex → agentirc_cli-5.0.1/culture/clients/acp}/message_buffer.py +8 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/SKILL.md +22 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/irc_client.py +15 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/daemon.py +40 -3
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/irc_transport.py +39 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/message_buffer.py +8 -0
- {agentirc_cli-4.5.2/plugins/claude-code/skills/irc → agentirc_cli-5.0.1/culture/clients/claude/skill}/SKILL.md +22 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/skill/irc_client.py +15 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/daemon.py +63 -6
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/irc_transport.py +39 -0
- {agentirc_cli-4.5.2/culture/clients/acp → agentirc_cli-5.0.1/culture/clients/codex}/message_buffer.py +8 -0
- {agentirc_cli-4.5.2/plugins/codex/skills/culture-irc → agentirc_cli-5.0.1/culture/clients/codex/skill}/SKILL.md +22 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/skill/irc_client.py +15 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/daemon.py +40 -3
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/irc_transport.py +39 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/message_buffer.py +8 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/SKILL.md +22 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/irc_client.py +15 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/collector.py +25 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/model.py +1 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/renderer_text.py +35 -18
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/renderer_web.py +7 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/agent-harness-spec.md +2 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer5-agent-harness.md +3 -3
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/culture-cli.md +16 -16
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/getting-started.md +14 -14
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/cli.md +77 -68
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/overview.md +8 -8
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/daemon.py +50 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/irc_transport.py +39 -0
- agentirc_cli-5.0.1/packages/agent-harness/message_buffer.py +71 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/skill/SKILL.md +7 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/skill/irc_client.py +15 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/plugins/claude-code/skills/culture/SKILL.md +5 -5
- {agentirc_cli-4.5.2/culture/clients/claude/skill → agentirc_cli-5.0.1/plugins/claude-code/skills/irc}/SKILL.md +22 -0
- {agentirc_cli-4.5.2/culture/clients/codex/skill → agentirc_cli-5.0.1/plugins/codex/skills/culture-irc}/SKILL.md +22 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/pyproject.toml +1 -1
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_codex_daemon.py +32 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon_ipc.py +57 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_irc_transport.py +90 -0
- agentirc_cli-5.0.1/tests/test_mention_warning.py +62 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_model.py +12 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_renderer.py +70 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/uv.lock +1 -1
- agentirc_cli-4.5.2/packages/agent-harness/message_buffer.py +0 -63
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.flake8 +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.gitignore +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pr_agent.toml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pylintrc +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/CLAUDE.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/CNAME +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/Gemfile +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/Gemfile.lock +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/LICENSE +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/README.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/SECURITY.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_config.yml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_includes/head_custom.html +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/apple-touch-icon.png +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon-16x16.png +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon-32x32.png +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon.ico +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/__main__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/aio.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/bot.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/agent.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/bot.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/channel.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/constants.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/display.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/formatting.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/mesh.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/process.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/skills.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/culture.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/culture.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/culture.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/culture.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/app.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/commands.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/credentials.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/formatting.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/learn_prompt.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/mesh_config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/observer.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/web/style.css +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/persistence.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/pidfile.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/commands.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/message.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/replies.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/__main__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/channel.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/history_store.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/ircd.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/remote_client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/room_store.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/server_link.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skill.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/history.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/icon.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/rooms.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/thread_store.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agent-lifecycle.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agents/decentralized-config.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/agent-client.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/harness-conformance.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/index.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer1-core-irc.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer2-attention.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer3-skills.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer4-federation.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/server-architecture.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/threads.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/channel-polling.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/acp/system-prompt.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/index.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/SECURITY.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/bots.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/ci.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/docs-site.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/index.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/ops-tooling.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/publishing.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/persistent-history.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/reflective-development.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/rooms.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/server-rename.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases-index.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/what-is-culture.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/favicon.ico +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/culture.yaml +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/sonar-project.properties +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/__init__.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/conftest.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_archive.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot_config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_channel.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_connection.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_commands.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_connection.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_icons.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_integration.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_culture_config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_discovery.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_display.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_federation.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_history.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_http_listener.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_ipc.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_learn_prompt.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mention_alias.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mention_target_cleanup.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mentions.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_message.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_messaging.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_migrate_cli.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_modes.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_web.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_persistence.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_pidfile.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_register_cli.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skill_client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skill_docs.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skills.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_socket_server.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_supervisor.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_template_engine.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_threads.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_webhook.py +0 -0
|
@@ -4,17 +4,55 @@ 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
|
+
## [5.0.1] - 2026-04-09
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Topic subcommand for IRC skill (#192)
|
|
13
|
+
- @mention validation warnings for unknown nicks (#196)
|
|
14
|
+
- GitHub issues skill for Claude Code
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Whitespace-only messages now rejected (#195)
|
|
20
|
+
- join/part channel state desync with # prefix validation (#194)
|
|
21
|
+
- Sending to unjoined channels now returns error (#193)
|
|
22
|
+
- Agents can now read own messages in channel history (#191)
|
|
23
|
+
- Codex backend meta-response stripping (#197)
|
|
24
|
+
|
|
25
|
+
## [5.0.0] - 2026-04-09
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Mesh overview shows stopped/registered agents from server.yaml manifest (#178)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- CLI docs use correct noun-group syntax (culture agent create, culture channel read, etc.) (#186)
|
|
36
|
+
- Replaced non-existent culture send with culture channel message / culture agent message (#187)
|
|
37
|
+
- All doc references updated from agents.yaml to server.yaml (#188)
|
|
38
|
+
- Documented --mesh-config, --webhook-port, --data-dir server start flags (#189)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Mesh overview now includes agents that are registered but not running
|
|
44
|
+
|
|
7
45
|
## [4.5.2] - 2026-04-09
|
|
8
46
|
|
|
9
47
|
|
|
10
48
|
### Fixed
|
|
11
49
|
|
|
12
|
-
- Agent status
|
|
13
|
-
- Agent status list
|
|
14
|
-
- Learn prompt
|
|
15
|
-
- Non-Claude backend skill docs
|
|
16
|
-
- Admin skill and learn prompt missing
|
|
17
|
-
- Mesh overview
|
|
50
|
+
- Agent status now reports the circuit-open state correctly instead of showing running (#179)
|
|
51
|
+
- Agent status list now distinguishes paused and sleeping agents correctly (#180)
|
|
52
|
+
- Learn prompt now includes compact/clear commands and ask --timeout (#181)
|
|
53
|
+
- Non-Claude backend skill docs now include the required features and comply with the all-backends rule (#182)
|
|
54
|
+
- Admin skill and learn prompt now include the missing CLI commands (#183)
|
|
55
|
+
- Mesh overview now indicates when bots are archived (#184)
|
|
18
56
|
|
|
19
57
|
## [4.5.1] - 2026-04-09
|
|
20
58
|
|
|
@@ -120,7 +120,13 @@ def dispatch(args: argparse.Namespace) -> None:
|
|
|
120
120
|
# -----------------------------------------------------------------------
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
def _collect_mesh_data(
|
|
123
|
+
def _collect_mesh_data(
|
|
124
|
+
host: str,
|
|
125
|
+
port: int,
|
|
126
|
+
server_name: str,
|
|
127
|
+
message_limit: int,
|
|
128
|
+
manifest_agents: list | None = None,
|
|
129
|
+
):
|
|
124
130
|
"""Collect mesh state, exiting with an error message on connection failure."""
|
|
125
131
|
from culture.overview.collector import collect_mesh_state
|
|
126
132
|
|
|
@@ -131,6 +137,7 @@ def _collect_mesh_data(host: str, port: int, server_name: str, message_limit: in
|
|
|
131
137
|
port=port,
|
|
132
138
|
server_name=server_name,
|
|
133
139
|
message_limit=message_limit,
|
|
140
|
+
manifest_agents=manifest_agents,
|
|
134
141
|
)
|
|
135
142
|
)
|
|
136
143
|
except ConnectionRefusedError:
|
|
@@ -169,11 +176,16 @@ def _cmd_overview(args: argparse.Namespace) -> None:
|
|
|
169
176
|
agent_filter=args.agent,
|
|
170
177
|
message_limit=message_limit,
|
|
171
178
|
refresh_interval=refresh_interval,
|
|
179
|
+
manifest_agents=config.agents,
|
|
172
180
|
)
|
|
173
181
|
return
|
|
174
182
|
|
|
175
183
|
mesh = _collect_mesh_data(
|
|
176
|
-
config.server.host,
|
|
184
|
+
config.server.host,
|
|
185
|
+
config.server.port,
|
|
186
|
+
config.server.name,
|
|
187
|
+
message_limit,
|
|
188
|
+
manifest_agents=config.agents,
|
|
177
189
|
)
|
|
178
190
|
output = render_text(
|
|
179
191
|
mesh,
|
|
@@ -35,6 +35,9 @@ _ERR_MISSING_CHANNEL = "Missing 'channel'"
|
|
|
35
35
|
_ERR_MISSING_CHANNEL_THREAD = "Missing 'channel' or 'thread'"
|
|
36
36
|
_ERR_MISSING_CHANNEL_THREAD_MSG = "Missing 'channel', 'thread', or 'message'"
|
|
37
37
|
|
|
38
|
+
# Regex to extract @mentioned nicks from messages
|
|
39
|
+
_MENTION_RE = re.compile(r"@([\w-]+)")
|
|
40
|
+
|
|
38
41
|
MAX_CRASH_COUNT = 3
|
|
39
42
|
CRASH_WINDOW_SECONDS = 300
|
|
40
43
|
CRASH_RESTART_DELAY = 5
|
|
@@ -103,6 +106,7 @@ class ACPDaemon:
|
|
|
103
106
|
"irc_part": self._ipc_irc_part,
|
|
104
107
|
"irc_channels": self._ipc_irc_channels,
|
|
105
108
|
"irc_who": self._ipc_irc_who,
|
|
109
|
+
"irc_topic": self._ipc_irc_topic,
|
|
106
110
|
"irc_ask": self._ipc_irc_ask,
|
|
107
111
|
"compact": self._ipc_compact,
|
|
108
112
|
"clear": self._ipc_clear,
|
|
@@ -749,16 +753,34 @@ class ACPDaemon:
|
|
|
749
753
|
self._status_query_event = None
|
|
750
754
|
self._status_query_response = ""
|
|
751
755
|
|
|
756
|
+
def _check_mention_warnings(self, text: str) -> list[str]:
|
|
757
|
+
"""Return warnings for @mentioned nicks not seen in any buffer."""
|
|
758
|
+
mentions = _MENTION_RE.findall(text)
|
|
759
|
+
if not mentions or not self._buffer:
|
|
760
|
+
return []
|
|
761
|
+
known_nicks = self._buffer.known_nicks()
|
|
762
|
+
warnings = []
|
|
763
|
+
for nick in mentions:
|
|
764
|
+
if nick not in known_nicks:
|
|
765
|
+
warnings.append(f"Mentioned nick not found: {nick}")
|
|
766
|
+
return warnings
|
|
767
|
+
|
|
752
768
|
async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
|
|
753
769
|
channel = msg.get("channel", "")
|
|
754
770
|
text = msg.get("message", "")
|
|
755
771
|
if not channel:
|
|
756
772
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
757
|
-
if not text:
|
|
773
|
+
if not text or not text.strip():
|
|
758
774
|
return make_response(req_id, ok=False, error="Missing 'message'")
|
|
759
775
|
assert self._transport is not None
|
|
776
|
+
if channel.startswith("#") and channel not in self._transport.channels:
|
|
777
|
+
return make_response(req_id, ok=False, error=f"Not joined to {channel}")
|
|
760
778
|
await self._transport.send_privmsg(channel, text)
|
|
761
|
-
|
|
779
|
+
warnings = self._check_mention_warnings(text)
|
|
780
|
+
resp = make_response(req_id, ok=True)
|
|
781
|
+
if warnings:
|
|
782
|
+
resp["warnings"] = warnings
|
|
783
|
+
return resp
|
|
762
784
|
|
|
763
785
|
def _ipc_irc_read(self, req_id: str, msg: dict) -> dict:
|
|
764
786
|
channel = msg.get("channel", "")
|
|
@@ -781,6 +803,8 @@ class ACPDaemon:
|
|
|
781
803
|
channel = msg.get("channel", "")
|
|
782
804
|
if not channel:
|
|
783
805
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
806
|
+
if not channel.startswith("#"):
|
|
807
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
784
808
|
assert self._transport is not None
|
|
785
809
|
await self._transport.join_channel(channel)
|
|
786
810
|
return make_response(req_id, ok=True)
|
|
@@ -789,6 +813,8 @@ class ACPDaemon:
|
|
|
789
813
|
channel = msg.get("channel", "")
|
|
790
814
|
if not channel:
|
|
791
815
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
816
|
+
if not channel.startswith("#"):
|
|
817
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
792
818
|
assert self._transport is not None
|
|
793
819
|
await self._transport.part_channel(channel)
|
|
794
820
|
return make_response(req_id, ok=True)
|
|
@@ -862,13 +888,24 @@ class ACPDaemon:
|
|
|
862
888
|
await self._transport.send_who(target)
|
|
863
889
|
return make_response(req_id, ok=True)
|
|
864
890
|
|
|
891
|
+
async def _ipc_irc_topic(self, req_id: str, msg: dict) -> dict:
|
|
892
|
+
channel = msg.get("channel", "")
|
|
893
|
+
if not channel:
|
|
894
|
+
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
895
|
+
if not channel.startswith("#"):
|
|
896
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
897
|
+
assert self._transport is not None
|
|
898
|
+
topic = msg.get("topic") # None means query, string means set
|
|
899
|
+
await self._transport.send_topic(channel, topic)
|
|
900
|
+
return make_response(req_id, ok=True)
|
|
901
|
+
|
|
865
902
|
async def _ipc_irc_ask(self, req_id: str, msg: dict) -> dict:
|
|
866
903
|
"""Send a PRIVMSG and fire a question webhook. Response matching is TODO."""
|
|
867
904
|
channel = msg.get("channel", "")
|
|
868
905
|
question = msg.get("message", "")
|
|
869
906
|
if not channel:
|
|
870
907
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
871
|
-
if not question:
|
|
908
|
+
if not question or not question.strip():
|
|
872
909
|
return make_response(req_id, ok=False, error="Missing 'message'")
|
|
873
910
|
assert self._transport is not None
|
|
874
911
|
await self._transport.send_privmsg(channel, question)
|
|
@@ -51,6 +51,9 @@ class IRCTransport:
|
|
|
51
51
|
"PRIVMSG": self._on_privmsg,
|
|
52
52
|
"NOTICE": self._on_notice,
|
|
53
53
|
"ROOMINVITE": self._on_roominvite,
|
|
54
|
+
"TOPIC": self._on_topic,
|
|
55
|
+
"331": self._on_numeric_topic,
|
|
56
|
+
"332": self._on_numeric_topic,
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
async def connect(self) -> None:
|
|
@@ -90,6 +93,10 @@ class IRCTransport:
|
|
|
90
93
|
for line in text.splitlines():
|
|
91
94
|
if line:
|
|
92
95
|
await self._send_raw(f"PRIVMSG {target} :{line}")
|
|
96
|
+
if target.startswith("#"):
|
|
97
|
+
self.buffer.add(target, self.nick, line)
|
|
98
|
+
else:
|
|
99
|
+
self.buffer.add(f"DM:{target}", self.nick, line)
|
|
93
100
|
|
|
94
101
|
async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
|
|
95
102
|
lines = [l for l in text.splitlines() if l]
|
|
@@ -112,11 +119,15 @@ class IRCTransport:
|
|
|
112
119
|
await self._send_raw(f"THREADS {channel}")
|
|
113
120
|
|
|
114
121
|
async def join_channel(self, channel: str) -> None:
|
|
122
|
+
if not channel.startswith("#"):
|
|
123
|
+
return
|
|
115
124
|
await self._send_raw(f"JOIN {channel}")
|
|
116
125
|
if channel not in self.channels:
|
|
117
126
|
self.channels.append(channel)
|
|
118
127
|
|
|
119
128
|
async def part_channel(self, channel: str) -> None:
|
|
129
|
+
if not channel.startswith("#"):
|
|
130
|
+
return
|
|
120
131
|
await self._send_raw(f"PART {channel}")
|
|
121
132
|
if channel in self.channels:
|
|
122
133
|
self.channels.remove(channel)
|
|
@@ -124,6 +135,12 @@ class IRCTransport:
|
|
|
124
135
|
async def send_who(self, target: str) -> None:
|
|
125
136
|
await self._send_raw(f"WHO {target}")
|
|
126
137
|
|
|
138
|
+
async def send_topic(self, channel: str, topic: str | None = None) -> None:
|
|
139
|
+
if topic is not None:
|
|
140
|
+
await self._send_raw(f"TOPIC {channel} :{topic}")
|
|
141
|
+
else:
|
|
142
|
+
await self._send_raw(f"TOPIC {channel}")
|
|
143
|
+
|
|
127
144
|
async def send_raw(self, line: str) -> None:
|
|
128
145
|
"""Send a raw IRC line. Public for commands like HISTORY."""
|
|
129
146
|
if self._writer:
|
|
@@ -202,6 +219,28 @@ class IRCTransport:
|
|
|
202
219
|
self._route_to_buffer(target, sender, text)
|
|
203
220
|
self._detect_and_fire_mention(target, sender, text)
|
|
204
221
|
|
|
222
|
+
def _on_topic(self, msg: Message) -> None:
|
|
223
|
+
"""Handle TOPIC broadcasts (someone changed the topic)."""
|
|
224
|
+
if len(msg.params) < 2:
|
|
225
|
+
return
|
|
226
|
+
channel = msg.params[0]
|
|
227
|
+
topic = msg.params[1]
|
|
228
|
+
sender = msg.prefix.split("!")[0] if msg.prefix else "server"
|
|
229
|
+
if channel.startswith("#"):
|
|
230
|
+
self.buffer.add(channel, sender, f"* Topic changed: {topic}")
|
|
231
|
+
|
|
232
|
+
def _on_numeric_topic(self, msg: Message) -> None:
|
|
233
|
+
"""Handle 331 (no topic) and 332 (topic is...) replies."""
|
|
234
|
+
if len(msg.params) < 2:
|
|
235
|
+
return
|
|
236
|
+
channel = msg.params[1]
|
|
237
|
+
if not channel.startswith("#"):
|
|
238
|
+
return
|
|
239
|
+
if msg.command == "331":
|
|
240
|
+
self.buffer.add(channel, "server", "* No topic is set")
|
|
241
|
+
elif msg.command == "332" and len(msg.params) >= 3:
|
|
242
|
+
self.buffer.add(channel, "server", f"* Topic: {msg.params[2]}")
|
|
243
|
+
|
|
205
244
|
def _route_to_buffer(self, target: str, sender: str, text: str) -> None:
|
|
206
245
|
"""Insert the message into the appropriate buffer (channel or DM)."""
|
|
207
246
|
if target.startswith("#"):
|
|
@@ -53,6 +53,14 @@ class MessageBuffer:
|
|
|
53
53
|
self._cursors[channel] = total
|
|
54
54
|
return new_messages
|
|
55
55
|
|
|
56
|
+
def known_nicks(self) -> set[str]:
|
|
57
|
+
"""Return the set of nicks seen across all buffers."""
|
|
58
|
+
nicks: set[str] = set()
|
|
59
|
+
for buf in self._buffers.values():
|
|
60
|
+
for m in buf:
|
|
61
|
+
nicks.add(m.nick)
|
|
62
|
+
return nicks
|
|
63
|
+
|
|
56
64
|
def read_thread(self, channel: str, thread_name: str, limit: int = 50) -> list[BufferedMessage]:
|
|
57
65
|
buf = self._buffers.get(channel)
|
|
58
66
|
if not buf:
|
|
@@ -141,6 +141,26 @@ python3 -m culture.clients.acp.skill.irc_client who <target>
|
|
|
141
141
|
|
|
142
142
|
---
|
|
143
143
|
|
|
144
|
+
### topic — get or set a channel topic
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python3 -m culture.clients.acp.skill.irc_client topic <channel> [topic text]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Get current topic:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python3 -m culture.clients.acp.skill.irc_client topic "#general"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Set topic:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python3 -m culture.clients.acp.skill.irc_client topic "#general" "Welcome to general chat"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
144
164
|
### compact — compact the agent's context window
|
|
145
165
|
|
|
146
166
|
```bash
|
|
@@ -190,6 +210,8 @@ result = await client.irc_join("#ops")
|
|
|
190
210
|
result = await client.irc_part("#ops")
|
|
191
211
|
result = await client.irc_channels()
|
|
192
212
|
result = await client.irc_who("#general")
|
|
213
|
+
result = await client.irc_topic("#general")
|
|
214
|
+
result = await client.irc_topic("#general", "Welcome to general chat")
|
|
193
215
|
result = await client.compact()
|
|
194
216
|
result = await client.clear()
|
|
195
217
|
|
|
@@ -165,6 +165,13 @@ class SkillClient:
|
|
|
165
165
|
"""Send a WHO query for a channel or nick."""
|
|
166
166
|
return await self._request("irc_who", target=target)
|
|
167
167
|
|
|
168
|
+
async def irc_topic(self, channel: str, topic: str | None = None) -> dict[str, Any]:
|
|
169
|
+
"""Get or set a channel topic."""
|
|
170
|
+
params: dict[str, Any] = {"channel": channel}
|
|
171
|
+
if topic is not None:
|
|
172
|
+
params["topic"] = topic
|
|
173
|
+
return await self._request("irc_topic", **params)
|
|
174
|
+
|
|
168
175
|
async def compact(self) -> dict[str, Any]:
|
|
169
176
|
"""Send /compact to the agent runner."""
|
|
170
177
|
return await self._request("compact")
|
|
@@ -248,6 +255,12 @@ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
|
248
255
|
return await client.irc_who(args[1])
|
|
249
256
|
|
|
250
257
|
|
|
258
|
+
async def _cmd_topic(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
259
|
+
channel = args[1]
|
|
260
|
+
topic = " ".join(args[2:]) if len(args) > 2 else None
|
|
261
|
+
return await client.irc_topic(channel, topic)
|
|
262
|
+
|
|
263
|
+
|
|
251
264
|
async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
252
265
|
return await client.compact()
|
|
253
266
|
|
|
@@ -264,6 +277,7 @@ _SUBCOMMANDS: dict[str, Any] = {
|
|
|
264
277
|
"part": _cmd_part,
|
|
265
278
|
"channels": _cmd_channels,
|
|
266
279
|
"who": _cmd_who,
|
|
280
|
+
"topic": _cmd_topic,
|
|
267
281
|
"compact": _cmd_compact,
|
|
268
282
|
"clear": _cmd_clear,
|
|
269
283
|
}
|
|
@@ -274,7 +288,7 @@ async def _main(args: list[str]) -> None:
|
|
|
274
288
|
if not args:
|
|
275
289
|
print(
|
|
276
290
|
"Usage: irc_client.py <subcommand> [args...]\n"
|
|
277
|
-
"Subcommands: send, read, ask, join, part, channels, who, compact, clear",
|
|
291
|
+
"Subcommands: send, read, ask, join, part, channels, who, topic, compact, clear",
|
|
278
292
|
file=sys.stderr,
|
|
279
293
|
)
|
|
280
294
|
sys.exit(1)
|
|
@@ -29,6 +29,9 @@ _ERR_MISSING_CHANNEL = "Missing 'channel'"
|
|
|
29
29
|
_ERR_MISSING_CHANNEL_THREAD = "Missing 'channel' or 'thread'"
|
|
30
30
|
_ERR_MISSING_CHANNEL_THREAD_MSG = "Missing 'channel', 'thread', or 'message'"
|
|
31
31
|
|
|
32
|
+
# Regex to extract @mentioned nicks from messages
|
|
33
|
+
_MENTION_RE = re.compile(r"@([\w-]+)")
|
|
34
|
+
|
|
32
35
|
|
|
33
36
|
class AgentDaemon:
|
|
34
37
|
"""Central orchestrator that ties together the IRC transport, socket server,
|
|
@@ -86,6 +89,7 @@ class AgentDaemon:
|
|
|
86
89
|
"irc_part": self._ipc_irc_part,
|
|
87
90
|
"irc_channels": self._ipc_irc_channels,
|
|
88
91
|
"irc_who": self._ipc_irc_who,
|
|
92
|
+
"irc_topic": self._ipc_irc_topic,
|
|
89
93
|
"irc_ask": self._ipc_irc_ask,
|
|
90
94
|
"compact": self._ipc_compact,
|
|
91
95
|
"clear": self._ipc_clear,
|
|
@@ -639,16 +643,34 @@ class AgentDaemon:
|
|
|
639
643
|
self._status_query_event = None
|
|
640
644
|
self._status_query_response = ""
|
|
641
645
|
|
|
646
|
+
def _check_mention_warnings(self, text: str) -> list[str]:
|
|
647
|
+
"""Return warnings for @mentioned nicks not seen in any buffer."""
|
|
648
|
+
mentions = _MENTION_RE.findall(text)
|
|
649
|
+
if not mentions or not self._buffer:
|
|
650
|
+
return []
|
|
651
|
+
known_nicks = self._buffer.known_nicks()
|
|
652
|
+
warnings = []
|
|
653
|
+
for nick in mentions:
|
|
654
|
+
if nick not in known_nicks:
|
|
655
|
+
warnings.append(f"Mentioned nick not found: {nick}")
|
|
656
|
+
return warnings
|
|
657
|
+
|
|
642
658
|
async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
|
|
643
659
|
channel = msg.get("channel", "")
|
|
644
660
|
text = msg.get("message", "")
|
|
645
661
|
if not channel:
|
|
646
662
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
647
|
-
if not text:
|
|
663
|
+
if not text or not text.strip():
|
|
648
664
|
return make_response(req_id, ok=False, error="Missing 'message'")
|
|
649
665
|
assert self._transport is not None
|
|
666
|
+
if channel.startswith("#") and channel not in self._transport.channels:
|
|
667
|
+
return make_response(req_id, ok=False, error=f"Not joined to {channel}")
|
|
650
668
|
await self._transport.send_privmsg(channel, text)
|
|
651
|
-
|
|
669
|
+
warnings = self._check_mention_warnings(text)
|
|
670
|
+
resp = make_response(req_id, ok=True)
|
|
671
|
+
if warnings:
|
|
672
|
+
resp["warnings"] = warnings
|
|
673
|
+
return resp
|
|
652
674
|
|
|
653
675
|
def _ipc_irc_read(self, req_id: str, msg: dict) -> dict:
|
|
654
676
|
channel = msg.get("channel", "")
|
|
@@ -671,6 +693,8 @@ class AgentDaemon:
|
|
|
671
693
|
channel = msg.get("channel", "")
|
|
672
694
|
if not channel:
|
|
673
695
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
696
|
+
if not channel.startswith("#"):
|
|
697
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
674
698
|
assert self._transport is not None
|
|
675
699
|
await self._transport.join_channel(channel)
|
|
676
700
|
return make_response(req_id, ok=True)
|
|
@@ -679,6 +703,8 @@ class AgentDaemon:
|
|
|
679
703
|
channel = msg.get("channel", "")
|
|
680
704
|
if not channel:
|
|
681
705
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
706
|
+
if not channel.startswith("#"):
|
|
707
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
682
708
|
assert self._transport is not None
|
|
683
709
|
await self._transport.part_channel(channel)
|
|
684
710
|
return make_response(req_id, ok=True)
|
|
@@ -752,13 +778,24 @@ class AgentDaemon:
|
|
|
752
778
|
await self._transport.send_who(target)
|
|
753
779
|
return make_response(req_id, ok=True)
|
|
754
780
|
|
|
781
|
+
async def _ipc_irc_topic(self, req_id: str, msg: dict) -> dict:
|
|
782
|
+
channel = msg.get("channel", "")
|
|
783
|
+
if not channel:
|
|
784
|
+
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
785
|
+
if not channel.startswith("#"):
|
|
786
|
+
return make_response(req_id, ok=False, error="Channel name must start with '#'")
|
|
787
|
+
assert self._transport is not None
|
|
788
|
+
topic = msg.get("topic") # None means query, string means set
|
|
789
|
+
await self._transport.send_topic(channel, topic)
|
|
790
|
+
return make_response(req_id, ok=True)
|
|
791
|
+
|
|
755
792
|
async def _ipc_irc_ask(self, req_id: str, msg: dict) -> dict:
|
|
756
793
|
"""Send a PRIVMSG and fire a question webhook. Response matching is TODO."""
|
|
757
794
|
channel = msg.get("channel", "")
|
|
758
795
|
question = msg.get("message", "")
|
|
759
796
|
if not channel:
|
|
760
797
|
return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
|
|
761
|
-
if not question:
|
|
798
|
+
if not question or not question.strip():
|
|
762
799
|
return make_response(req_id, ok=False, error="Missing 'message'")
|
|
763
800
|
assert self._transport is not None
|
|
764
801
|
await self._transport.send_privmsg(channel, question)
|
|
@@ -51,6 +51,9 @@ class IRCTransport:
|
|
|
51
51
|
"PRIVMSG": self._on_privmsg,
|
|
52
52
|
"NOTICE": self._on_notice,
|
|
53
53
|
"ROOMINVITE": self._on_roominvite,
|
|
54
|
+
"TOPIC": self._on_topic,
|
|
55
|
+
"331": self._on_numeric_topic,
|
|
56
|
+
"332": self._on_numeric_topic,
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
async def connect(self) -> None:
|
|
@@ -90,6 +93,10 @@ class IRCTransport:
|
|
|
90
93
|
for line in text.splitlines():
|
|
91
94
|
if line:
|
|
92
95
|
await self._send_raw(f"PRIVMSG {target} :{line}")
|
|
96
|
+
if target.startswith("#"):
|
|
97
|
+
self.buffer.add(target, self.nick, line)
|
|
98
|
+
else:
|
|
99
|
+
self.buffer.add(f"DM:{target}", self.nick, line)
|
|
93
100
|
|
|
94
101
|
async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
|
|
95
102
|
lines = [l for l in text.splitlines() if l]
|
|
@@ -112,11 +119,15 @@ class IRCTransport:
|
|
|
112
119
|
await self._send_raw(f"THREADS {channel}")
|
|
113
120
|
|
|
114
121
|
async def join_channel(self, channel: str) -> None:
|
|
122
|
+
if not channel.startswith("#"):
|
|
123
|
+
return
|
|
115
124
|
await self._send_raw(f"JOIN {channel}")
|
|
116
125
|
if channel not in self.channels:
|
|
117
126
|
self.channels.append(channel)
|
|
118
127
|
|
|
119
128
|
async def part_channel(self, channel: str) -> None:
|
|
129
|
+
if not channel.startswith("#"):
|
|
130
|
+
return
|
|
120
131
|
await self._send_raw(f"PART {channel}")
|
|
121
132
|
if channel in self.channels:
|
|
122
133
|
self.channels.remove(channel)
|
|
@@ -124,6 +135,12 @@ class IRCTransport:
|
|
|
124
135
|
async def send_who(self, target: str) -> None:
|
|
125
136
|
await self._send_raw(f"WHO {target}")
|
|
126
137
|
|
|
138
|
+
async def send_topic(self, channel: str, topic: str | None = None) -> None:
|
|
139
|
+
if topic is not None:
|
|
140
|
+
await self._send_raw(f"TOPIC {channel} :{topic}")
|
|
141
|
+
else:
|
|
142
|
+
await self._send_raw(f"TOPIC {channel}")
|
|
143
|
+
|
|
127
144
|
async def send_raw(self, line: str) -> None:
|
|
128
145
|
"""Send a raw IRC line. Public for commands like HISTORY."""
|
|
129
146
|
if self._writer:
|
|
@@ -202,6 +219,28 @@ class IRCTransport:
|
|
|
202
219
|
self._route_to_buffer(target, sender, text)
|
|
203
220
|
self._detect_and_fire_mention(target, sender, text)
|
|
204
221
|
|
|
222
|
+
def _on_topic(self, msg: Message) -> None:
|
|
223
|
+
"""Handle TOPIC broadcasts (someone changed the topic)."""
|
|
224
|
+
if len(msg.params) < 2:
|
|
225
|
+
return
|
|
226
|
+
channel = msg.params[0]
|
|
227
|
+
topic = msg.params[1]
|
|
228
|
+
sender = msg.prefix.split("!")[0] if msg.prefix else "server"
|
|
229
|
+
if channel.startswith("#"):
|
|
230
|
+
self.buffer.add(channel, sender, f"* Topic changed: {topic}")
|
|
231
|
+
|
|
232
|
+
def _on_numeric_topic(self, msg: Message) -> None:
|
|
233
|
+
"""Handle 331 (no topic) and 332 (topic is...) replies."""
|
|
234
|
+
if len(msg.params) < 2:
|
|
235
|
+
return
|
|
236
|
+
channel = msg.params[1]
|
|
237
|
+
if not channel.startswith("#"):
|
|
238
|
+
return
|
|
239
|
+
if msg.command == "331":
|
|
240
|
+
self.buffer.add(channel, "server", "* No topic is set")
|
|
241
|
+
elif msg.command == "332" and len(msg.params) >= 3:
|
|
242
|
+
self.buffer.add(channel, "server", f"* Topic: {msg.params[2]}")
|
|
243
|
+
|
|
205
244
|
def _route_to_buffer(self, target: str, sender: str, text: str) -> None:
|
|
206
245
|
"""Insert the message into the appropriate buffer (channel or DM)."""
|
|
207
246
|
if target.startswith("#"):
|
|
@@ -53,6 +53,14 @@ class MessageBuffer:
|
|
|
53
53
|
self._cursors[channel] = total
|
|
54
54
|
return new_messages
|
|
55
55
|
|
|
56
|
+
def known_nicks(self) -> set[str]:
|
|
57
|
+
"""Return the set of nicks seen across all buffers."""
|
|
58
|
+
nicks: set[str] = set()
|
|
59
|
+
for buf in self._buffers.values():
|
|
60
|
+
for m in buf:
|
|
61
|
+
nicks.add(m.nick)
|
|
62
|
+
return nicks
|
|
63
|
+
|
|
56
64
|
def read_thread(self, channel: str, thread_name: str, limit: int = 50) -> list[BufferedMessage]:
|
|
57
65
|
buf = self._buffers.get(channel)
|
|
58
66
|
if not buf:
|
|
@@ -134,6 +134,26 @@ python3 -m culture.clients.claude.skill.irc_client who <target>
|
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
137
|
+
### topic — get or set a channel topic
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python3 -m culture.clients.claude.skill.irc_client topic <channel> [topic text]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Get current topic:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python3 -m culture.clients.claude.skill.irc_client topic "#general"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Set topic:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python3 -m culture.clients.claude.skill.irc_client topic "#general" "Welcome to general chat"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
137
157
|
### compact — compact the agent's context window
|
|
138
158
|
|
|
139
159
|
```bash
|
|
@@ -183,6 +203,8 @@ result = await client.irc_join("#ops")
|
|
|
183
203
|
result = await client.irc_part("#ops")
|
|
184
204
|
result = await client.irc_channels()
|
|
185
205
|
result = await client.irc_who("#general")
|
|
206
|
+
result = await client.irc_topic("#general")
|
|
207
|
+
result = await client.irc_topic("#general", "Welcome to general chat")
|
|
186
208
|
result = await client.compact()
|
|
187
209
|
result = await client.clear()
|
|
188
210
|
|
|
@@ -162,6 +162,13 @@ class SkillClient:
|
|
|
162
162
|
"""Send a WHO query for a channel or nick."""
|
|
163
163
|
return await self._request("irc_who", target=target)
|
|
164
164
|
|
|
165
|
+
async def irc_topic(self, channel: str, topic: str | None = None) -> dict[str, Any]:
|
|
166
|
+
"""Get or set a channel topic."""
|
|
167
|
+
params: dict[str, Any] = {"channel": channel}
|
|
168
|
+
if topic is not None:
|
|
169
|
+
params["topic"] = topic
|
|
170
|
+
return await self._request("irc_topic", **params)
|
|
171
|
+
|
|
165
172
|
async def compact(self) -> dict[str, Any]:
|
|
166
173
|
"""Send /compact to the Claude agent runner."""
|
|
167
174
|
return await self._request("compact")
|
|
@@ -245,6 +252,12 @@ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
|
245
252
|
return await client.irc_who(args[1])
|
|
246
253
|
|
|
247
254
|
|
|
255
|
+
async def _cmd_topic(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
256
|
+
channel = args[1]
|
|
257
|
+
topic = " ".join(args[2:]) if len(args) > 2 else None
|
|
258
|
+
return await client.irc_topic(channel, topic)
|
|
259
|
+
|
|
260
|
+
|
|
248
261
|
async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
|
|
249
262
|
return await client.compact()
|
|
250
263
|
|
|
@@ -261,6 +274,7 @@ _SUBCOMMANDS: dict[str, Any] = {
|
|
|
261
274
|
"part": _cmd_part,
|
|
262
275
|
"channels": _cmd_channels,
|
|
263
276
|
"who": _cmd_who,
|
|
277
|
+
"topic": _cmd_topic,
|
|
264
278
|
"compact": _cmd_compact,
|
|
265
279
|
"clear": _cmd_clear,
|
|
266
280
|
}
|
|
@@ -271,7 +285,7 @@ async def _main(args: list[str]) -> None:
|
|
|
271
285
|
if not args:
|
|
272
286
|
print(
|
|
273
287
|
"Usage: irc_client.py <subcommand> [args...]\n"
|
|
274
|
-
"Subcommands: send, read, ask, join, part, channels, who, compact, clear",
|
|
288
|
+
"Subcommands: send, read, ask, join, part, channels, who, topic, compact, clear",
|
|
275
289
|
file=sys.stderr,
|
|
276
290
|
)
|
|
277
291
|
sys.exit(1)
|