agentirc-cli 4.1.1__tar.gz → 4.1.3__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.1 → agentirc_cli-4.1.3}/CHANGELOG.md +14 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/PKG-INFO +1 -1
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/mesh.py +64 -11
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/agent_runner.py +4 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/daemon.py +6 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/agent_runner.py +4 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/daemon.py +6 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/agent_runner.py +4 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/daemon.py +6 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/mesh_config.py +9 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/cli.md +5 -2
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/daemon.py +9 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/pyproject.toml +1 -1
- agentirc_cli-4.1.3/tests/test_mention_target_cleanup.py +135 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mesh_config.py +37 -0
- agentirc_cli-4.1.3/tests/test_setup_update_cli.py +185 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/uv.lock +1 -1
- agentirc_cli-4.1.1/tests/test_setup_update_cli.py +0 -45
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.flake8 +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.gitignore +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pr_agent.toml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/.pylintrc +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/CLAUDE.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/CNAME +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/Gemfile +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/Gemfile.lock +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/LICENSE +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/README.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/SECURITY.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_config.yml +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/__main__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/bot.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/_helpers.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/agent.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/bot.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/channel.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/cli/skills.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/app.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/commands.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/credentials.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/learn_prompt.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/observer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/collector.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/model.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/overview/web/style.css +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/persistence.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/pidfile.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/commands.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/message.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/protocol/replies.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/__main__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/channel.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/ircd.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/remote_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/room_store.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/server_link.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skill.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/history.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/icon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/rooms.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/server/thread_store.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/agent-lifecycle.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/agent-client.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/harness-conformance.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/index.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer1-core-irc.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer2-attention.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer3-skills.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer4-federation.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/layer5-agent-harness.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/server-architecture.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/architecture/threads.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/channel-polling.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/acp/overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/culture-cli.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/getting-started.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/index.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/SECURITY.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/bots.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/ci.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/docs-site.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/index.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/ops-tooling.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/operations/publishing.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/rooms.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/server-rename.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/use-cases-index.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/docs/what-is-culture.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/sonar-project.properties +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/__init__.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/conftest.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot_config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_channel.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_connection.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_commands.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_connection.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_icons.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_console_integration.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_discovery.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_federation.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_history.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_http_listener.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_ipc.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mention_alias.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_mentions.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_message.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_messaging.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_modes.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_model.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_overview_web.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_persistence.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_pidfile.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_skill_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_skills.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_socket_server.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_supervisor.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_template_engine.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_threads.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-4.1.1 → agentirc_cli-4.1.3}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,20 @@ 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.3] - 2026-04-06
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- mesh update now discovers and restarts all running servers instead of only the one in mesh.yaml
|
|
13
|
+
|
|
14
|
+
## [4.1.2] - 2026-04-06
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Clean up _mention_targets deque on prompt failure to prevent misrouted responses
|
|
20
|
+
|
|
7
21
|
## [4.1.1] - 2026-04-06
|
|
8
22
|
|
|
9
23
|
|
|
@@ -549,23 +549,76 @@ def _restart_mesh_services(
|
|
|
549
549
|
check=False,
|
|
550
550
|
)
|
|
551
551
|
|
|
552
|
-
print(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
def
|
|
556
|
-
|
|
552
|
+
print()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _resolve_mesh_for_server(server_name: str, config_path: str):
|
|
556
|
+
"""Find or build a MeshConfig for *server_name*.
|
|
557
|
+
|
|
558
|
+
Resolution order:
|
|
559
|
+
1. mesh.yaml — use directly if its server.name matches.
|
|
560
|
+
2. agents.yaml — build via from_daemon_config(), preserving host, port,
|
|
561
|
+
and links from the old mesh.yaml. Saves the updated mesh.yaml so
|
|
562
|
+
future runs are consistent.
|
|
563
|
+
"""
|
|
564
|
+
from culture.mesh_config import (
|
|
565
|
+
from_daemon_config,
|
|
566
|
+
load_mesh_config,
|
|
567
|
+
merge_links,
|
|
568
|
+
save_mesh_config,
|
|
569
|
+
)
|
|
557
570
|
|
|
571
|
+
old_server = None
|
|
558
572
|
try:
|
|
559
|
-
|
|
573
|
+
old_mesh = load_mesh_config(config_path)
|
|
574
|
+
if old_mesh.server.name == server_name:
|
|
575
|
+
return old_mesh
|
|
576
|
+
old_server = old_mesh.server
|
|
560
577
|
except FileNotFoundError:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
578
|
+
pass
|
|
579
|
+
|
|
580
|
+
if os.path.isfile(DEFAULT_CONFIG):
|
|
581
|
+
daemon_config = load_config(DEFAULT_CONFIG)
|
|
582
|
+
if daemon_config.server.name == server_name:
|
|
583
|
+
mesh = from_daemon_config(daemon_config)
|
|
584
|
+
if old_server is not None:
|
|
585
|
+
mesh.server.host = old_server.host
|
|
586
|
+
mesh.server.port = old_server.port
|
|
587
|
+
merge_links(mesh, old_server.links)
|
|
588
|
+
save_mesh_config(mesh, config_path)
|
|
589
|
+
return mesh
|
|
590
|
+
|
|
591
|
+
return None
|
|
564
592
|
|
|
565
|
-
|
|
593
|
+
|
|
594
|
+
def _cmd_update(args: argparse.Namespace) -> None:
|
|
595
|
+
from culture.mesh_config import load_mesh_config
|
|
596
|
+
from culture.pidfile import list_servers
|
|
566
597
|
|
|
567
598
|
if not _upgrade_culture_package(args):
|
|
568
599
|
return
|
|
569
600
|
|
|
570
601
|
culture_bin = shutil.which("culture") or "culture"
|
|
571
|
-
|
|
602
|
+
|
|
603
|
+
running = list_servers()
|
|
604
|
+
|
|
605
|
+
if running:
|
|
606
|
+
for srv in running:
|
|
607
|
+
mesh = _resolve_mesh_for_server(srv["name"], args.config)
|
|
608
|
+
if mesh is None:
|
|
609
|
+
print(
|
|
610
|
+
f" Warning: no config found for server '{srv['name']}', skipping",
|
|
611
|
+
file=sys.stderr,
|
|
612
|
+
)
|
|
613
|
+
continue
|
|
614
|
+
_restart_mesh_services(mesh, srv["name"], culture_bin, args.config, args.dry_run)
|
|
615
|
+
else:
|
|
616
|
+
try:
|
|
617
|
+
mesh = load_mesh_config(args.config)
|
|
618
|
+
except FileNotFoundError:
|
|
619
|
+
mesh = generate_mesh_from_agents(args.config)
|
|
620
|
+
if mesh is None:
|
|
621
|
+
sys.exit(1)
|
|
622
|
+
_restart_mesh_services(mesh, mesh.server.name, culture_bin, args.config, args.dry_run)
|
|
623
|
+
|
|
624
|
+
print("Update complete. All services restarted.")
|
|
@@ -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
|
|
@@ -384,6 +386,8 @@ class ACPAgentRunner:
|
|
|
384
386
|
|
|
385
387
|
except Exception:
|
|
386
388
|
logger.exception("ACP turn error")
|
|
389
|
+
if self.on_turn_error:
|
|
390
|
+
await self.on_turn_error()
|
|
387
391
|
finally:
|
|
388
392
|
self._busy = False
|
|
389
393
|
|
|
@@ -318,6 +318,11 @@ class ACPDaemon:
|
|
|
318
318
|
# Agent runner helpers
|
|
319
319
|
# ------------------------------------------------------------------
|
|
320
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
|
+
|
|
321
326
|
async def _start_agent_runner(self) -> None:
|
|
322
327
|
self._agent_runner = ACPAgentRunner(
|
|
323
328
|
model=self.agent.model,
|
|
@@ -326,6 +331,7 @@ class ACPDaemon:
|
|
|
326
331
|
system_prompt=self._build_system_prompt(),
|
|
327
332
|
on_exit=self._on_agent_exit,
|
|
328
333
|
on_message=self._on_agent_message,
|
|
334
|
+
on_turn_error=self._on_turn_error,
|
|
329
335
|
)
|
|
330
336
|
# Absorb the system prompt response without relaying to IRC
|
|
331
337
|
self._mention_targets.append(None)
|
|
@@ -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
|
|
@@ -293,6 +293,11 @@ class CodexDaemon:
|
|
|
293
293
|
# Agent runner helpers
|
|
294
294
|
# ------------------------------------------------------------------
|
|
295
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
|
+
|
|
296
301
|
async def _start_agent_runner(self) -> None:
|
|
297
302
|
self._agent_runner = CodexAgentRunner(
|
|
298
303
|
model=self.agent.model,
|
|
@@ -300,6 +305,7 @@ class CodexDaemon:
|
|
|
300
305
|
system_prompt=self._build_system_prompt(),
|
|
301
306
|
on_exit=self._on_agent_exit,
|
|
302
307
|
on_message=self._on_agent_message,
|
|
308
|
+
on_turn_error=self._on_turn_error,
|
|
303
309
|
)
|
|
304
310
|
await self._agent_runner.start()
|
|
305
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:
|
|
@@ -293,6 +293,11 @@ class CopilotDaemon:
|
|
|
293
293
|
# Agent runner helpers
|
|
294
294
|
# ------------------------------------------------------------------
|
|
295
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
|
+
|
|
296
301
|
async def _start_agent_runner(self) -> None:
|
|
297
302
|
# Resolve installed skill path for the Copilot session
|
|
298
303
|
skill_dirs: list[str] = []
|
|
@@ -307,6 +312,7 @@ class CopilotDaemon:
|
|
|
307
312
|
skill_directories=skill_dirs,
|
|
308
313
|
on_exit=self._on_agent_exit,
|
|
309
314
|
on_message=self._on_agent_message,
|
|
315
|
+
on_turn_error=self._on_turn_error,
|
|
310
316
|
)
|
|
311
317
|
await self._agent_runner.start()
|
|
312
318
|
logger.info("CopilotAgentRunner started for %s", self.agent.nick)
|
|
@@ -108,6 +108,15 @@ def from_daemon_config(daemon_config: DaemonConfig) -> MeshConfig:
|
|
|
108
108
|
return MeshConfig(server=server, agents=agents)
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def merge_links(target: MeshConfig, source_links: list[MeshLinkConfig]) -> None:
|
|
112
|
+
"""Copy link configs from *source_links* into *target* if not already present."""
|
|
113
|
+
existing = {link.name for link in target.server.links}
|
|
114
|
+
for link in source_links:
|
|
115
|
+
if link.name not in existing:
|
|
116
|
+
target.server.links.append(link)
|
|
117
|
+
existing.add(link.name)
|
|
118
|
+
|
|
119
|
+
|
|
111
120
|
def save_mesh_config(config: MeshConfig, path: str | Path = DEFAULT_MESH_PATH) -> None:
|
|
112
121
|
"""Serialize mesh config to YAML and write atomically."""
|
|
113
122
|
path = Path(path)
|
|
@@ -288,8 +288,11 @@ walkthrough.
|
|
|
288
288
|
|
|
289
289
|
### `culture update`
|
|
290
290
|
|
|
291
|
-
Upgrade the `culture` package and restart all
|
|
292
|
-
|
|
291
|
+
Upgrade the `culture` package and restart all running servers. The command
|
|
292
|
+
discovers running servers from PID files rather than relying solely on
|
|
293
|
+
`mesh.yaml`, so it restarts every server on the machine even if `mesh.yaml`
|
|
294
|
+
is stale or names a different server. When no servers are running, it falls
|
|
295
|
+
back to `mesh.yaml`.
|
|
293
296
|
|
|
294
297
|
```bash
|
|
295
298
|
culture update # upgrade package + restart everything
|
|
@@ -258,6 +258,15 @@ class AgentDaemon:
|
|
|
258
258
|
except Exception:
|
|
259
259
|
logger.exception("Poll loop error")
|
|
260
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
|
+
|
|
261
270
|
def _on_mention(self, target: str, sender: str, text: str) -> None:
|
|
262
271
|
"""Called when the agent is @mentioned. Sends prompt to runner.
|
|
263
272
|
|
|
@@ -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()
|
|
@@ -11,6 +11,7 @@ from culture.mesh_config import (
|
|
|
11
11
|
MeshServerConfig,
|
|
12
12
|
from_daemon_config,
|
|
13
13
|
load_mesh_config,
|
|
14
|
+
merge_links,
|
|
14
15
|
save_mesh_config,
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -153,3 +154,39 @@ def test_from_daemon_config_empty_agents():
|
|
|
153
154
|
assert mesh.server.name == "test"
|
|
154
155
|
assert mesh.server.port == 7000
|
|
155
156
|
assert mesh.agents == []
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_merge_links_appends_missing():
|
|
160
|
+
"""merge_links adds links not already present."""
|
|
161
|
+
target = MeshConfig(
|
|
162
|
+
server=MeshServerConfig(
|
|
163
|
+
name="spark",
|
|
164
|
+
links=[MeshLinkConfig(name="thor", host="1.2.3.4", port=6667)],
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
source = [
|
|
168
|
+
MeshLinkConfig(name="orin", host="5.6.7.8", port=6668),
|
|
169
|
+
]
|
|
170
|
+
merge_links(target, source)
|
|
171
|
+
assert len(target.server.links) == 2
|
|
172
|
+
assert target.server.links[1].name == "orin"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_merge_links_skips_duplicates():
|
|
176
|
+
"""merge_links does not duplicate links already present."""
|
|
177
|
+
target = MeshConfig(
|
|
178
|
+
server=MeshServerConfig(
|
|
179
|
+
name="spark",
|
|
180
|
+
links=[MeshLinkConfig(name="thor", host="1.2.3.4", port=6667)],
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
source = [
|
|
184
|
+
MeshLinkConfig(name="thor", host="9.9.9.9", port=9999),
|
|
185
|
+
MeshLinkConfig(name="orin", host="5.6.7.8", port=6668),
|
|
186
|
+
]
|
|
187
|
+
merge_links(target, source)
|
|
188
|
+
assert len(target.server.links) == 2
|
|
189
|
+
names = [l.name for l in target.server.links]
|
|
190
|
+
assert names == ["thor", "orin"]
|
|
191
|
+
# Original thor link is unchanged
|
|
192
|
+
assert target.server.links[0].host == "1.2.3.4"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# tests/test_setup_update_cli.py
|
|
2
|
+
"""Lightweight parser tests for setup and update subcommands."""
|
|
3
|
+
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from culture.cli import _build_parser
|
|
9
|
+
from culture.mesh_config import MeshAgentConfig, MeshConfig, MeshLinkConfig, MeshServerConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_setup_parser():
|
|
13
|
+
"""setup subcommand parses --config and --uninstall."""
|
|
14
|
+
p = _build_parser()
|
|
15
|
+
args = p.parse_args(["mesh", "setup", "--uninstall"])
|
|
16
|
+
assert args.command == "mesh"
|
|
17
|
+
assert args.mesh_command == "setup"
|
|
18
|
+
assert args.uninstall is True
|
|
19
|
+
|
|
20
|
+
args = p.parse_args(["mesh", "setup", "--config", "/tmp/mesh.yaml"])
|
|
21
|
+
assert args.config == "/tmp/mesh.yaml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_update_parser():
|
|
25
|
+
"""update subcommand parses --dry-run, --skip-upgrade, --config."""
|
|
26
|
+
p = _build_parser()
|
|
27
|
+
args = p.parse_args(["mesh", "update", "--dry-run", "--skip-upgrade"])
|
|
28
|
+
assert args.command == "mesh"
|
|
29
|
+
assert args.mesh_command == "update"
|
|
30
|
+
assert args.dry_run is True
|
|
31
|
+
assert args.skip_upgrade is True
|
|
32
|
+
|
|
33
|
+
args = p.parse_args(["mesh", "update", "--config", "/tmp/mesh.yaml"])
|
|
34
|
+
assert args.config == "/tmp/mesh.yaml"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_setup_in_dispatch():
|
|
38
|
+
"""setup command is wired into the mesh module."""
|
|
39
|
+
from culture.cli import mesh
|
|
40
|
+
|
|
41
|
+
assert hasattr(mesh, "_cmd_setup")
|
|
42
|
+
assert callable(mesh._cmd_setup)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_update_in_dispatch():
|
|
46
|
+
"""update command is wired into the mesh module."""
|
|
47
|
+
from culture.cli import mesh
|
|
48
|
+
|
|
49
|
+
assert hasattr(mesh, "_cmd_update")
|
|
50
|
+
assert callable(mesh._cmd_update)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---- _cmd_update behaviour tests ----
|
|
54
|
+
|
|
55
|
+
_MESH_MOD = "culture.cli.mesh"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def update_args(tmp_path):
|
|
60
|
+
"""Minimal argparse namespace for _cmd_update."""
|
|
61
|
+
p = _build_parser()
|
|
62
|
+
config = str(tmp_path / "mesh.yaml")
|
|
63
|
+
return p.parse_args(["mesh", "update", "--skip-upgrade", "--dry-run", "--config", config])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@patch("culture.pidfile.list_servers")
|
|
67
|
+
@patch(f"{_MESH_MOD}._resolve_mesh_for_server")
|
|
68
|
+
@patch(f"{_MESH_MOD}._restart_mesh_services")
|
|
69
|
+
@patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
|
|
70
|
+
def test_update_discovers_running_servers(
|
|
71
|
+
_mock_upgrade, mock_restart, mock_resolve, mock_list, update_args
|
|
72
|
+
):
|
|
73
|
+
"""_cmd_update restarts every running server, not just mesh.yaml."""
|
|
74
|
+
from culture.cli.mesh import _cmd_update
|
|
75
|
+
|
|
76
|
+
spark_mesh = MeshConfig(server=MeshServerConfig(name="spark"))
|
|
77
|
+
mock_list.return_value = [{"name": "spark", "pid": 1, "port": 6667}]
|
|
78
|
+
mock_resolve.return_value = spark_mesh
|
|
79
|
+
|
|
80
|
+
_cmd_update(update_args)
|
|
81
|
+
|
|
82
|
+
mock_resolve.assert_called_once_with("spark", update_args.config)
|
|
83
|
+
mock_restart.assert_called_once()
|
|
84
|
+
assert mock_restart.call_args[0][1] == "spark"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@patch("culture.pidfile.list_servers", return_value=[])
|
|
88
|
+
@patch("culture.mesh_config.load_mesh_config")
|
|
89
|
+
@patch(f"{_MESH_MOD}._restart_mesh_services")
|
|
90
|
+
@patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
|
|
91
|
+
def test_update_falls_back_to_mesh_yaml_when_no_servers(
|
|
92
|
+
_mock_upgrade, mock_restart, mock_load, _mock_list, update_args
|
|
93
|
+
):
|
|
94
|
+
"""When no servers are running, fall back to mesh.yaml."""
|
|
95
|
+
from culture.cli.mesh import _cmd_update
|
|
96
|
+
|
|
97
|
+
mesh = MeshConfig(server=MeshServerConfig(name="culture"))
|
|
98
|
+
mock_load.return_value = mesh
|
|
99
|
+
|
|
100
|
+
_cmd_update(update_args)
|
|
101
|
+
|
|
102
|
+
mock_load.assert_called_once_with(update_args.config)
|
|
103
|
+
mock_restart.assert_called_once()
|
|
104
|
+
assert mock_restart.call_args[0][1] == "culture"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@patch("culture.pidfile.list_servers")
|
|
108
|
+
@patch(f"{_MESH_MOD}._restart_mesh_services")
|
|
109
|
+
@patch(f"{_MESH_MOD}._upgrade_culture_package", return_value=True)
|
|
110
|
+
def test_update_skips_server_without_config(
|
|
111
|
+
_mock_upgrade, mock_restart, mock_list, update_args, capsys
|
|
112
|
+
):
|
|
113
|
+
"""Servers with no matching config are skipped with a warning."""
|
|
114
|
+
from culture.cli.mesh import _cmd_update
|
|
115
|
+
|
|
116
|
+
mock_list.return_value = [{"name": "unknown", "pid": 1, "port": 6667}]
|
|
117
|
+
|
|
118
|
+
with patch(f"{_MESH_MOD}._resolve_mesh_for_server", return_value=None):
|
|
119
|
+
_cmd_update(update_args)
|
|
120
|
+
|
|
121
|
+
mock_restart.assert_not_called()
|
|
122
|
+
assert "no config found" in capsys.readouterr().err
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---- _resolve_mesh_for_server tests ----
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_resolve_uses_mesh_yaml_when_name_matches(tmp_path):
|
|
129
|
+
"""_resolve_mesh_for_server returns mesh.yaml config when server name matches."""
|
|
130
|
+
from culture.cli.mesh import _resolve_mesh_for_server
|
|
131
|
+
|
|
132
|
+
mesh = MeshConfig(
|
|
133
|
+
server=MeshServerConfig(name="spark", links=[MeshLinkConfig(name="thor", host="1.2.3.4")]),
|
|
134
|
+
agents=[MeshAgentConfig(nick="claude", workdir="/tmp")],
|
|
135
|
+
)
|
|
136
|
+
config_path = str(tmp_path / "mesh.yaml")
|
|
137
|
+
from culture.mesh_config import save_mesh_config
|
|
138
|
+
|
|
139
|
+
save_mesh_config(mesh, config_path)
|
|
140
|
+
|
|
141
|
+
result = _resolve_mesh_for_server("spark", config_path)
|
|
142
|
+
assert result is not None
|
|
143
|
+
assert result.server.name == "spark"
|
|
144
|
+
assert len(result.server.links) == 1
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_resolve_rebuilds_from_agents_yaml_preserving_links(tmp_path):
|
|
148
|
+
"""When mesh.yaml has wrong name, rebuild from agents.yaml and keep links."""
|
|
149
|
+
from culture.cli.mesh import _resolve_mesh_for_server
|
|
150
|
+
from culture.clients.claude.config import AgentConfig, DaemonConfig, ServerConnConfig
|
|
151
|
+
from culture.mesh_config import save_mesh_config
|
|
152
|
+
|
|
153
|
+
# mesh.yaml says "culture" but running server is "spark"
|
|
154
|
+
old_mesh = MeshConfig(
|
|
155
|
+
server=MeshServerConfig(
|
|
156
|
+
name="culture",
|
|
157
|
+
host="127.0.0.1",
|
|
158
|
+
port=7000,
|
|
159
|
+
links=[MeshLinkConfig(name="thor", host="1.2.3.4")],
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
config_path = str(tmp_path / "mesh.yaml")
|
|
163
|
+
save_mesh_config(old_mesh, config_path)
|
|
164
|
+
|
|
165
|
+
# agents.yaml says "spark"
|
|
166
|
+
daemon = DaemonConfig(
|
|
167
|
+
server=ServerConnConfig(name="spark", host="localhost", port=6667),
|
|
168
|
+
agents=[AgentConfig(nick="spark-claude", agent="claude", directory="/tmp")],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
with patch(f"{_MESH_MOD}.DEFAULT_CONFIG", str(tmp_path / "agents.yaml")):
|
|
172
|
+
from culture.clients.claude.config import save_config
|
|
173
|
+
|
|
174
|
+
save_config(str(tmp_path / "agents.yaml"), daemon)
|
|
175
|
+
result = _resolve_mesh_for_server("spark", config_path)
|
|
176
|
+
|
|
177
|
+
assert result is not None
|
|
178
|
+
assert result.server.name == "spark"
|
|
179
|
+
assert len(result.agents) == 1
|
|
180
|
+
assert result.agents[0].nick == "claude"
|
|
181
|
+
# Server settings from old mesh.yaml are preserved
|
|
182
|
+
assert result.server.host == "127.0.0.1"
|
|
183
|
+
assert result.server.port == 7000
|
|
184
|
+
assert len(result.server.links) == 1
|
|
185
|
+
assert result.server.links[0].name == "thor"
|