agentirc-cli 4.1.0__tar.gz → 4.1.2__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.1.0 → agentirc_cli-4.1.2}/CHANGELOG.md +16 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/PKG-INFO +1 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/agent_runner.py +26 -8
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/config.py +2 -2
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/daemon.py +9 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/config.py +2 -2
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/agent_runner.py +4 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/config.py +2 -2
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/daemon.py +9 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/agent_runner.py +4 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/config.py +2 -2
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/daemon.py +9 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/channel-polling.md +1 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/config.py +2 -2
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/daemon.py +16 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/pyproject.toml +1 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_acp_daemon.py +46 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_mention_alias.py +0 -1
- agentirc_cli-4.1.2/tests/test_mention_target_cleanup.py +135 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/uv.lock +1 -1
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.flake8 +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.gitignore +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.pr_agent.toml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/.pylintrc +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/CLAUDE.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/CNAME +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/Gemfile +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/Gemfile.lock +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/LICENSE +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/README.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/SECURITY.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/_config.yml +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/__main__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/bot.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/_helpers.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/agent.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/bot.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/channel.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/mesh.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/cli/skills.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/app.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/commands.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/credentials.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/learn_prompt.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/mesh_config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/observer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/collector.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/model.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/overview/web/style.css +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/persistence.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/pidfile.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/commands.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/message.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/protocol/replies.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/__main__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/channel.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/ircd.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/remote_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/room_store.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/server_link.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skill.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skills/history.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skills/icon.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skills/rooms.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/server/thread_store.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/agent-lifecycle.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/agent-client.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/harness-conformance.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/index.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/layer1-core-irc.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/layer2-attention.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/layer3-skills.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/layer4-federation.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/layer5-agent-harness.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/server-architecture.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/architecture/threads.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/culture-cli.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/getting-started.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/index.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/SECURITY.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/bots.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/ci.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/cli.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/docs-site.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/index.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/ops-tooling.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/operations/publishing.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/rooms.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/server-rename.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/use-cases-index.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/docs/what-is-culture.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/sonar-project.properties +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/__init__.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/conftest.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_bot.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_bot_config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_channel.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_connection.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_console_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_console_commands.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_console_connection.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_console_icons.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_console_integration.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_daemon.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_discovery.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_federation.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_history.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_http_listener.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_ipc.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_mentions.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_message.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_messaging.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_modes.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_overview_model.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_overview_web.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_persistence.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_pidfile.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_rooms.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_skill_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_skills.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_socket_server.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_supervisor.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_template_engine.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_threads.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-4.1.0 → agentirc_cli-4.1.2}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,22 @@ 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.2] - 2026-04-06
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Clean up _mention_targets deque on prompt failure to prevent misrouted responses
|
|
13
|
+
|
|
14
|
+
## [4.1.1] - 2026-04-06
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fix ACP/Codex/Copilot poll loop to use fire-and-forget (race condition fix)
|
|
20
|
+
- Increase ACP prompt timeout from 120s to 300s with retry on timeout (issue #115)
|
|
21
|
+
- Lower default poll_interval from 300s to 60s across all backends
|
|
22
|
+
|
|
7
23
|
## [4.1.0] - 2026-04-06
|
|
8
24
|
|
|
9
25
|
|
|
@@ -33,6 +33,7 @@ class ACPAgentRunner:
|
|
|
33
33
|
system_prompt: str = "",
|
|
34
34
|
on_exit: Callable[[int], Awaitable[None]] | None = None,
|
|
35
35
|
on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
|
|
36
|
+
on_turn_error: Callable[[], Awaitable[None]] | None = None,
|
|
36
37
|
) -> None:
|
|
37
38
|
self.model = model
|
|
38
39
|
self.directory = directory
|
|
@@ -40,6 +41,7 @@ class ACPAgentRunner:
|
|
|
40
41
|
self.system_prompt = system_prompt
|
|
41
42
|
self.on_exit = on_exit
|
|
42
43
|
self.on_message = on_message
|
|
44
|
+
self.on_turn_error = on_turn_error
|
|
43
45
|
|
|
44
46
|
self._isolated_home: str | None = None
|
|
45
47
|
self._process: asyncio.subprocess.Process | None = None
|
|
@@ -353,14 +355,26 @@ class ACPAgentRunner:
|
|
|
353
355
|
|
|
354
356
|
try:
|
|
355
357
|
self._busy = True
|
|
356
|
-
|
|
357
|
-
"
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
358
|
+
prompt_params = {
|
|
359
|
+
"sessionId": self._session_id,
|
|
360
|
+
"prompt": [{"type": "text", "text": text}],
|
|
361
|
+
}
|
|
362
|
+
try:
|
|
363
|
+
resp = await self._send_request(
|
|
364
|
+
"session/prompt",
|
|
365
|
+
prompt_params,
|
|
366
|
+
timeout=300,
|
|
367
|
+
)
|
|
368
|
+
except TimeoutError:
|
|
369
|
+
logger.warning(
|
|
370
|
+
"ACP prompt timed out, retrying once: %s",
|
|
371
|
+
text[:80],
|
|
372
|
+
)
|
|
373
|
+
resp = await self._send_request(
|
|
374
|
+
"session/prompt",
|
|
375
|
+
prompt_params,
|
|
376
|
+
timeout=300,
|
|
377
|
+
)
|
|
364
378
|
|
|
365
379
|
result = resp.get("result", {})
|
|
366
380
|
if "stopReason" in result:
|
|
@@ -372,6 +386,10 @@ class ACPAgentRunner:
|
|
|
372
386
|
|
|
373
387
|
except Exception:
|
|
374
388
|
logger.exception("ACP turn error")
|
|
389
|
+
if self.on_turn_error:
|
|
390
|
+
await self.on_turn_error()
|
|
391
|
+
finally:
|
|
392
|
+
self._busy = False
|
|
375
393
|
|
|
376
394
|
except asyncio.CancelledError:
|
|
377
395
|
raise
|
|
@@ -70,7 +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 =
|
|
73
|
+
poll_interval: int = 60
|
|
74
74
|
sleep_start: str = "23:00"
|
|
75
75
|
sleep_end: str = "08:00"
|
|
76
76
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -101,7 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
101
101
|
supervisor=supervisor,
|
|
102
102
|
webhooks=webhooks,
|
|
103
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
-
poll_interval=raw.get("poll_interval",
|
|
104
|
+
poll_interval=raw.get("poll_interval", 60),
|
|
105
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
106
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
107
107
|
agents=agents,
|
|
@@ -293,7 +293,9 @@ class ACPDaemon:
|
|
|
293
293
|
f"Respond naturally if any messages need your attention."
|
|
294
294
|
)
|
|
295
295
|
self._mention_targets.append(channel)
|
|
296
|
-
|
|
296
|
+
task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
297
|
+
self._background_tasks.add(task)
|
|
298
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
297
299
|
except asyncio.CancelledError:
|
|
298
300
|
raise
|
|
299
301
|
except Exception:
|
|
@@ -316,6 +318,11 @@ class ACPDaemon:
|
|
|
316
318
|
# Agent runner helpers
|
|
317
319
|
# ------------------------------------------------------------------
|
|
318
320
|
|
|
321
|
+
async def _on_turn_error(self) -> None:
|
|
322
|
+
"""Clean up stale relay target when a prompt fails."""
|
|
323
|
+
if self._mention_targets:
|
|
324
|
+
self._mention_targets.popleft()
|
|
325
|
+
|
|
319
326
|
async def _start_agent_runner(self) -> None:
|
|
320
327
|
self._agent_runner = ACPAgentRunner(
|
|
321
328
|
model=self.agent.model,
|
|
@@ -324,6 +331,7 @@ class ACPDaemon:
|
|
|
324
331
|
system_prompt=self._build_system_prompt(),
|
|
325
332
|
on_exit=self._on_agent_exit,
|
|
326
333
|
on_message=self._on_agent_message,
|
|
334
|
+
on_turn_error=self._on_turn_error,
|
|
327
335
|
)
|
|
328
336
|
# Absorb the system prompt response without relaying to IRC
|
|
329
337
|
self._mention_targets.append(None)
|
|
@@ -70,7 +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 =
|
|
73
|
+
poll_interval: int = 60
|
|
74
74
|
sleep_start: str = "23:00"
|
|
75
75
|
sleep_end: str = "08:00"
|
|
76
76
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -105,7 +105,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
105
105
|
supervisor=supervisor,
|
|
106
106
|
webhooks=webhooks,
|
|
107
107
|
buffer_size=raw.get("buffer_size", 500),
|
|
108
|
-
poll_interval=raw.get("poll_interval",
|
|
108
|
+
poll_interval=raw.get("poll_interval", 60),
|
|
109
109
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
110
110
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
111
111
|
agents=agents,
|
|
@@ -23,12 +23,14 @@ class CodexAgentRunner:
|
|
|
23
23
|
system_prompt: str = "",
|
|
24
24
|
on_exit: Callable[[int], Awaitable[None]] | None = None,
|
|
25
25
|
on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
|
|
26
|
+
on_turn_error: Callable[[], Awaitable[None]] | None = None,
|
|
26
27
|
) -> None:
|
|
27
28
|
self.model = model
|
|
28
29
|
self.directory = directory
|
|
29
30
|
self.system_prompt = system_prompt
|
|
30
31
|
self.on_exit = on_exit
|
|
31
32
|
self.on_message = on_message
|
|
33
|
+
self.on_turn_error = on_turn_error
|
|
32
34
|
|
|
33
35
|
self._isolated_home: str | None = None
|
|
34
36
|
self._process: asyncio.subprocess.Process | None = None
|
|
@@ -318,6 +320,8 @@ class CodexAgentRunner:
|
|
|
318
320
|
|
|
319
321
|
except Exception:
|
|
320
322
|
logger.exception("Codex turn error")
|
|
323
|
+
if self.on_turn_error:
|
|
324
|
+
await self.on_turn_error()
|
|
321
325
|
|
|
322
326
|
except asyncio.CancelledError:
|
|
323
327
|
raise
|
|
@@ -68,7 +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 =
|
|
71
|
+
poll_interval: int = 60
|
|
72
72
|
sleep_start: str = "23:00"
|
|
73
73
|
sleep_end: str = "08:00"
|
|
74
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -101,7 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
101
101
|
supervisor=supervisor,
|
|
102
102
|
webhooks=webhooks,
|
|
103
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
-
poll_interval=raw.get("poll_interval",
|
|
104
|
+
poll_interval=raw.get("poll_interval", 60),
|
|
105
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
106
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
107
107
|
agents=agents,
|
|
@@ -268,7 +268,9 @@ class CodexDaemon:
|
|
|
268
268
|
f"Respond naturally if any messages need your attention."
|
|
269
269
|
)
|
|
270
270
|
self._mention_targets.append(channel)
|
|
271
|
-
|
|
271
|
+
task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
272
|
+
self._background_tasks.add(task)
|
|
273
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
272
274
|
except asyncio.CancelledError:
|
|
273
275
|
raise
|
|
274
276
|
except Exception:
|
|
@@ -291,6 +293,11 @@ class CodexDaemon:
|
|
|
291
293
|
# Agent runner helpers
|
|
292
294
|
# ------------------------------------------------------------------
|
|
293
295
|
|
|
296
|
+
async def _on_turn_error(self) -> None:
|
|
297
|
+
"""Clean up stale relay target when a prompt fails."""
|
|
298
|
+
if self._mention_targets:
|
|
299
|
+
self._mention_targets.popleft()
|
|
300
|
+
|
|
294
301
|
async def _start_agent_runner(self) -> None:
|
|
295
302
|
self._agent_runner = CodexAgentRunner(
|
|
296
303
|
model=self.agent.model,
|
|
@@ -298,6 +305,7 @@ class CodexDaemon:
|
|
|
298
305
|
system_prompt=self._build_system_prompt(),
|
|
299
306
|
on_exit=self._on_agent_exit,
|
|
300
307
|
on_message=self._on_agent_message,
|
|
308
|
+
on_turn_error=self._on_turn_error,
|
|
301
309
|
)
|
|
302
310
|
await self._agent_runner.start()
|
|
303
311
|
logger.info("CodexAgentRunner started for %s", self.agent.nick)
|
|
@@ -23,6 +23,7 @@ class CopilotAgentRunner:
|
|
|
23
23
|
skill_directories: list[str] | None = None,
|
|
24
24
|
on_exit: Callable[[int], Awaitable[None]] | None = None,
|
|
25
25
|
on_message: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
|
|
26
|
+
on_turn_error: Callable[[], Awaitable[None]] | None = None,
|
|
26
27
|
) -> None:
|
|
27
28
|
self.model = model
|
|
28
29
|
self.directory = directory
|
|
@@ -30,6 +31,7 @@ class CopilotAgentRunner:
|
|
|
30
31
|
self.skill_directories = skill_directories or []
|
|
31
32
|
self.on_exit = on_exit
|
|
32
33
|
self.on_message = on_message
|
|
34
|
+
self.on_turn_error = on_turn_error
|
|
33
35
|
|
|
34
36
|
self._isolated_home: str | None = None
|
|
35
37
|
self._client: Any = None
|
|
@@ -158,6 +160,8 @@ class CopilotAgentRunner:
|
|
|
158
160
|
|
|
159
161
|
except Exception:
|
|
160
162
|
logger.exception("Copilot session turn error")
|
|
163
|
+
if self.on_turn_error:
|
|
164
|
+
await self.on_turn_error()
|
|
161
165
|
if not self._stopping:
|
|
162
166
|
self._running = False
|
|
163
167
|
if self.on_exit:
|
|
@@ -68,7 +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 =
|
|
71
|
+
poll_interval: int = 60
|
|
72
72
|
sleep_start: str = "23:00"
|
|
73
73
|
sleep_end: str = "08:00"
|
|
74
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -101,7 +101,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
101
101
|
supervisor=supervisor,
|
|
102
102
|
webhooks=webhooks,
|
|
103
103
|
buffer_size=raw.get("buffer_size", 500),
|
|
104
|
-
poll_interval=raw.get("poll_interval",
|
|
104
|
+
poll_interval=raw.get("poll_interval", 60),
|
|
105
105
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
106
106
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
107
107
|
agents=agents,
|
|
@@ -268,7 +268,9 @@ class CopilotDaemon:
|
|
|
268
268
|
f"Respond naturally if any messages need your attention."
|
|
269
269
|
)
|
|
270
270
|
self._mention_targets.append(channel)
|
|
271
|
-
|
|
271
|
+
task = asyncio.create_task(self._agent_runner.send_prompt(prompt))
|
|
272
|
+
self._background_tasks.add(task)
|
|
273
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
272
274
|
except asyncio.CancelledError:
|
|
273
275
|
raise
|
|
274
276
|
except Exception:
|
|
@@ -291,6 +293,11 @@ class CopilotDaemon:
|
|
|
291
293
|
# Agent runner helpers
|
|
292
294
|
# ------------------------------------------------------------------
|
|
293
295
|
|
|
296
|
+
async def _on_turn_error(self) -> None:
|
|
297
|
+
"""Clean up stale relay target when a prompt fails."""
|
|
298
|
+
if self._mention_targets:
|
|
299
|
+
self._mention_targets.popleft()
|
|
300
|
+
|
|
294
301
|
async def _start_agent_runner(self) -> None:
|
|
295
302
|
# Resolve installed skill path for the Copilot session
|
|
296
303
|
skill_dirs: list[str] = []
|
|
@@ -305,6 +312,7 @@ class CopilotDaemon:
|
|
|
305
312
|
skill_directories=skill_dirs,
|
|
306
313
|
on_exit=self._on_agent_exit,
|
|
307
314
|
on_message=self._on_agent_message,
|
|
315
|
+
on_turn_error=self._on_turn_error,
|
|
308
316
|
)
|
|
309
317
|
await self._agent_runner.start()
|
|
310
318
|
logger.info("CopilotAgentRunner started for %s", self.agent.nick)
|
|
@@ -22,7 +22,7 @@ Respond naturally if any messages need your attention.
|
|
|
22
22
|
Add `poll_interval` to `agents.yaml`:
|
|
23
23
|
|
|
24
24
|
```yaml
|
|
25
|
-
poll_interval:
|
|
25
|
+
poll_interval: 60 # seconds (default: 1 minute)
|
|
26
26
|
buffer_size: 500
|
|
27
27
|
sleep_start: '23:00'
|
|
28
28
|
sleep_end: '08:00'
|
|
@@ -68,7 +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 =
|
|
71
|
+
poll_interval: int = 60
|
|
72
72
|
sleep_start: str = "23:00"
|
|
73
73
|
sleep_end: str = "08:00"
|
|
74
74
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
@@ -99,7 +99,7 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
99
99
|
supervisor=supervisor,
|
|
100
100
|
webhooks=webhooks,
|
|
101
101
|
buffer_size=raw.get("buffer_size", 500),
|
|
102
|
-
poll_interval=raw.get("poll_interval",
|
|
102
|
+
poll_interval=raw.get("poll_interval", 60),
|
|
103
103
|
sleep_start=raw.get("sleep_start", "23:00"),
|
|
104
104
|
sleep_end=raw.get("sleep_end", "08:00"),
|
|
105
105
|
agents=agents,
|
|
@@ -60,8 +60,15 @@ class AgentDaemon:
|
|
|
60
60
|
self._buffer: MessageBuffer | None = None
|
|
61
61
|
self._socket_server: SocketServer | None = None
|
|
62
62
|
self._webhook: WebhookClient | None = None
|
|
63
|
+
self._agent_runner: Any = None
|
|
63
64
|
self._stop_event: asyncio.Event | None = None
|
|
64
65
|
|
|
66
|
+
# FIFO queue of relay targets — each @mention or poll enqueues a
|
|
67
|
+
# target, each agent response dequeues one for correct routing.
|
|
68
|
+
from collections import deque
|
|
69
|
+
|
|
70
|
+
self._mention_targets: deque[str] = deque()
|
|
71
|
+
|
|
65
72
|
# Pause/sleep state
|
|
66
73
|
self._paused: bool = False
|
|
67
74
|
self._last_activation: float | None = None
|
|
@@ -251,6 +258,15 @@ class AgentDaemon:
|
|
|
251
258
|
except Exception:
|
|
252
259
|
logger.exception("Poll loop error")
|
|
253
260
|
|
|
261
|
+
async def _on_turn_error(self) -> None:
|
|
262
|
+
"""Clean up stale relay target when a prompt fails.
|
|
263
|
+
|
|
264
|
+
Wire this as the ``on_turn_error`` callback on your agent runner so
|
|
265
|
+
the ``_mention_targets`` deque stays in sync with the prompt queue.
|
|
266
|
+
"""
|
|
267
|
+
if self._mention_targets:
|
|
268
|
+
self._mention_targets.popleft()
|
|
269
|
+
|
|
254
270
|
def _on_mention(self, target: str, sender: str, text: str) -> None:
|
|
255
271
|
"""Called when the agent is @mentioned. Sends prompt to runner.
|
|
256
272
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
3
|
import tempfile
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
@@ -153,3 +154,48 @@ async def test_acp_relay_target_fifo(server, make_client):
|
|
|
153
154
|
assert any("dm response" in l for l in lines), f"Expected 'dm response', got: {lines}"
|
|
154
155
|
|
|
155
156
|
await daemon.stop()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _inject_fake_acp_runner(daemon):
|
|
160
|
+
"""Inject a fake agent runner that records prompts."""
|
|
161
|
+
runner = MagicMock()
|
|
162
|
+
runner.is_running.return_value = True
|
|
163
|
+
runner.send_prompt = AsyncMock()
|
|
164
|
+
runner.stop = AsyncMock()
|
|
165
|
+
daemon._agent_runner = runner
|
|
166
|
+
return runner
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
async def test_acp_poll_loop_uses_fire_and_forget(server, make_client):
|
|
171
|
+
"""ACP poll loop should use create_task (fire-and-forget), not blocking await."""
|
|
172
|
+
config = DaemonConfig(
|
|
173
|
+
server=ServerConnConfig(host="127.0.0.1", port=server.config.port),
|
|
174
|
+
poll_interval=1,
|
|
175
|
+
)
|
|
176
|
+
agent = AgentConfig(nick="testserv-opencode", directory="/tmp", channels=["#general"])
|
|
177
|
+
sock_dir = tempfile.mkdtemp()
|
|
178
|
+
daemon = ACPDaemon(config, agent, socket_dir=sock_dir, skip_agent=True)
|
|
179
|
+
await daemon.start()
|
|
180
|
+
runner = _inject_fake_acp_runner(daemon)
|
|
181
|
+
await asyncio.sleep(0.5)
|
|
182
|
+
|
|
183
|
+
# Human sends a message (no @mention)
|
|
184
|
+
human = await make_client(nick="testserv-ori", user="ori")
|
|
185
|
+
await human.send("JOIN #general")
|
|
186
|
+
await human.recv_all(timeout=0.3)
|
|
187
|
+
await human.send("PRIVMSG #general :hello from poll test")
|
|
188
|
+
|
|
189
|
+
# Wait for poll to fire
|
|
190
|
+
await asyncio.sleep(1.5)
|
|
191
|
+
|
|
192
|
+
# Poll loop should have sent the prompt
|
|
193
|
+
assert runner.send_prompt.call_count >= 1
|
|
194
|
+
prompt = runner.send_prompt.call_args[0][0]
|
|
195
|
+
assert "hello from poll test" in prompt
|
|
196
|
+
assert "[IRC Channel Poll: #general]" in prompt
|
|
197
|
+
|
|
198
|
+
# The poll should also have enqueued a relay target
|
|
199
|
+
assert len(daemon._mention_targets) >= 1
|
|
200
|
+
|
|
201
|
+
await daemon.stop()
|
|
@@ -22,7 +22,6 @@ async def test_mention_full_nick(server, make_client):
|
|
|
22
22
|
daemon = AgentDaemon(config, agent, socket_dir=sock_dir, skip_claude=True)
|
|
23
23
|
|
|
24
24
|
mentions = []
|
|
25
|
-
original_on_mention = daemon._on_mention
|
|
26
25
|
daemon._on_mention = lambda t, s, txt: mentions.append((t, s, txt))
|
|
27
26
|
daemon._transport_on_mention = daemon._on_mention
|
|
28
27
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Tests for _mention_targets deque cleanup on prompt failure.
|
|
2
|
+
|
|
3
|
+
When a prompt fails (timeout, error), the corresponding _mention_targets
|
|
4
|
+
entry must be cleaned up so that future responses route correctly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import tempfile
|
|
9
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from culture.clients.acp.config import (
|
|
14
|
+
AgentConfig,
|
|
15
|
+
DaemonConfig,
|
|
16
|
+
ServerConnConfig,
|
|
17
|
+
)
|
|
18
|
+
from culture.clients.acp.daemon import ACPDaemon
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _make_daemon(server_port: int) -> ACPDaemon:
|
|
22
|
+
config = DaemonConfig(
|
|
23
|
+
server=ServerConnConfig(host="127.0.0.1", port=server_port),
|
|
24
|
+
poll_interval=0,
|
|
25
|
+
)
|
|
26
|
+
agent = AgentConfig(
|
|
27
|
+
nick="testserv-bot",
|
|
28
|
+
directory="/tmp",
|
|
29
|
+
channels=["#general"],
|
|
30
|
+
acp_command=["echo"],
|
|
31
|
+
)
|
|
32
|
+
sock_dir = tempfile.mkdtemp()
|
|
33
|
+
return ACPDaemon(config, agent, socket_dir=sock_dir, skip_agent=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _inject_fake_runner(daemon):
|
|
37
|
+
"""Inject a fake agent runner that records prompts."""
|
|
38
|
+
runner = MagicMock()
|
|
39
|
+
runner.is_running.return_value = True
|
|
40
|
+
runner.send_prompt = AsyncMock()
|
|
41
|
+
runner.stop = AsyncMock()
|
|
42
|
+
daemon._agent_runner = runner
|
|
43
|
+
return runner
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_on_turn_error_pops_stale_target(server):
|
|
48
|
+
"""_on_turn_error should pop the front entry from _mention_targets."""
|
|
49
|
+
daemon = _make_daemon(server.config.port)
|
|
50
|
+
await daemon.start()
|
|
51
|
+
|
|
52
|
+
# Simulate: poll enqueued a target, then the prompt failed
|
|
53
|
+
daemon._mention_targets.append("#general")
|
|
54
|
+
daemon._mention_targets.append("#code-review")
|
|
55
|
+
assert len(daemon._mention_targets) == 2
|
|
56
|
+
|
|
57
|
+
await daemon._on_turn_error()
|
|
58
|
+
assert len(daemon._mention_targets) == 1
|
|
59
|
+
assert daemon._mention_targets[0] == "#code-review"
|
|
60
|
+
|
|
61
|
+
await daemon.stop()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_on_turn_error_empty_deque_is_safe(server):
|
|
66
|
+
"""_on_turn_error should be a no-op on an empty deque."""
|
|
67
|
+
daemon = _make_daemon(server.config.port)
|
|
68
|
+
await daemon.start()
|
|
69
|
+
|
|
70
|
+
assert len(daemon._mention_targets) == 0
|
|
71
|
+
await daemon._on_turn_error() # should not raise
|
|
72
|
+
assert len(daemon._mention_targets) == 0
|
|
73
|
+
|
|
74
|
+
await daemon.stop()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_relay_routes_correctly_after_error_cleanup(server, make_client):
|
|
79
|
+
"""After a failed prompt is cleaned up, the next response should route correctly."""
|
|
80
|
+
daemon = _make_daemon(server.config.port)
|
|
81
|
+
await daemon.start()
|
|
82
|
+
_inject_fake_runner(daemon)
|
|
83
|
+
await asyncio.sleep(0.5)
|
|
84
|
+
|
|
85
|
+
# Simulate: system prompt enqueued None, then timed out and was cleaned up
|
|
86
|
+
daemon._mention_targets.append(None)
|
|
87
|
+
await daemon._on_turn_error() # cleans up None
|
|
88
|
+
|
|
89
|
+
# Now simulate a real mention that succeeds
|
|
90
|
+
daemon._mention_targets.append("#general")
|
|
91
|
+
|
|
92
|
+
# Simulate agent response
|
|
93
|
+
sent_messages = []
|
|
94
|
+
original_send = daemon._transport.send_privmsg
|
|
95
|
+
|
|
96
|
+
async def capture_send(target, text):
|
|
97
|
+
sent_messages.append((target, text))
|
|
98
|
+
await original_send(target, text)
|
|
99
|
+
|
|
100
|
+
daemon._transport.send_privmsg = capture_send
|
|
101
|
+
|
|
102
|
+
msg = {
|
|
103
|
+
"type": "assistant",
|
|
104
|
+
"content": [{"type": "text", "text": "Hello from bot!"}],
|
|
105
|
+
}
|
|
106
|
+
await daemon._relay_response_to_irc(msg)
|
|
107
|
+
|
|
108
|
+
assert len(sent_messages) == 1
|
|
109
|
+
assert sent_messages[0][0] == "#general"
|
|
110
|
+
assert "Hello from bot!" in sent_messages[0][1]
|
|
111
|
+
assert len(daemon._mention_targets) == 0
|
|
112
|
+
|
|
113
|
+
await daemon.stop()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.asyncio
|
|
117
|
+
async def test_multiple_errors_drain_deque_correctly(server):
|
|
118
|
+
"""Multiple consecutive errors should each pop one entry."""
|
|
119
|
+
daemon = _make_daemon(server.config.port)
|
|
120
|
+
await daemon.start()
|
|
121
|
+
|
|
122
|
+
# Simulate 3 failed prompts
|
|
123
|
+
daemon._mention_targets.append(None) # system prompt
|
|
124
|
+
daemon._mention_targets.append("#general") # poll 1
|
|
125
|
+
daemon._mention_targets.append("#general") # poll 2
|
|
126
|
+
|
|
127
|
+
await daemon._on_turn_error() # pops None
|
|
128
|
+
await daemon._on_turn_error() # pops #general
|
|
129
|
+
assert len(daemon._mention_targets) == 1
|
|
130
|
+
assert daemon._mention_targets[0] == "#general"
|
|
131
|
+
|
|
132
|
+
await daemon._on_turn_error() # pops last #general
|
|
133
|
+
assert len(daemon._mention_targets) == 0
|
|
134
|
+
|
|
135
|
+
await daemon.stop()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|