agentirc-cli 1.0.4__tar.gz → 1.0.6__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-1.0.6/.claude/skills/run-tests/SKILL.md +48 -0
- agentirc_cli-1.0.6/.claude/skills/run-tests/scripts/test.sh +47 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/CHANGELOG.md +23 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/PKG-INFO +1 -1
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/cli.py +84 -73
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/agent_runner.py +39 -41
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/irc_transport.py +54 -34
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/socket_server.py +33 -15
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/irc_transport.py +54 -34
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/socket_server.py +33 -15
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/agent_runner.py +32 -39
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/irc_transport.py +54 -34
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/socket_server.py +33 -15
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/agent_runner.py +12 -8
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/irc_transport.py +54 -34
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/socket_server.py +33 -15
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/ircd.py +33 -32
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/server_link.py +5 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/skills/rooms.py +97 -84
- agentirc_cli-1.0.6/packages/agent-harness/irc_transport.py +209 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/socket_server.py +33 -15
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/pyproject.toml +1 -1
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/sonar-project.properties +1 -1
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/uv.lock +1 -1
- agentirc_cli-1.0.4/packages/agent-harness/irc_transport.py +0 -176
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.flake8 +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.gitignore +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.pr_agent.toml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/.pylintrc +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/CLAUDE.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/CNAME +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/Gemfile +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/Gemfile.lock +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/LICENSE +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/README.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/SECURITY.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/_config.yml +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/__main__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/bot.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/credentials.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/learn_prompt.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/mesh_config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/observer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/collector.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/model.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/overview/web/style.css +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/persistence.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/pidfile.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/commands.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/message.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/protocol/replies.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/__main__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/channel.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/remote_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/room_store.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/skill.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/skills/history.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/server/thread_store.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/SECURITY.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/agent-client.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/agent-harness-spec.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/bots.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/ci.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/cli.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/codex-backend.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/copilot-backend.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/docs-site.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/getting-started.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/grow-your-agent.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/harness-conformance.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/layer1-core-irc.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/layer2-attention.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/layer3-skills.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/layer4-federation.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/layer5-agent-harness.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/ops-tooling.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/publishing.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/rooms.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/server-architecture.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/threads.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases/10-grow-your-agent.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/docs/use-cases-index.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/index.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/__init__.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/conftest.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_bot.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_bot_config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_channel.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_connection.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_daemon.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_discovery.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_federation.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_history.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_http_listener.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_ipc.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_mentions.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_message.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_messaging.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_modes.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_overview_model.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_overview_web.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_persistence.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_rooms.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_skill_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_skills.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_socket_server.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_supervisor.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_template_engine.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_threads.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-1.0.4 → agentirc_cli-1.0.6}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: run-tests
|
|
3
|
+
description: Run pytest with parallel execution and coverage. Use when running tests, verifying changes, or the user says "run tests", "test", or "pytest".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Run Tests
|
|
7
|
+
|
|
8
|
+
Run the project's pytest suite with optional parallelism (pytest-xdist) and coverage.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Default: parallel + verbose (recommended)
|
|
14
|
+
bash .claude/skills/run-tests/scripts/test.sh -p
|
|
15
|
+
|
|
16
|
+
# Quick check: parallel + quiet
|
|
17
|
+
bash .claude/skills/run-tests/scripts/test.sh -p -q
|
|
18
|
+
|
|
19
|
+
# Full CI mode: parallel + coverage + xml report
|
|
20
|
+
bash .claude/skills/run-tests/scripts/test.sh --ci
|
|
21
|
+
|
|
22
|
+
# Specific test file
|
|
23
|
+
bash .claude/skills/run-tests/scripts/test.sh -p tests/test_socket_server.py
|
|
24
|
+
|
|
25
|
+
# Without parallelism (for debugging test ordering issues)
|
|
26
|
+
bash .claude/skills/run-tests/scripts/test.sh tests/test_rooms.py
|
|
27
|
+
|
|
28
|
+
# With coverage
|
|
29
|
+
bash .claude/skills/run-tests/scripts/test.sh -p -c
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
| Flag | Short | Description |
|
|
35
|
+
|------|-------|-------------|
|
|
36
|
+
| `--parallel` | `-p` | Run with `-n auto` (pytest-xdist, uses all CPU cores) |
|
|
37
|
+
| `--coverage` | `-c` | Enable coverage reporting to terminal |
|
|
38
|
+
| `--ci` | | Full CI mode: parallel + coverage + XML report + verbose |
|
|
39
|
+
| `--quick` | `-q` | Quiet output (no verbose, no coverage) |
|
|
40
|
+
|
|
41
|
+
Extra arguments are passed through to pytest (e.g., `-x` for stop-on-first-failure, `-k "pattern"` for filtering).
|
|
42
|
+
|
|
43
|
+
## When to Use Which Mode
|
|
44
|
+
|
|
45
|
+
- **After code changes:** `bash .claude/skills/run-tests/scripts/test.sh -p` — fast parallel run, verbose output
|
|
46
|
+
- **Quick sanity check:** `bash .claude/skills/run-tests/scripts/test.sh -p -q` — minimal output
|
|
47
|
+
- **Before PR / release:** `bash .claude/skills/run-tests/scripts/test.sh --ci` — matches CI exactly
|
|
48
|
+
- **Debugging flaky test:** `bash .claude/skills/run-tests/scripts/test.sh tests/test_flaky.py` — sequential, single file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run pytest with optional parallelism and coverage.
|
|
3
|
+
# Usage: bash test.sh [OPTIONS] [PYTEST_ARGS...]
|
|
4
|
+
#
|
|
5
|
+
# Options:
|
|
6
|
+
# --parallel, -p Run with -n auto (pytest-xdist)
|
|
7
|
+
# --coverage, -c Enable coverage reporting
|
|
8
|
+
# --ci Mimic full CI invocation (-n auto + coverage + xml)
|
|
9
|
+
# --quick, -q Quick mode: no coverage, quiet output
|
|
10
|
+
#
|
|
11
|
+
# Extra args are passed through to pytest.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
PARALLEL=""
|
|
16
|
+
COVERAGE=""
|
|
17
|
+
CI_MODE=""
|
|
18
|
+
QUIET=""
|
|
19
|
+
EXTRA_ARGS=()
|
|
20
|
+
|
|
21
|
+
while [[ $# -gt 0 ]]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
--parallel|-p) PARALLEL=1; shift ;;
|
|
24
|
+
--coverage|-c) COVERAGE=1; shift ;;
|
|
25
|
+
--ci) CI_MODE=1; shift ;;
|
|
26
|
+
--quick|-q) QUIET=1; shift ;;
|
|
27
|
+
*) EXTRA_ARGS+=("$1"); shift ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
CMD=(uv run pytest)
|
|
32
|
+
|
|
33
|
+
if [[ -n "$CI_MODE" ]]; then
|
|
34
|
+
CMD+=(-n auto --cov=culture --cov-report=xml:coverage.xml --cov-report=term -v)
|
|
35
|
+
elif [[ -n "$QUIET" ]]; then
|
|
36
|
+
CMD+=(-q)
|
|
37
|
+
[[ -n "$PARALLEL" ]] && CMD+=(-n auto)
|
|
38
|
+
else
|
|
39
|
+
[[ -n "$PARALLEL" ]] && CMD+=(-n auto)
|
|
40
|
+
[[ -n "$COVERAGE" ]] && CMD+=(--cov=culture --cov-report=term)
|
|
41
|
+
CMD+=(-v)
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
CMD+=("${EXTRA_ARGS[@]}")
|
|
45
|
+
|
|
46
|
+
echo "Running: ${CMD[*]}"
|
|
47
|
+
exec "${CMD[@]}"
|
|
@@ -4,6 +4,29 @@ 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
|
+
## [1.0.6] - 2026-04-05
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Project-local run-tests skill for portable pytest execution
|
|
13
|
+
|
|
14
|
+
## [1.0.5] - 2026-04-05
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Extract helper methods from socket_server _handle_client (all backends)
|
|
20
|
+
- Convert irc_transport _handle to dispatch table (all backends)
|
|
21
|
+
- Extract _auto_approve and _flush_accumulated_text in codex/acp agent_runner
|
|
22
|
+
- Extract _handle_session_update and _extract_response_text in acp/copilot agent_runner
|
|
23
|
+
- Decompose _handle_roommeta into query/update methods in rooms.py
|
|
24
|
+
- Extract _merge_room_metadata in server_link.py
|
|
25
|
+
- Extract _attempt_single_reconnect in ircd.py
|
|
26
|
+
- Extract _create_agent_config and _try_ipc_shutdown/_try_pid_shutdown in cli.py
|
|
27
|
+
- Update packages/agent-harness templates to match backend features
|
|
28
|
+
- Add socket_server and irc_transport to sonar CPD exclusions
|
|
29
|
+
|
|
7
30
|
## [1.0.4] - 2026-04-05
|
|
8
31
|
|
|
9
32
|
|
|
@@ -525,55 +525,27 @@ def _server_status(args: argparse.Namespace) -> None:
|
|
|
525
525
|
# -----------------------------------------------------------------------
|
|
526
526
|
|
|
527
527
|
|
|
528
|
-
def
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
# Determine server name
|
|
532
|
-
server_name = args.server or config.server.name or "culture"
|
|
533
|
-
|
|
534
|
-
# Determine agent suffix
|
|
535
|
-
if args.nick:
|
|
536
|
-
suffix = args.nick
|
|
537
|
-
else:
|
|
538
|
-
dirname = os.path.basename(os.getcwd())
|
|
539
|
-
suffix = sanitize_agent_name(dirname)
|
|
540
|
-
|
|
541
|
-
full_nick = f"{server_name}-{suffix}"
|
|
542
|
-
|
|
543
|
-
# Check for collision
|
|
544
|
-
for existing in config.agents:
|
|
545
|
-
if existing.nick == full_nick:
|
|
546
|
-
channels = existing.channels if isinstance(existing.channels, list) else []
|
|
547
|
-
print(f"Agent '{full_nick}' already exists in config", file=sys.stderr)
|
|
548
|
-
print(f" Directory: {existing.directory}", file=sys.stderr)
|
|
549
|
-
print(f" Backend: {existing.agent}", file=sys.stderr)
|
|
550
|
-
print(f" Channels: {', '.join(channels)}", file=sys.stderr)
|
|
551
|
-
print(f" Model: {existing.model}", file=sys.stderr)
|
|
552
|
-
print(f" Config: {args.config}", file=sys.stderr)
|
|
553
|
-
print(file=sys.stderr)
|
|
554
|
-
print(f"Start with: culture start {full_nick}", file=sys.stderr)
|
|
555
|
-
sys.exit(1)
|
|
556
|
-
|
|
557
|
-
# Use backend-specific config for correct defaults
|
|
528
|
+
def _create_agent_config(args: argparse.Namespace, full_nick: str) -> "AgentConfig":
|
|
529
|
+
"""Build a backend-specific AgentConfig from CLI args."""
|
|
558
530
|
if args.agent == "codex":
|
|
559
531
|
from culture.clients.codex.config import AgentConfig as CodexAgentConfig
|
|
560
532
|
|
|
561
|
-
|
|
533
|
+
return CodexAgentConfig(
|
|
562
534
|
nick=full_nick,
|
|
563
535
|
agent="codex",
|
|
564
536
|
directory=os.getcwd(),
|
|
565
537
|
channels=["#general"],
|
|
566
538
|
)
|
|
567
|
-
|
|
539
|
+
if args.agent == "copilot":
|
|
568
540
|
from culture.clients.copilot.config import AgentConfig as CopilotAgentConfig
|
|
569
541
|
|
|
570
|
-
|
|
542
|
+
return CopilotAgentConfig(
|
|
571
543
|
nick=full_nick,
|
|
572
544
|
agent="copilot",
|
|
573
545
|
directory=os.getcwd(),
|
|
574
546
|
channels=["#general"],
|
|
575
547
|
)
|
|
576
|
-
|
|
548
|
+
if args.agent == "acp":
|
|
577
549
|
import json as _json
|
|
578
550
|
|
|
579
551
|
from culture.clients.acp.config import AgentConfig as ACPAgentConfig
|
|
@@ -583,7 +555,6 @@ def _cmd_init(args: argparse.Namespace) -> None:
|
|
|
583
555
|
try:
|
|
584
556
|
acp_cmd = _json.loads(args.acp_command)
|
|
585
557
|
except _json.JSONDecodeError:
|
|
586
|
-
# Treat as a simple command name (e.g. "cline --acp")
|
|
587
558
|
acp_cmd = args.acp_command.split()
|
|
588
559
|
if (
|
|
589
560
|
not isinstance(acp_cmd, list)
|
|
@@ -592,20 +563,51 @@ def _cmd_init(args: argparse.Namespace) -> None:
|
|
|
592
563
|
):
|
|
593
564
|
print("Error: --acp-command must be a non-empty list of strings", file=sys.stderr)
|
|
594
565
|
sys.exit(1)
|
|
595
|
-
|
|
566
|
+
return ACPAgentConfig(
|
|
596
567
|
nick=full_nick,
|
|
597
568
|
agent="acp",
|
|
598
569
|
acp_command=acp_cmd,
|
|
599
570
|
directory=os.getcwd(),
|
|
600
571
|
channels=["#general"],
|
|
601
572
|
)
|
|
573
|
+
return AgentConfig(
|
|
574
|
+
nick=full_nick,
|
|
575
|
+
agent=args.agent,
|
|
576
|
+
directory=os.getcwd(),
|
|
577
|
+
channels=["#general"],
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _cmd_init(args: argparse.Namespace) -> None:
|
|
582
|
+
config = load_config_or_default(args.config)
|
|
583
|
+
|
|
584
|
+
# Determine server name
|
|
585
|
+
server_name = args.server or config.server.name or "culture"
|
|
586
|
+
|
|
587
|
+
# Determine agent suffix
|
|
588
|
+
if args.nick:
|
|
589
|
+
suffix = args.nick
|
|
602
590
|
else:
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
591
|
+
dirname = os.path.basename(os.getcwd())
|
|
592
|
+
suffix = sanitize_agent_name(dirname)
|
|
593
|
+
|
|
594
|
+
full_nick = f"{server_name}-{suffix}"
|
|
595
|
+
|
|
596
|
+
# Check for collision
|
|
597
|
+
for existing in config.agents:
|
|
598
|
+
if existing.nick == full_nick:
|
|
599
|
+
channels = existing.channels if isinstance(existing.channels, list) else []
|
|
600
|
+
print(f"Agent '{full_nick}' already exists in config", file=sys.stderr)
|
|
601
|
+
print(f" Directory: {existing.directory}", file=sys.stderr)
|
|
602
|
+
print(f" Backend: {existing.agent}", file=sys.stderr)
|
|
603
|
+
print(f" Channels: {', '.join(channels)}", file=sys.stderr)
|
|
604
|
+
print(f" Model: {existing.model}", file=sys.stderr)
|
|
605
|
+
print(f" Config: {args.config}", file=sys.stderr)
|
|
606
|
+
print(file=sys.stderr)
|
|
607
|
+
print(f"Start with: culture start {full_nick}", file=sys.stderr)
|
|
608
|
+
sys.exit(1)
|
|
609
|
+
|
|
610
|
+
agent = _create_agent_config(args, full_nick)
|
|
609
611
|
|
|
610
612
|
add_agent_to_config(args.config, agent, server_name=server_name)
|
|
611
613
|
|
|
@@ -853,35 +855,46 @@ def _cmd_stop(args: argparse.Namespace) -> None:
|
|
|
853
855
|
|
|
854
856
|
def _stop_agent(nick: str) -> None:
|
|
855
857
|
"""Stop a single agent by trying IPC shutdown first, then PID file."""
|
|
856
|
-
# Try Unix socket IPC shutdown
|
|
857
858
|
socket_path = os.path.join(
|
|
858
859
|
os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
|
|
859
860
|
f"culture-{nick}.sock",
|
|
860
861
|
)
|
|
861
862
|
|
|
862
|
-
if
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
else:
|
|
879
|
-
print(f"Agent '{nick}' stopped")
|
|
880
|
-
return
|
|
881
|
-
except Exception:
|
|
882
|
-
pass # Fall through to PID-based stop
|
|
863
|
+
if _try_ipc_shutdown(nick, socket_path):
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
_try_pid_shutdown(nick)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _try_ipc_shutdown(nick: str, socket_path: str) -> bool:
|
|
870
|
+
"""Attempt graceful IPC shutdown. Return True if the agent stopped."""
|
|
871
|
+
if not os.path.exists(socket_path):
|
|
872
|
+
return False
|
|
873
|
+
try:
|
|
874
|
+
success = asyncio.run(_ipc_shutdown(socket_path))
|
|
875
|
+
if not success:
|
|
876
|
+
return False
|
|
877
|
+
except Exception:
|
|
878
|
+
return False
|
|
883
879
|
|
|
884
|
-
|
|
880
|
+
print(f"Agent '{nick}' shutdown requested via IPC")
|
|
881
|
+
pid_name = f"agent-{nick}"
|
|
882
|
+
pid = read_pid(pid_name)
|
|
883
|
+
if not pid:
|
|
884
|
+
print(f"Agent '{nick}' stopped")
|
|
885
|
+
return True
|
|
886
|
+
for _ in range(50):
|
|
887
|
+
if not is_process_alive(pid):
|
|
888
|
+
remove_pid(pid_name)
|
|
889
|
+
print(f"Agent '{nick}' stopped")
|
|
890
|
+
return True
|
|
891
|
+
time.sleep(0.1)
|
|
892
|
+
# Still alive after 5s — fall through to PID-based stop
|
|
893
|
+
return False
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def _try_pid_shutdown(nick: str) -> None:
|
|
897
|
+
"""Stop an agent via PID file with SIGTERM/SIGKILL fallback."""
|
|
885
898
|
pid_name = f"agent-{nick}"
|
|
886
899
|
pid = read_pid(pid_name)
|
|
887
900
|
|
|
@@ -907,16 +920,14 @@ def _stop_agent(nick: str) -> None:
|
|
|
907
920
|
# Force kill
|
|
908
921
|
if sys.platform == "win32":
|
|
909
922
|
print(f"Agent '{nick}' did not stop gracefully, terminating")
|
|
910
|
-
|
|
911
|
-
os.kill(pid, signal.SIGTERM)
|
|
912
|
-
except ProcessLookupError:
|
|
913
|
-
pass
|
|
923
|
+
sig = signal.SIGTERM
|
|
914
924
|
else:
|
|
915
925
|
print(f"Agent '{nick}' did not stop gracefully, sending SIGKILL")
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
926
|
+
sig = signal.SIGKILL
|
|
927
|
+
try:
|
|
928
|
+
os.kill(pid, sig)
|
|
929
|
+
except ProcessLookupError:
|
|
930
|
+
pass
|
|
920
931
|
remove_pid(pid_name)
|
|
921
932
|
print(f"Agent '{nick}' killed")
|
|
922
933
|
|
|
@@ -299,42 +299,50 @@ class ACPAgentRunner:
|
|
|
299
299
|
params = msg.get("params", {})
|
|
300
300
|
|
|
301
301
|
if method == "session/update":
|
|
302
|
-
|
|
303
|
-
update = params.get("update", params)
|
|
304
|
-
update_type = update.get("sessionUpdate", "")
|
|
305
|
-
|
|
306
|
-
if update_type in ("agent_message_chunk", "agent_thought_chunk"):
|
|
307
|
-
# Accumulate streaming text (both message and thought chunks)
|
|
308
|
-
self._busy = True
|
|
309
|
-
content = update.get("content", {})
|
|
310
|
-
if update_type == "agent_message_chunk" and content.get("type") == "text":
|
|
311
|
-
self._accumulated_text += content.get("text", "")
|
|
312
|
-
|
|
313
|
-
# Check for stopReason — indicates turn is complete
|
|
314
|
-
if "stopReason" in update:
|
|
315
|
-
self._busy = False
|
|
316
|
-
if self.on_message and self._accumulated_text:
|
|
317
|
-
msg_dict = {
|
|
318
|
-
"type": "assistant",
|
|
319
|
-
"model": self.model,
|
|
320
|
-
"content": [{"type": "text", "text": self._accumulated_text}],
|
|
321
|
-
}
|
|
322
|
-
await self.on_message(msg_dict)
|
|
323
|
-
self._accumulated_text = ""
|
|
302
|
+
await self._handle_session_update(params)
|
|
324
303
|
|
|
325
304
|
elif method == "session/request_permission":
|
|
326
|
-
|
|
327
|
-
req_id = msg.get("id")
|
|
328
|
-
if req_id is not None:
|
|
329
|
-
resp = {"jsonrpc": "2.0", "id": req_id, "result": {"approved": True}}
|
|
330
|
-
if self._process and self._process.stdin:
|
|
331
|
-
line = json.dumps(resp) + "\n"
|
|
332
|
-
self._process.stdin.write(line.encode())
|
|
333
|
-
await self._process.stdin.drain()
|
|
305
|
+
await self._auto_approve(msg)
|
|
334
306
|
|
|
335
307
|
elif method == "error":
|
|
336
308
|
logger.error("ACP error (%s): %s", self.acp_command[0], params)
|
|
337
309
|
|
|
310
|
+
async def _handle_session_update(self, params: dict) -> None:
|
|
311
|
+
"""Process a session/update notification."""
|
|
312
|
+
update = params.get("update", params)
|
|
313
|
+
update_type = update.get("sessionUpdate", "")
|
|
314
|
+
|
|
315
|
+
if update_type in ("agent_message_chunk", "agent_thought_chunk"):
|
|
316
|
+
self._busy = True
|
|
317
|
+
content = update.get("content", {})
|
|
318
|
+
if update_type == "agent_message_chunk" and content.get("type") == "text":
|
|
319
|
+
self._accumulated_text += content.get("text", "")
|
|
320
|
+
|
|
321
|
+
if "stopReason" in update:
|
|
322
|
+
self._busy = False
|
|
323
|
+
await self._flush_accumulated_text()
|
|
324
|
+
|
|
325
|
+
async def _flush_accumulated_text(self) -> None:
|
|
326
|
+
"""Fire on_message with any accumulated text and reset the buffer."""
|
|
327
|
+
if self.on_message and self._accumulated_text:
|
|
328
|
+
msg_dict = {
|
|
329
|
+
"type": "assistant",
|
|
330
|
+
"model": self.model,
|
|
331
|
+
"content": [{"type": "text", "text": self._accumulated_text}],
|
|
332
|
+
}
|
|
333
|
+
await self.on_message(msg_dict)
|
|
334
|
+
self._accumulated_text = ""
|
|
335
|
+
|
|
336
|
+
async def _auto_approve(self, msg: dict) -> None:
|
|
337
|
+
"""Auto-approve a permission request from the ACP process."""
|
|
338
|
+
req_id = msg.get("id")
|
|
339
|
+
if req_id is not None:
|
|
340
|
+
resp = {"jsonrpc": "2.0", "id": req_id, "result": {"approved": True}}
|
|
341
|
+
if self._process and self._process.stdin:
|
|
342
|
+
line = json.dumps(resp) + "\n"
|
|
343
|
+
self._process.stdin.write(line.encode())
|
|
344
|
+
await self._process.stdin.drain()
|
|
345
|
+
|
|
338
346
|
async def _prompt_loop(self) -> None:
|
|
339
347
|
"""Process queued prompts one at a time."""
|
|
340
348
|
try:
|
|
@@ -354,21 +362,11 @@ class ACPAgentRunner:
|
|
|
354
362
|
timeout=120,
|
|
355
363
|
)
|
|
356
364
|
|
|
357
|
-
# Check if response itself signals turn completion
|
|
358
365
|
result = resp.get("result", {})
|
|
359
366
|
if "stopReason" in result:
|
|
360
|
-
|
|
361
|
-
if self.on_message and self._accumulated_text:
|
|
362
|
-
msg_dict = {
|
|
363
|
-
"type": "assistant",
|
|
364
|
-
"model": self.model,
|
|
365
|
-
"content": [{"type": "text", "text": self._accumulated_text}],
|
|
366
|
-
}
|
|
367
|
-
await self.on_message(msg_dict)
|
|
368
|
-
self._accumulated_text = ""
|
|
367
|
+
await self._flush_accumulated_text()
|
|
369
368
|
self._busy = False
|
|
370
369
|
|
|
371
|
-
# Wait for turn to complete (via notifications)
|
|
372
370
|
while self._busy and self._running:
|
|
373
371
|
await asyncio.sleep(0.1)
|
|
374
372
|
|
|
@@ -41,6 +41,13 @@ class IRCTransport:
|
|
|
41
41
|
self._reconnecting = False
|
|
42
42
|
self._should_run = False
|
|
43
43
|
self._background_tasks: set[asyncio.Task] = set()
|
|
44
|
+
self._cmd_handlers: dict[str, Callable] = {
|
|
45
|
+
"PING": self._on_ping,
|
|
46
|
+
"001": self._on_welcome,
|
|
47
|
+
"PRIVMSG": self._on_privmsg,
|
|
48
|
+
"NOTICE": self._on_notice,
|
|
49
|
+
"ROOMINVITE": self._on_roominvite,
|
|
50
|
+
}
|
|
44
51
|
|
|
45
52
|
async def connect(self) -> None:
|
|
46
53
|
self._should_run = True
|
|
@@ -152,37 +159,50 @@ class IRCTransport:
|
|
|
152
159
|
delay = min(delay * 2, 60)
|
|
153
160
|
|
|
154
161
|
async def _handle(self, msg: Message) -> None:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
162
|
+
handler = self._cmd_handlers.get(msg.command)
|
|
163
|
+
if handler:
|
|
164
|
+
await handler(msg)
|
|
165
|
+
|
|
166
|
+
async def _on_ping(self, msg: Message) -> None:
|
|
167
|
+
token = msg.params[0] if msg.params else ""
|
|
168
|
+
await self._send_raw(f"PONG :{token}")
|
|
169
|
+
|
|
170
|
+
async def _on_welcome(self, msg: Message) -> None:
|
|
171
|
+
self.connected = True
|
|
172
|
+
for channel in self.channels:
|
|
173
|
+
await self._send_raw(f"JOIN {channel}")
|
|
174
|
+
if self.tags:
|
|
175
|
+
tags_str = ",".join(self.tags)
|
|
176
|
+
await self._send_raw(f"TAGS {self.nick} {tags_str}")
|
|
177
|
+
|
|
178
|
+
async def _on_privmsg(self, msg: Message) -> None:
|
|
179
|
+
if len(msg.params) < 2:
|
|
180
|
+
return
|
|
181
|
+
target = msg.params[0]
|
|
182
|
+
text = msg.params[1]
|
|
183
|
+
sender = msg.prefix.split("!")[0] if msg.prefix else "unknown"
|
|
184
|
+
if sender == self.nick:
|
|
185
|
+
return
|
|
186
|
+
if target.startswith("#"):
|
|
187
|
+
self.buffer.add(target, sender, text)
|
|
188
|
+
else:
|
|
189
|
+
self.buffer.add(f"DM:{sender}", sender, text)
|
|
190
|
+
if self.on_mention and f"@{self.nick}" in text:
|
|
191
|
+
self.on_mention(target, sender, text)
|
|
192
|
+
|
|
193
|
+
async def _on_notice(self, msg: Message) -> None:
|
|
194
|
+
if len(msg.params) < 2:
|
|
195
|
+
return
|
|
196
|
+
target = msg.params[0]
|
|
197
|
+
text = msg.params[1]
|
|
198
|
+
sender = msg.prefix.split("!")[0] if msg.prefix else "server"
|
|
199
|
+
if target.startswith("#"):
|
|
200
|
+
self.buffer.add(target, sender, text)
|
|
201
|
+
|
|
202
|
+
async def _on_roominvite(self, msg: Message) -> None:
|
|
203
|
+
if len(msg.params) < 3:
|
|
204
|
+
return
|
|
205
|
+
channel = msg.params[0]
|
|
206
|
+
meta_text = msg.params[2]
|
|
207
|
+
if self.on_roominvite:
|
|
208
|
+
self.on_roominvite(channel, meta_text)
|
|
@@ -61,7 +61,14 @@ class SocketServer:
|
|
|
61
61
|
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
62
62
|
) -> None:
|
|
63
63
|
self._clients.append(writer)
|
|
64
|
-
|
|
64
|
+
try:
|
|
65
|
+
await self._drain_queued_whispers(writer)
|
|
66
|
+
await self._process_client_messages(reader, writer)
|
|
67
|
+
finally:
|
|
68
|
+
self._cleanup_client(writer)
|
|
69
|
+
|
|
70
|
+
async def _drain_queued_whispers(self, writer: asyncio.StreamWriter) -> None:
|
|
71
|
+
"""Deliver any whispers queued before this client connected."""
|
|
65
72
|
while not self._whisper_queue.empty():
|
|
66
73
|
try:
|
|
67
74
|
data = self._whisper_queue.get_nowait()
|
|
@@ -71,6 +78,11 @@ class SocketServer:
|
|
|
71
78
|
break
|
|
72
79
|
except (ConnectionError, BrokenPipeError, OSError):
|
|
73
80
|
break
|
|
81
|
+
|
|
82
|
+
async def _process_client_messages(
|
|
83
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Read and dispatch IPC messages until the client disconnects."""
|
|
74
86
|
try:
|
|
75
87
|
while True:
|
|
76
88
|
line = await reader.readline()
|
|
@@ -85,20 +97,26 @@ class SocketServer:
|
|
|
85
97
|
await writer.drain()
|
|
86
98
|
except Exception as exc:
|
|
87
99
|
logger.exception("Handler error for message: %s", msg)
|
|
88
|
-
|
|
89
|
-
request_id = msg.get("id") if isinstance(msg, dict) else None
|
|
90
|
-
err_resp = make_response(request_id or "", ok=False, error=str(exc))
|
|
91
|
-
writer.write(encode_message(err_resp))
|
|
92
|
-
await writer.drain()
|
|
93
|
-
except (ConnectionError, BrokenPipeError, OSError):
|
|
100
|
+
if not await self._send_error_response(msg, exc, writer):
|
|
94
101
|
break
|
|
95
102
|
except (ConnectionError, asyncio.IncompleteReadError):
|
|
96
103
|
pass
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
|
|
105
|
+
async def _send_error_response(
|
|
106
|
+
self, msg: dict, exc: Exception, writer: asyncio.StreamWriter
|
|
107
|
+
) -> bool:
|
|
108
|
+
"""Send an error response to the client. Return False if the connection broke."""
|
|
109
|
+
try:
|
|
110
|
+
request_id = msg.get("id") if isinstance(msg, dict) else None
|
|
111
|
+
err_resp = make_response(request_id or "", ok=False, error=str(exc))
|
|
112
|
+
writer.write(encode_message(err_resp))
|
|
113
|
+
await writer.drain()
|
|
114
|
+
return True
|
|
115
|
+
except (ConnectionError, BrokenPipeError, OSError):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def _cleanup_client(self, writer: asyncio.StreamWriter) -> None:
|
|
119
|
+
"""Remove client from the active list and close the connection."""
|
|
120
|
+
if writer in self._clients:
|
|
121
|
+
self._clients.remove(writer)
|
|
122
|
+
writer.close()
|