agentirc-cli 4.0.0__tar.gz → 4.1.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-4.0.0 → agentirc_cli-4.1.0}/CHANGELOG.md +8 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/PKG-INFO +1 -1
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/config.py +2 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/daemon.py +35 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/irc_transport.py +7 -2
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/config.py +2 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/daemon.py +36 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/irc_transport.py +7 -2
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/config.py +2 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/daemon.py +35 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/irc_transport.py +7 -2
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/config.py +2 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/daemon.py +35 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/irc_transport.py +7 -2
- agentirc_cli-4.1.0/docs/channel-polling.md +51 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/config.py +2 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/daemon.py +37 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/irc_transport.py +7 -2
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/pyproject.toml +1 -1
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_console_client.py +1 -1
- agentirc_cli-4.1.0/tests/test_mention_alias.py +128 -0
- agentirc_cli-4.1.0/tests/test_poll_loop.py +129 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_room_persistence.py +3 -1
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/uv.lock +1 -1
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.flake8 +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.gitignore +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.pr_agent.toml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/.pylintrc +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/CLAUDE.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/CNAME +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/Gemfile +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/Gemfile.lock +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/LICENSE +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/README.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/SECURITY.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/_config.yml +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/__main__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/bot.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/_helpers.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/agent.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/bot.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/channel.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/mesh.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/cli/skills.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/app.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/commands.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/credentials.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/learn_prompt.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/mesh_config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/observer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/collector.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/model.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/overview/web/style.css +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/persistence.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/pidfile.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/commands.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/message.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/protocol/replies.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/__main__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/channel.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/ircd.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/remote_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/room_store.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/server_link.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skill.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skills/history.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skills/icon.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skills/rooms.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/server/thread_store.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/agent-lifecycle.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/agent-client.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/harness-conformance.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/index.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/layer1-core-irc.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/layer2-attention.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/layer3-skills.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/layer4-federation.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/layer5-agent-harness.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/server-architecture.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/architecture/threads.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/culture-cli.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/getting-started.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/index.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/SECURITY.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/bots.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/ci.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/cli.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/docs-site.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/index.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/ops-tooling.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/operations/publishing.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/rooms.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/server-rename.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/use-cases-index.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/docs/what-is-culture.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/sonar-project.properties +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/__init__.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/conftest.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_bot.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_bot_config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_channel.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_connection.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_console_commands.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_console_connection.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_console_icons.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_console_integration.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_daemon.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_discovery.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_federation.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_history.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_http_listener.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_ipc.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_mentions.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_message.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_messaging.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_modes.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_overview_model.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_overview_web.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_persistence.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_pidfile.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_rooms.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_skill_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_skills.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_socket_server.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_supervisor.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_template_engine.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_threads.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-4.0.0 → agentirc_cli-4.1.0}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [4.1.0] - 2026-04-06
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Channel polling: agents periodically check channels for unread messages (configurable via poll_interval, default 5 minutes)
|
|
13
|
+
- Nick alias matching: @culture now triggers spark-culture (short suffix matching)
|
|
14
|
+
|
|
7
15
|
## [4.0.0] - 2026-04-06
|
|
8
16
|
|
|
9
17
|
|
|
@@ -70,6 +70,7 @@ class DaemonConfig:
|
|
|
70
70
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
71
71
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
72
72
|
buffer_size: int = 500
|
|
73
|
+
poll_interval: int = 300
|
|
73
74
|
sleep_start: str = "23:00"
|
|
74
75
|
sleep_end: str = "08:00"
|
|
75
76
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
100
101
|
supervisor=supervisor,
|
|
101
102
|
webhooks=webhooks,
|
|
102
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
+
poll_interval=raw.get("poll_interval", 300),
|
|
103
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
104
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
105
107
|
agents=agents,
|
|
@@ -182,6 +182,9 @@ class ACPDaemon:
|
|
|
182
182
|
# 7. Sleep scheduler background task
|
|
183
183
|
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
184
184
|
|
|
185
|
+
# 8. Channel poll background task
|
|
186
|
+
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
187
|
+
|
|
185
188
|
logger.info(
|
|
186
189
|
"ACPDaemon started for %s (cmd=%s, socket=%s)",
|
|
187
190
|
self.agent.nick,
|
|
@@ -191,6 +194,11 @@ class ACPDaemon:
|
|
|
191
194
|
|
|
192
195
|
async def stop(self) -> None:
|
|
193
196
|
"""Cleanly shut down all components."""
|
|
197
|
+
if hasattr(self, "_poll_task") and self._poll_task:
|
|
198
|
+
self._poll_task.cancel()
|
|
199
|
+
await asyncio.gather(self._poll_task, return_exceptions=True)
|
|
200
|
+
self._poll_task = None
|
|
201
|
+
|
|
194
202
|
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
195
203
|
self._sleep_task.cancel()
|
|
196
204
|
await asyncio.gather(self._sleep_task, return_exceptions=True)
|
|
@@ -264,6 +272,33 @@ class ACPDaemon:
|
|
|
264
272
|
except Exception:
|
|
265
273
|
logger.exception("Sleep scheduler error")
|
|
266
274
|
|
|
275
|
+
async def _poll_loop(self) -> None:
|
|
276
|
+
"""Background task that periodically checks channels for unread messages."""
|
|
277
|
+
interval = self.config.poll_interval
|
|
278
|
+
if interval <= 0:
|
|
279
|
+
return
|
|
280
|
+
while True:
|
|
281
|
+
try:
|
|
282
|
+
await asyncio.sleep(interval)
|
|
283
|
+
if self._paused or not self._agent_runner or not self._agent_runner.is_running():
|
|
284
|
+
continue
|
|
285
|
+
for channel in self.agent.channels:
|
|
286
|
+
msgs = self._buffer.read(channel)
|
|
287
|
+
if not msgs:
|
|
288
|
+
continue
|
|
289
|
+
lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
|
|
290
|
+
prompt = (
|
|
291
|
+
f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
|
|
292
|
+
f"{lines}\n\n"
|
|
293
|
+
f"Respond naturally if any messages need your attention."
|
|
294
|
+
)
|
|
295
|
+
self._mention_targets.append(channel)
|
|
296
|
+
await self._agent_runner.send_prompt(prompt)
|
|
297
|
+
except asyncio.CancelledError:
|
|
298
|
+
raise
|
|
299
|
+
except Exception:
|
|
300
|
+
logger.exception("Poll loop error")
|
|
301
|
+
|
|
267
302
|
async def _graceful_shutdown(self) -> None:
|
|
268
303
|
"""Trigger a graceful shutdown, signaling any waiting stop event."""
|
|
269
304
|
logger.info("Graceful shutdown requested for %s", self.agent.nick)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
from typing import Callable
|
|
6
7
|
|
|
7
8
|
from culture.clients.acp.message_buffer import MessageBuffer
|
|
@@ -191,8 +192,12 @@ class IRCTransport:
|
|
|
191
192
|
self.buffer.add(target, sender, text)
|
|
192
193
|
else:
|
|
193
194
|
self.buffer.add(f"DM:{sender}", sender, text)
|
|
194
|
-
if self.on_mention
|
|
195
|
-
self.
|
|
195
|
+
if self.on_mention:
|
|
196
|
+
short = self.nick.split("-", 1)[1] if "-" in self.nick else None
|
|
197
|
+
if re.search(rf"@{re.escape(self.nick)}\b", text) or (
|
|
198
|
+
short and re.search(rf"@{re.escape(short)}\b", text)
|
|
199
|
+
):
|
|
200
|
+
self.on_mention(target, sender, text)
|
|
196
201
|
|
|
197
202
|
async def _on_notice(self, msg: Message) -> None:
|
|
198
203
|
if len(msg.params) < 2:
|
|
@@ -70,6 +70,7 @@ class DaemonConfig:
|
|
|
70
70
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
71
71
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
72
72
|
buffer_size: int = 500
|
|
73
|
+
poll_interval: int = 300
|
|
73
74
|
sleep_start: str = "23:00"
|
|
74
75
|
sleep_end: str = "08:00"
|
|
75
76
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -104,6 +105,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
104
105
|
supervisor=supervisor,
|
|
105
106
|
webhooks=webhooks,
|
|
106
107
|
buffer_size=raw.get("buffer_size", 500),
|
|
108
|
+
poll_interval=raw.get("poll_interval", 300),
|
|
107
109
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
108
110
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
109
111
|
agents=agents,
|
|
@@ -153,10 +153,18 @@ class AgentDaemon:
|
|
|
153
153
|
# 7. Sleep scheduler background task
|
|
154
154
|
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
155
155
|
|
|
156
|
+
# 8. Channel poll background task
|
|
157
|
+
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
158
|
+
|
|
156
159
|
logger.info("AgentDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
|
|
157
160
|
|
|
158
161
|
async def stop(self) -> None:
|
|
159
162
|
"""Cleanly shut down all components."""
|
|
163
|
+
if hasattr(self, "_poll_task") and self._poll_task:
|
|
164
|
+
self._poll_task.cancel()
|
|
165
|
+
await asyncio.gather(self._poll_task, return_exceptions=True)
|
|
166
|
+
self._poll_task = None
|
|
167
|
+
|
|
160
168
|
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
161
169
|
self._sleep_task.cancel()
|
|
162
170
|
await asyncio.gather(self._sleep_task, return_exceptions=True)
|
|
@@ -230,6 +238,34 @@ class AgentDaemon:
|
|
|
230
238
|
except Exception:
|
|
231
239
|
logger.exception("Sleep scheduler error")
|
|
232
240
|
|
|
241
|
+
async def _poll_loop(self) -> None:
|
|
242
|
+
"""Background task that periodically checks channels for unread messages."""
|
|
243
|
+
interval = self.config.poll_interval
|
|
244
|
+
if interval <= 0:
|
|
245
|
+
return
|
|
246
|
+
while True:
|
|
247
|
+
try:
|
|
248
|
+
await asyncio.sleep(interval)
|
|
249
|
+
if self._paused or not self._agent_runner or not self._agent_runner.is_running():
|
|
250
|
+
continue
|
|
251
|
+
for channel in self.agent.channels:
|
|
252
|
+
msgs = self._buffer.read(channel)
|
|
253
|
+
if not msgs:
|
|
254
|
+
continue
|
|
255
|
+
lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
|
|
256
|
+
prompt = (
|
|
257
|
+
f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
|
|
258
|
+
f"{lines}\n\n"
|
|
259
|
+
f"Respond naturally if any messages need your attention."
|
|
260
|
+
)
|
|
261
|
+
task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
262
|
+
self._background_tasks.add(task)
|
|
263
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
264
|
+
except asyncio.CancelledError:
|
|
265
|
+
raise
|
|
266
|
+
except Exception:
|
|
267
|
+
logger.exception("Poll loop error")
|
|
268
|
+
|
|
233
269
|
async def _graceful_shutdown(self) -> None:
|
|
234
270
|
"""Trigger a graceful shutdown, signaling any waiting stop event."""
|
|
235
271
|
logger.info("Graceful shutdown requested for %s", self.agent.nick)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
from typing import Callable
|
|
6
7
|
|
|
7
8
|
from culture.clients.claude.message_buffer import MessageBuffer
|
|
@@ -191,8 +192,12 @@ class IRCTransport:
|
|
|
191
192
|
self.buffer.add(target, sender, text)
|
|
192
193
|
else:
|
|
193
194
|
self.buffer.add(f"DM:{sender}", sender, text)
|
|
194
|
-
if self.on_mention
|
|
195
|
-
self.
|
|
195
|
+
if self.on_mention:
|
|
196
|
+
short = self.nick.split("-", 1)[1] if "-" in self.nick else None
|
|
197
|
+
if re.search(rf"@{re.escape(self.nick)}\b", text) or (
|
|
198
|
+
short and re.search(rf"@{re.escape(short)}\b", text)
|
|
199
|
+
):
|
|
200
|
+
self.on_mention(target, sender, text)
|
|
196
201
|
|
|
197
202
|
async def _on_notice(self, msg: Message) -> None:
|
|
198
203
|
if len(msg.params) < 2:
|
|
@@ -68,6 +68,7 @@ class DaemonConfig:
|
|
|
68
68
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
69
69
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
70
70
|
buffer_size: int = 500
|
|
71
|
+
poll_interval: int = 300
|
|
71
72
|
sleep_start: str = "23:00"
|
|
72
73
|
sleep_end: str = "08:00"
|
|
73
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
100
101
|
supervisor=supervisor,
|
|
101
102
|
webhooks=webhooks,
|
|
102
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
+
poll_interval=raw.get("poll_interval", 300),
|
|
103
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
104
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
105
107
|
agents=agents,
|
|
@@ -162,10 +162,18 @@ class CodexDaemon:
|
|
|
162
162
|
# 7. Sleep scheduler background task
|
|
163
163
|
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
164
164
|
|
|
165
|
+
# 8. Channel poll background task
|
|
166
|
+
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
167
|
+
|
|
165
168
|
logger.info("CodexDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
|
|
166
169
|
|
|
167
170
|
async def stop(self) -> None:
|
|
168
171
|
"""Cleanly shut down all components."""
|
|
172
|
+
if hasattr(self, "_poll_task") and self._poll_task:
|
|
173
|
+
self._poll_task.cancel()
|
|
174
|
+
await asyncio.gather(self._poll_task, return_exceptions=True)
|
|
175
|
+
self._poll_task = None
|
|
176
|
+
|
|
169
177
|
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
170
178
|
self._sleep_task.cancel()
|
|
171
179
|
await asyncio.gather(self._sleep_task, return_exceptions=True)
|
|
@@ -239,6 +247,33 @@ class CodexDaemon:
|
|
|
239
247
|
except Exception:
|
|
240
248
|
logger.exception("Sleep scheduler error")
|
|
241
249
|
|
|
250
|
+
async def _poll_loop(self) -> None:
|
|
251
|
+
"""Background task that periodically checks channels for unread messages."""
|
|
252
|
+
interval = self.config.poll_interval
|
|
253
|
+
if interval <= 0:
|
|
254
|
+
return
|
|
255
|
+
while True:
|
|
256
|
+
try:
|
|
257
|
+
await asyncio.sleep(interval)
|
|
258
|
+
if self._paused or not self._agent_runner or not self._agent_runner.is_running():
|
|
259
|
+
continue
|
|
260
|
+
for channel in self.agent.channels:
|
|
261
|
+
msgs = self._buffer.read(channel)
|
|
262
|
+
if not msgs:
|
|
263
|
+
continue
|
|
264
|
+
lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
|
|
265
|
+
prompt = (
|
|
266
|
+
f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
|
|
267
|
+
f"{lines}\n\n"
|
|
268
|
+
f"Respond naturally if any messages need your attention."
|
|
269
|
+
)
|
|
270
|
+
self._mention_targets.append(channel)
|
|
271
|
+
await self._agent_runner.send_prompt(prompt)
|
|
272
|
+
except asyncio.CancelledError:
|
|
273
|
+
raise
|
|
274
|
+
except Exception:
|
|
275
|
+
logger.exception("Poll loop error")
|
|
276
|
+
|
|
242
277
|
async def _graceful_shutdown(self) -> None:
|
|
243
278
|
"""Trigger a graceful shutdown, signaling any waiting stop event."""
|
|
244
279
|
logger.info("Graceful shutdown requested for %s", self.agent.nick)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
from typing import Callable
|
|
6
7
|
|
|
7
8
|
from culture.clients.codex.message_buffer import MessageBuffer
|
|
@@ -191,8 +192,12 @@ class IRCTransport:
|
|
|
191
192
|
self.buffer.add(target, sender, text)
|
|
192
193
|
else:
|
|
193
194
|
self.buffer.add(f"DM:{sender}", sender, text)
|
|
194
|
-
if self.on_mention
|
|
195
|
-
self.
|
|
195
|
+
if self.on_mention:
|
|
196
|
+
short = self.nick.split("-", 1)[1] if "-" in self.nick else None
|
|
197
|
+
if re.search(rf"@{re.escape(self.nick)}\b", text) or (
|
|
198
|
+
short and re.search(rf"@{re.escape(short)}\b", text)
|
|
199
|
+
):
|
|
200
|
+
self.on_mention(target, sender, text)
|
|
196
201
|
|
|
197
202
|
async def _on_notice(self, msg: Message) -> None:
|
|
198
203
|
if len(msg.params) < 2:
|
|
@@ -68,6 +68,7 @@ class DaemonConfig:
|
|
|
68
68
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
69
69
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
70
70
|
buffer_size: int = 500
|
|
71
|
+
poll_interval: int = 300
|
|
71
72
|
sleep_start: str = "23:00"
|
|
72
73
|
sleep_end: str = "08:00"
|
|
73
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -100,6 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
100
101
|
supervisor=supervisor,
|
|
101
102
|
webhooks=webhooks,
|
|
102
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
+
poll_interval=raw.get("poll_interval", 300),
|
|
103
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
104
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
105
107
|
agents=agents,
|
|
@@ -162,10 +162,18 @@ class CopilotDaemon:
|
|
|
162
162
|
# 7. Sleep scheduler background task
|
|
163
163
|
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
164
164
|
|
|
165
|
+
# 8. Channel poll background task
|
|
166
|
+
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
167
|
+
|
|
165
168
|
logger.info("CopilotDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path)
|
|
166
169
|
|
|
167
170
|
async def stop(self) -> None:
|
|
168
171
|
"""Cleanly shut down all components."""
|
|
172
|
+
if hasattr(self, "_poll_task") and self._poll_task:
|
|
173
|
+
self._poll_task.cancel()
|
|
174
|
+
await asyncio.gather(self._poll_task, return_exceptions=True)
|
|
175
|
+
self._poll_task = None
|
|
176
|
+
|
|
169
177
|
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
170
178
|
self._sleep_task.cancel()
|
|
171
179
|
await asyncio.gather(self._sleep_task, return_exceptions=True)
|
|
@@ -239,6 +247,33 @@ class CopilotDaemon:
|
|
|
239
247
|
except Exception:
|
|
240
248
|
logger.exception("Sleep scheduler error")
|
|
241
249
|
|
|
250
|
+
async def _poll_loop(self) -> None:
|
|
251
|
+
"""Background task that periodically checks channels for unread messages."""
|
|
252
|
+
interval = self.config.poll_interval
|
|
253
|
+
if interval <= 0:
|
|
254
|
+
return
|
|
255
|
+
while True:
|
|
256
|
+
try:
|
|
257
|
+
await asyncio.sleep(interval)
|
|
258
|
+
if self._paused or not self._agent_runner or not self._agent_runner.is_running():
|
|
259
|
+
continue
|
|
260
|
+
for channel in self.agent.channels:
|
|
261
|
+
msgs = self._buffer.read(channel)
|
|
262
|
+
if not msgs:
|
|
263
|
+
continue
|
|
264
|
+
lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
|
|
265
|
+
prompt = (
|
|
266
|
+
f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
|
|
267
|
+
f"{lines}\n\n"
|
|
268
|
+
f"Respond naturally if any messages need your attention."
|
|
269
|
+
)
|
|
270
|
+
self._mention_targets.append(channel)
|
|
271
|
+
await self._agent_runner.send_prompt(prompt)
|
|
272
|
+
except asyncio.CancelledError:
|
|
273
|
+
raise
|
|
274
|
+
except Exception:
|
|
275
|
+
logger.exception("Poll loop error")
|
|
276
|
+
|
|
242
277
|
async def _graceful_shutdown(self) -> None:
|
|
243
278
|
"""Trigger a graceful shutdown, signaling any waiting stop event."""
|
|
244
279
|
logger.info("Graceful shutdown requested for %s", self.agent.nick)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
from typing import Callable
|
|
6
7
|
|
|
7
8
|
from culture.clients.copilot.message_buffer import MessageBuffer
|
|
@@ -191,8 +192,12 @@ class IRCTransport:
|
|
|
191
192
|
self.buffer.add(target, sender, text)
|
|
192
193
|
else:
|
|
193
194
|
self.buffer.add(f"DM:{sender}", sender, text)
|
|
194
|
-
if self.on_mention
|
|
195
|
-
self.
|
|
195
|
+
if self.on_mention:
|
|
196
|
+
short = self.nick.split("-", 1)[1] if "-" in self.nick else None
|
|
197
|
+
if re.search(rf"@{re.escape(self.nick)}\b", text) or (
|
|
198
|
+
short and re.search(rf"@{re.escape(short)}\b", text)
|
|
199
|
+
):
|
|
200
|
+
self.on_mention(target, sender, text)
|
|
196
201
|
|
|
197
202
|
async def _on_notice(self, msg: Message) -> None:
|
|
198
203
|
if len(msg.params) < 2:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Channel Polling
|
|
2
|
+
|
|
3
|
+
Agents periodically check their subscribed channels for unread messages and respond to anything that needs attention. This runs alongside the existing @mention system.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
- **@mentions**: Trigger immediate agent activation (no change)
|
|
8
|
+
- **Polling**: Every `poll_interval` seconds, the daemon checks each channel for unread messages. If any exist, they're sent to the agent as context.
|
|
9
|
+
|
|
10
|
+
The poll prompt looks like:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
[IRC Channel Poll: #general] Recent unread messages:
|
|
14
|
+
<spark-ori> hello everyone
|
|
15
|
+
<spark-ori> anyone working on the API?
|
|
16
|
+
|
|
17
|
+
Respond naturally if any messages need your attention.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Add `poll_interval` to `agents.yaml`:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
poll_interval: 300 # seconds (default: 5 minutes)
|
|
26
|
+
buffer_size: 500
|
|
27
|
+
sleep_start: '23:00'
|
|
28
|
+
sleep_end: '08:00'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Set `poll_interval: 0` to disable polling (agents only respond to @mentions).
|
|
32
|
+
|
|
33
|
+
## Nick Alias Matching
|
|
34
|
+
|
|
35
|
+
Agents respond to both their full nick and short suffix:
|
|
36
|
+
|
|
37
|
+
| Agent Nick | Responds To |
|
|
38
|
+
|------------|-------------|
|
|
39
|
+
| `spark-culture` | `@spark-culture`, `@culture` |
|
|
40
|
+
| `spark-daria` | `@spark-daria`, `@daria` |
|
|
41
|
+
| `thor-claude` | `@thor-claude`, `@claude` |
|
|
42
|
+
|
|
43
|
+
The short name is the part after the first hyphen in the nick.
|
|
44
|
+
|
|
45
|
+
## Interaction with Sleep Schedule
|
|
46
|
+
|
|
47
|
+
Polling respects the sleep schedule. When an agent is paused (during sleep hours), the poll loop skips processing. Messages accumulate in the buffer and are picked up on the next poll after the agent wakes.
|
|
48
|
+
|
|
49
|
+
## Interaction with @mentions
|
|
50
|
+
|
|
51
|
+
If an @mention arrives between polls, it triggers immediately. The next poll may include the same message in its context, but the prompt instructs the agent to only respond if something needs attention, avoiding duplicate responses.
|
|
@@ -68,6 +68,7 @@ class DaemonConfig:
|
|
|
68
68
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
69
69
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
70
70
|
buffer_size: int = 500
|
|
71
|
+
poll_interval: int = 300
|
|
71
72
|
sleep_start: str = "23:00"
|
|
72
73
|
sleep_end: str = "08:00"
|
|
73
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -98,6 +99,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
98
99
|
supervisor=supervisor,
|
|
99
100
|
webhooks=webhooks,
|
|
100
101
|
buffer_size=raw.get("buffer_size", 500),
|
|
102
|
+
poll_interval=raw.get("poll_interval", 300),
|
|
101
103
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
102
104
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
103
105
|
agents=agents,
|
|
@@ -137,8 +137,16 @@ class AgentDaemon:
|
|
|
137
137
|
# 6. Sleep scheduler background task
|
|
138
138
|
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
139
139
|
|
|
140
|
+
# 7. Channel poll background task
|
|
141
|
+
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
142
|
+
|
|
140
143
|
async def stop(self) -> None:
|
|
141
144
|
"""Stop all daemon components."""
|
|
145
|
+
if hasattr(self, "_poll_task") and self._poll_task:
|
|
146
|
+
self._poll_task.cancel()
|
|
147
|
+
await asyncio.gather(self._poll_task, return_exceptions=True)
|
|
148
|
+
self._poll_task = None
|
|
149
|
+
|
|
142
150
|
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
143
151
|
self._sleep_task.cancel()
|
|
144
152
|
await asyncio.gather(self._sleep_task, return_exceptions=True)
|
|
@@ -214,6 +222,35 @@ class AgentDaemon:
|
|
|
214
222
|
except Exception:
|
|
215
223
|
logger.exception("Sleep scheduler error")
|
|
216
224
|
|
|
225
|
+
async def _poll_loop(self) -> None:
|
|
226
|
+
"""Background task that periodically checks channels for unread messages."""
|
|
227
|
+
interval = self.config.poll_interval
|
|
228
|
+
if interval <= 0:
|
|
229
|
+
return
|
|
230
|
+
while True:
|
|
231
|
+
try:
|
|
232
|
+
await asyncio.sleep(interval)
|
|
233
|
+
if self._paused or not self._agent_runner or not self._agent_runner.is_running():
|
|
234
|
+
continue
|
|
235
|
+
for channel in self.agent.channels:
|
|
236
|
+
msgs = self._buffer.read(channel)
|
|
237
|
+
if not msgs:
|
|
238
|
+
continue
|
|
239
|
+
lines = "\n".join(f" <{m.nick}> {m.text}" for m in msgs)
|
|
240
|
+
prompt = (
|
|
241
|
+
f"[IRC Channel Poll: {channel}] Recent unread messages:\n"
|
|
242
|
+
f"{lines}\n\n"
|
|
243
|
+
f"Respond naturally if any messages need your attention."
|
|
244
|
+
)
|
|
245
|
+
self._mention_targets.append(channel)
|
|
246
|
+
task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
247
|
+
self._background_tasks.add(task)
|
|
248
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
249
|
+
except asyncio.CancelledError:
|
|
250
|
+
raise
|
|
251
|
+
except Exception:
|
|
252
|
+
logger.exception("Poll loop error")
|
|
253
|
+
|
|
217
254
|
def _on_mention(self, target: str, sender: str, text: str) -> None:
|
|
218
255
|
"""Called when the agent is @mentioned. Sends prompt to runner.
|
|
219
256
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
6
7
|
from typing import Callable
|
|
7
8
|
|
|
8
9
|
from culture.clients.BACKEND.message_buffer import MessageBuffer
|
|
@@ -192,8 +193,12 @@ class IRCTransport:
|
|
|
192
193
|
self.buffer.add(target, sender, text)
|
|
193
194
|
else:
|
|
194
195
|
self.buffer.add(f"DM:{sender}", sender, text)
|
|
195
|
-
if self.on_mention
|
|
196
|
-
self.
|
|
196
|
+
if self.on_mention:
|
|
197
|
+
short = self.nick.split("-", 1)[1] if "-" in self.nick else None
|
|
198
|
+
if re.search(rf"@{re.escape(self.nick)}\b", text) or (
|
|
199
|
+
short and re.search(rf"@{re.escape(short)}\b", text)
|
|
200
|
+
):
|
|
201
|
+
self.on_mention(target, sender, text)
|
|
197
202
|
|
|
198
203
|
async def _on_notice(self, msg: Message) -> None:
|
|
199
204
|
if len(msg.params) < 2:
|
|
@@ -184,7 +184,7 @@ def test_chat_message_has_timestamp():
|
|
|
184
184
|
def test_chat_message_explicit_timestamp():
|
|
185
185
|
"""ChatMessage accepts an explicit timestamp."""
|
|
186
186
|
msg = ChatMessage(channel="#test", nick="ori", text="hello", timestamp=1234567890.0)
|
|
187
|
-
assert msg.timestamp == 1234567890.0
|
|
187
|
+
assert msg.timestamp == pytest.approx(1234567890.0)
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
# ---------------------------------------------------------------------------
|