agentirc-cli 8.2.0__tar.gz → 8.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/CHANGELOG.md +36 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/PKG-INFO +1 -1
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/client.py +138 -111
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/config.py +2 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/ircd.py +16 -2
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/server_link.py +224 -44
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/tracing.md +7 -1
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/__init__.py +7 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/context.py +37 -0
- agentirc_cli-8.4.0/culture/telemetry/metrics.py +225 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/tracing.py +4 -1
- agentirc_cli-8.4.0/docs/agentirc/telemetry.md +140 -0
- agentirc_cli-8.4.0/docs/superpowers/plans/2026-04-25-otel-federation.md +229 -0
- agentirc_cli-8.4.0/docs/superpowers/plans/2026-04-26-otel-metrics.md +303 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/pyproject.toml +1 -1
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/conftest.py +25 -0
- agentirc_cli-8.4.0/tests/telemetry/_metrics_helpers.py +66 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_context.py +42 -0
- agentirc_cli-8.4.0/tests/telemetry/test_federation_propagation.py +141 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_clients.py +79 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_events.py +81 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_init.py +58 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_irc.py +99 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_s2s.py +160 -0
- agentirc_cli-8.4.0/tests/telemetry/test_metrics_trace_inbound.py +127 -0
- agentirc_cli-8.4.0/tests/telemetry/test_s2s_dispatch_span.py +156 -0
- agentirc_cli-8.4.0/tests/telemetry/test_s2s_relay_span.py +114 -0
- agentirc_cli-8.4.0/tests/telemetry/test_s2s_session_span.py +64 -0
- agentirc_cli-8.4.0/tests/telemetry/test_server_link_inject.py +124 -0
- agentirc_cli-8.4.0/tests/telemetry/test_session_span.py +105 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_federation.py +47 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/uv.lock +1 -1
- agentirc_cli-8.2.0/docs/agentirc/telemetry.md +0 -81
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/agents/doc-test-alignment.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.flake8 +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/docs-check.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.gitignore +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pr_agent.toml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pylintrc +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/CLAUDE.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/Gemfile +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/Gemfile.lock +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/LICENSE +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/README.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/SECURITY.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_config.base.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_config.culture.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_data/sites.yml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_includes/head_custom.html +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/color_schemes/dark-terminal.scss +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/IMG_3183.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/apple-touch-icon.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon-16x16.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon-32x32.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon.ico +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/og-agentirc.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/og-culture.png +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/__main__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/CLAUDE.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/__main__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/channel.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-architecture.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-features.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-skill.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/events.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/history_store.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/remote_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/room_store.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/rooms_util.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skill.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/history.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/icon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/threads.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/thread_store.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/aio.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/bot.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/filter_dsl.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/bot.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/handler.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/_passthrough.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/afi.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/agent.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/bot.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/channel.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/devex.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/introspect.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/mesh.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/constants.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/display.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/formatting.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/mesh.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/process.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/skills.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/culture.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/culture.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/culture.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/culture.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/app.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/commands.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/status.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/constants.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/credentials.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/formatting.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/learn_prompt.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/mesh_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/observer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/collector.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/model.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/web/style.css +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/persistence.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/pidfile.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/commands.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/events.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/message.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/replies.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/README.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/architecture-overview.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/bots.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/events.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/otelcol-template.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/why-agentirc.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/agent-lifecycle.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/choose-a-harness.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/features.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/mental-model.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/operate.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/patterns.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/quickstart.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/reflective-development.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/vision-patterns-index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/vision.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/what-is-culture.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/layers.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/subsites.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/threads.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/afi.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/commands.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/devex.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/console.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/acp.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/claude.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/codex.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/copilot.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/architecture.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/config.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/deployment.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/security.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/resources/positioning.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/federation.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/harnesses.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/humans-and-agents.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/persistence.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/rooms.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/demos/magic-demo.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/first-session.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/join-as-human.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/local-setup.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/multi-machine.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases-index.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-15-mesh-events.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-18-culture-dev-positioning.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-22-agex-integration.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-24-otel-foundation.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-15-mesh-events-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-17-sites-repositioning-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-18-culture-dev-positioning-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-22-agex-integration-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-24-otel-observability-design.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/favicon.ico +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/culture.yaml +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/robots.txt +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap-agentirc.html +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap-main.html +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap.html +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sonar-project.properties +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/__init__.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/_fakes.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_config_load.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_dispatch_span.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_emit_event_span.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_outbound_inject.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_parse_error.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_privmsg_span.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_server_init.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_tracing.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_archive.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_config_fires_event_toplevel.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_channel.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_channel_cli.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_afi.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_devex.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_introspect.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_passthrough.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_connection.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_chat_markdown.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_commands.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_connection.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_fixes_224_227.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_icons.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_integration.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_status.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_credentials.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_culture_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_discovery.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_display.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_basic.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_bot_chain.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_bot_trigger.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_cap_fallback.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_catalog.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_federation.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_history.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_lifecycle.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_reserved_nick.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_filter_dsl.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_history.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_http_listener.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_ipc.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_irc_transport_tags.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_learn_prompt.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_manifest_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_alias.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_target_cleanup.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_warning.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mentions.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mesh_readiness.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message_tags.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_messaging.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_migrate_cli.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_modes.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_model.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_web.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_persistence.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_persistence_timeout.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_pidfile.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_register_cli.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skill_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skill_docs.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skills.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_socket_server.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_supervisor.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_template_engine.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_threads.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_webhook.py +0 -0
- {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_welcome_bot.py +0 -0
|
@@ -4,6 +4,42 @@ 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
|
+
## [8.4.0] - 2026-04-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `culture/telemetry/metrics.py`: `init_metrics(config)` + `MetricsRegistry` dataclass for all 15 server-side instruments — mirrors `tracing.py`'s idempotency + no-op pattern.
|
|
12
|
+
- Public `culture.telemetry.MetricsRegistry` and `culture.telemetry.init_metrics`.
|
|
13
|
+
- `TelemetryConfig.metrics_enabled` (default `True`) and `metrics_export_interval_ms` (default 10000).
|
|
14
|
+
- Message-flow metrics: `culture.irc.bytes_sent`, `culture.irc.bytes_received`, `culture.irc.message.size`, `culture.privmsg.delivered`.
|
|
15
|
+
- Events metrics: `culture.events.emitted`, `culture.events.render.duration`.
|
|
16
|
+
- Federation metrics: `culture.s2s.messages` (inbound), `culture.s2s.relay_latency`, `culture.s2s.links_active`, `culture.s2s.link_events`.
|
|
17
|
+
- Client metrics: `culture.clients.connected`, `culture.client.session.duration`, `culture.client.command.duration`.
|
|
18
|
+
- `culture.trace.inbound` counter — closes Plan 2's deferral.
|
|
19
|
+
- `tests/conftest.py` `metrics_reader` fixture parallel to `tracing_exporter`.
|
|
20
|
+
- `tests/telemetry/_metrics_helpers.py` — `get_counter_value`, `get_histogram_count`, `get_up_down_value`.
|
|
21
|
+
|
|
22
|
+
## [8.3.0] - 2026-04-25
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `irc.s2s.session` span over ServerLink connection lifetime.
|
|
27
|
+
- `irc.s2s.<VERB>` per-verb spans on inbound federation messages with traceparent extraction and the inbound mitigation rules from `culture/protocol/extensions/tracing.md`.
|
|
28
|
+
- `irc.s2s.relay` span on outbound relay enforcing the re-sign-per-hop rule.
|
|
29
|
+
- `irc.client.session` span over Client connection lifetime (#290).
|
|
30
|
+
- `irc.join` and `irc.part` spans (#290).
|
|
31
|
+
- Public `culture.telemetry.context_from_traceparent` and `culture.telemetry.current_traceparent` helpers.
|
|
32
|
+
- Single traceparent injection choke point at `ServerLink.send_raw`.
|
|
33
|
+
- End-to-end propagation tests proving one `trace_id` spans federated client → server → relay → server hops.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `Client._dispatch` span name and `irc.command` attribute now uppercase, matching `ServerLink._dispatch` convention.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `_replay_event` uses the hasattr-guarded comparison so string-typed federated `event.type` no longer skips the typed fast path. (#291)
|
|
42
|
+
|
|
7
43
|
## [8.2.0] - 2026-04-24
|
|
8
44
|
|
|
9
45
|
### Added
|
|
@@ -4,11 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
+
import time
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
9
10
|
from opentelemetry import trace as _otel_trace
|
|
10
|
-
from opentelemetry.
|
|
11
|
-
from opentelemetry.trace
|
|
11
|
+
from opentelemetry.context import Context as _OtelContext
|
|
12
|
+
from opentelemetry.trace import Span as _OtelSpan
|
|
12
13
|
|
|
13
14
|
from culture.agentirc.channel import Channel
|
|
14
15
|
from culture.agentirc.skill import Event, EventType
|
|
@@ -18,6 +19,8 @@ from culture.protocol import replies
|
|
|
18
19
|
from culture.protocol.message import Message
|
|
19
20
|
from culture.telemetry.context import TRACEPARENT_TAG as _TP_TAG_NAME
|
|
20
21
|
from culture.telemetry.context import (
|
|
22
|
+
context_from_traceparent,
|
|
23
|
+
current_traceparent,
|
|
21
24
|
extract_traceparent_from_tags,
|
|
22
25
|
)
|
|
23
26
|
from culture.telemetry.context import inject_traceparent as _inject_traceparent
|
|
@@ -25,39 +28,12 @@ from culture.telemetry.context import inject_traceparent as _inject_traceparent
|
|
|
25
28
|
# OTEL instrumentation name (must match `_CULTURE_TRACER_NAME` in
|
|
26
29
|
# culture/telemetry/tracing.py so trace consumers see one consistent value).
|
|
27
30
|
_TRACER_NAME = "culture.agentirc"
|
|
28
|
-
# Span attribute keys
|
|
29
|
-
#
|
|
31
|
+
# Span attribute keys, defined once so a future rename / sanitization layer
|
|
32
|
+
# has one edit point.
|
|
30
33
|
_ATTR_BODY = "irc.message.body"
|
|
31
34
|
_ATTR_SIZE = "irc.message.size"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _context_from_traceparent(tp: str):
|
|
35
|
-
"""Build an OTEL context whose current span is a NonRecordingSpan
|
|
36
|
-
synthesized from a W3C traceparent string. The `_dispatch` span we
|
|
37
|
-
start next will be a child of this context."""
|
|
38
|
-
# Format: 00-<trace-id>-<parent-id>-<flags>
|
|
39
|
-
_, trace_hex, parent_hex, flags_hex = tp.split("-")
|
|
40
|
-
span_ctx = SpanContext(
|
|
41
|
-
trace_id=int(trace_hex, 16),
|
|
42
|
-
span_id=int(parent_hex, 16),
|
|
43
|
-
is_remote=True,
|
|
44
|
-
trace_flags=TraceFlags(int(flags_hex, 16)),
|
|
45
|
-
)
|
|
46
|
-
return set_span_in_context(NonRecordingSpan(span_ctx))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _current_traceparent() -> str | None:
|
|
50
|
-
"""Return the W3C traceparent for the currently-active span, or None
|
|
51
|
-
if no span is recording (no-op tracer / sampler dropped).
|
|
52
|
-
"""
|
|
53
|
-
span = _otel_trace.get_current_span()
|
|
54
|
-
ctx = span.get_span_context()
|
|
55
|
-
if not ctx.is_valid:
|
|
56
|
-
return None
|
|
57
|
-
return (
|
|
58
|
-
f"00-{format(ctx.trace_id, '032x')}-{format(ctx.span_id, '016x')}"
|
|
59
|
-
f"-{format(int(ctx.trace_flags), '02x')}"
|
|
60
|
-
)
|
|
35
|
+
_ATTR_NICK = "irc.client.nick"
|
|
36
|
+
_ATTR_CHANNEL = "irc.channel"
|
|
61
37
|
|
|
62
38
|
|
|
63
39
|
if TYPE_CHECKING:
|
|
@@ -86,6 +62,7 @@ class Client:
|
|
|
86
62
|
self.caps: set[str] = set()
|
|
87
63
|
self.modes: set[str] = set()
|
|
88
64
|
self.icon: str | None = None
|
|
65
|
+
self._session_span: _OtelSpan | None = None
|
|
89
66
|
|
|
90
67
|
@property
|
|
91
68
|
def prefix(self) -> str:
|
|
@@ -97,12 +74,16 @@ class Client:
|
|
|
97
74
|
# block and `send_tagged`'s tag-stripping for non-capable clients
|
|
98
75
|
# would be undone here.
|
|
99
76
|
if "message-tags" in self.caps:
|
|
100
|
-
tp =
|
|
77
|
+
tp = current_traceparent()
|
|
101
78
|
if tp is not None:
|
|
102
79
|
_inject_traceparent(message, traceparent=tp, tracestate=None)
|
|
103
80
|
try:
|
|
104
|
-
|
|
81
|
+
wire = message.format().encode("utf-8")
|
|
82
|
+
self.writer.write(wire)
|
|
105
83
|
await self.writer.drain()
|
|
84
|
+
# Record bytes after a successful drain so we don't count
|
|
85
|
+
# writes that immediately faulted.
|
|
86
|
+
self.server.metrics.irc_bytes_sent.add(len(wire), {"direction": "s2c"})
|
|
106
87
|
except OSError:
|
|
107
88
|
pass # Client disconnected; cleanup happens in ircd._handle_connection
|
|
108
89
|
|
|
@@ -114,14 +95,16 @@ class Client:
|
|
|
114
95
|
AND the client negotiated the `message-tags` capability.
|
|
115
96
|
"""
|
|
116
97
|
if "message-tags" in self.caps:
|
|
117
|
-
tp =
|
|
98
|
+
tp = current_traceparent()
|
|
118
99
|
if tp is not None:
|
|
119
100
|
# send_raw takes a pre-formatted line without an existing tag
|
|
120
101
|
# block; prefix a fresh @tag.
|
|
121
102
|
line = f"@{_TP_TAG_NAME}={tp} {line}"
|
|
122
103
|
try:
|
|
123
|
-
|
|
104
|
+
wire = f"{line}\r\n".encode("utf-8")
|
|
105
|
+
self.writer.write(wire)
|
|
124
106
|
await self.writer.drain()
|
|
107
|
+
self.server.metrics.irc_bytes_sent.add(len(wire), {"direction": "s2c"})
|
|
125
108
|
except OSError:
|
|
126
109
|
pass # Client disconnected; cleanup happens in ircd._handle_connection
|
|
127
110
|
|
|
@@ -166,35 +149,63 @@ class Client:
|
|
|
166
149
|
},
|
|
167
150
|
)
|
|
168
151
|
continue
|
|
152
|
+
# Record received bytes + message size for every successfully-parsed
|
|
153
|
+
# line. +2 accounts for the \r\n that was stripped during line-split.
|
|
154
|
+
line_bytes = len(line.encode("utf-8")) + 2
|
|
155
|
+
self.server.metrics.irc_bytes_received.add(line_bytes, {"direction": "c2s"})
|
|
156
|
+
self.server.metrics.irc_message_size.record(
|
|
157
|
+
line_bytes, {"verb": msg.command, "direction": "c2s"}
|
|
158
|
+
)
|
|
169
159
|
if msg.command:
|
|
170
160
|
await self._dispatch(msg)
|
|
171
161
|
return buffer
|
|
172
162
|
|
|
173
163
|
async def handle(self, initial_msg: str | None = None) -> None:
|
|
174
|
-
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
buffer =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
164
|
+
peer_info = self.writer.get_extra_info("peername")
|
|
165
|
+
remote_addr = f"{peer_info[0]}:{peer_info[1]}" if peer_info else ""
|
|
166
|
+
kind = "human" # Plan 5/6 will refine to bot/harness
|
|
167
|
+
self.server.metrics.clients_connected.add(1, {"kind": kind})
|
|
168
|
+
session_started = time.perf_counter()
|
|
169
|
+
with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
|
|
170
|
+
"irc.client.session",
|
|
171
|
+
attributes={"irc.client.remote_addr": remote_addr},
|
|
172
|
+
) as span:
|
|
173
|
+
self._session_span = span
|
|
174
|
+
try:
|
|
175
|
+
buffer = ""
|
|
176
|
+
if initial_msg:
|
|
177
|
+
buffer = initial_msg.replace("\r\n", "\n").replace("\r", "\n")
|
|
178
|
+
buffer = await self._process_buffer(buffer)
|
|
179
|
+
while True:
|
|
180
|
+
data = await self.reader.read(4096)
|
|
181
|
+
if not data:
|
|
182
|
+
break
|
|
183
|
+
buffer += data.decode("utf-8", errors="replace")
|
|
184
|
+
# Cap buffer to prevent unbounded memory growth (512 bytes per RFC 2812)
|
|
185
|
+
if len(buffer) > 8192:
|
|
186
|
+
buffer = buffer[-4096:]
|
|
187
|
+
# Normalize all line endings to \n for simpler parsing
|
|
188
|
+
buffer = buffer.replace("\r\n", "\n").replace("\r", "\n")
|
|
189
|
+
buffer = await self._process_buffer(buffer)
|
|
190
|
+
except (ConnectionError, asyncio.IncompleteReadError):
|
|
191
|
+
pass
|
|
192
|
+
finally:
|
|
193
|
+
self.server.metrics.clients_connected.add(-1, {"kind": kind})
|
|
194
|
+
self.server.metrics.client_session_duration.record(
|
|
195
|
+
time.perf_counter() - session_started, {"kind": kind}
|
|
196
|
+
)
|
|
189
197
|
|
|
190
198
|
async def _dispatch(self, msg: Message) -> None:
|
|
191
199
|
extract = extract_traceparent_from_tags(msg, peer=None)
|
|
192
|
-
|
|
200
|
+
self.server.metrics.trace_inbound.add(1, {"result": extract.status, "peer": ""})
|
|
193
201
|
if extract.status == "valid":
|
|
194
|
-
parent_ctx =
|
|
202
|
+
parent_ctx: _OtelContext | None = context_from_traceparent(extract.traceparent)
|
|
203
|
+
else:
|
|
204
|
+
parent_ctx = _OtelContext() # force root: detach from session span
|
|
195
205
|
|
|
206
|
+
verb = msg.command.upper()
|
|
196
207
|
attrs = {
|
|
197
|
-
"irc.command":
|
|
208
|
+
"irc.command": verb,
|
|
198
209
|
"irc.prefix_nick": (msg.prefix.split("!")[0] if msg.prefix else ""),
|
|
199
210
|
"culture.trace.origin": "local" if extract.status == "missing" else "remote",
|
|
200
211
|
}
|
|
@@ -202,8 +213,9 @@ class Client:
|
|
|
202
213
|
attrs["culture.trace.dropped_reason"] = extract.status
|
|
203
214
|
|
|
204
215
|
# Per-call get_tracer: test fixture swaps provider between tests.
|
|
216
|
+
cmd_started = time.perf_counter()
|
|
205
217
|
with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
|
|
206
|
-
f"irc.command.{
|
|
218
|
+
f"irc.command.{verb}",
|
|
207
219
|
context=parent_ctx,
|
|
208
220
|
attributes=attrs,
|
|
209
221
|
):
|
|
@@ -223,6 +235,9 @@ class Client:
|
|
|
223
235
|
await self.send_numeric(
|
|
224
236
|
replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command"
|
|
225
237
|
)
|
|
238
|
+
self.server.metrics.client_command_duration.record(
|
|
239
|
+
(time.perf_counter() - cmd_started) * 1000.0, {"verb": verb}
|
|
240
|
+
)
|
|
226
241
|
|
|
227
242
|
async def _handle_ping(self, msg: Message) -> None:
|
|
228
243
|
token = msg.params[0] if msg.params else ""
|
|
@@ -304,6 +319,8 @@ class Client:
|
|
|
304
319
|
|
|
305
320
|
self.nick = nick
|
|
306
321
|
self.server.clients[nick] = self
|
|
322
|
+
if self._session_span is not None:
|
|
323
|
+
self._session_span.set_attribute(_ATTR_NICK, nick)
|
|
307
324
|
await self._try_register()
|
|
308
325
|
|
|
309
326
|
async def _handle_user(self, msg: Message) -> None:
|
|
@@ -355,45 +372,49 @@ class Client:
|
|
|
355
372
|
return
|
|
356
373
|
|
|
357
374
|
channel_name = msg.params[0]
|
|
358
|
-
|
|
359
|
-
|
|
375
|
+
with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
|
|
376
|
+
"irc.join",
|
|
377
|
+
attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
|
|
378
|
+
):
|
|
379
|
+
if not channel_name.startswith("#"):
|
|
380
|
+
return
|
|
360
381
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
382
|
+
# Block joins to archived rooms
|
|
383
|
+
existing = self.server.channels.get(channel_name)
|
|
384
|
+
if existing and existing.archived:
|
|
385
|
+
await self.send(
|
|
386
|
+
Message(
|
|
387
|
+
prefix=self.server.config.name,
|
|
388
|
+
command="NOTICE",
|
|
389
|
+
params=[self.nick, f"{channel_name} is archived and cannot be joined"],
|
|
390
|
+
)
|
|
369
391
|
)
|
|
370
|
-
|
|
371
|
-
return
|
|
392
|
+
return
|
|
372
393
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
394
|
+
channel = self.server.get_or_create_channel(channel_name)
|
|
395
|
+
if self in channel.members:
|
|
396
|
+
return
|
|
376
397
|
|
|
377
|
-
|
|
378
|
-
|
|
398
|
+
channel.add(self)
|
|
399
|
+
self.channels.add(channel)
|
|
379
400
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
401
|
+
# Notify all channel members (including self)
|
|
402
|
+
join_msg = Message(prefix=self.prefix, command="JOIN", params=[channel_name])
|
|
403
|
+
for member in list(channel.members):
|
|
404
|
+
await member.send(join_msg)
|
|
384
405
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
406
|
+
# Send topic if set
|
|
407
|
+
if channel.topic:
|
|
408
|
+
await self.send_numeric(replies.RPL_TOPIC, channel_name, channel.topic)
|
|
388
409
|
|
|
389
|
-
|
|
390
|
-
|
|
410
|
+
# Send names list
|
|
411
|
+
await self._send_names(channel)
|
|
391
412
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
413
|
+
# Emit event AFTER delivering all join-related numerics (topic, NAMES)
|
|
414
|
+
# so that the event PRIVMSG doesn't interleave with 353/366 in client buffers.
|
|
415
|
+
await self.server.emit_event(
|
|
416
|
+
Event(type=EventType.JOIN, channel=channel_name, nick=self.nick)
|
|
417
|
+
)
|
|
397
418
|
|
|
398
419
|
async def _handle_part(self, msg: Message) -> None:
|
|
399
420
|
if not msg.params:
|
|
@@ -401,36 +422,40 @@ class Client:
|
|
|
401
422
|
return
|
|
402
423
|
|
|
403
424
|
channel_name = msg.params[0]
|
|
404
|
-
|
|
425
|
+
with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
|
|
426
|
+
"irc.part",
|
|
427
|
+
attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
|
|
428
|
+
):
|
|
429
|
+
reason = msg.params[1] if len(msg.params) > 1 else ""
|
|
405
430
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
431
|
+
channel = self.server.channels.get(channel_name)
|
|
432
|
+
if not channel or self not in channel.members:
|
|
433
|
+
await self.send_numeric(
|
|
434
|
+
replies.ERR_NOTONCHANNEL,
|
|
435
|
+
channel_name,
|
|
436
|
+
replies.MSG_NOTONCHANNEL,
|
|
437
|
+
)
|
|
438
|
+
return
|
|
414
439
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
440
|
+
part_params = [channel_name, reason] if reason else [channel_name]
|
|
441
|
+
part_msg = Message(prefix=self.prefix, command="PART", params=part_params)
|
|
442
|
+
for member in list(channel.members):
|
|
443
|
+
await member.send(part_msg)
|
|
419
444
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
445
|
+
await self.server.emit_event(
|
|
446
|
+
Event(
|
|
447
|
+
type=EventType.PART,
|
|
448
|
+
channel=channel_name,
|
|
449
|
+
nick=self.nick,
|
|
450
|
+
data={"reason": reason},
|
|
451
|
+
)
|
|
426
452
|
)
|
|
427
|
-
)
|
|
428
453
|
|
|
429
|
-
|
|
430
|
-
|
|
454
|
+
channel.remove(self)
|
|
455
|
+
self.channels.discard(channel)
|
|
431
456
|
|
|
432
|
-
|
|
433
|
-
|
|
457
|
+
if not channel.members and not channel.persistent:
|
|
458
|
+
del self.server.channels[channel_name]
|
|
434
459
|
|
|
435
460
|
async def _handle_topic(self, msg: Message) -> None:
|
|
436
461
|
if not msg.params:
|
|
@@ -726,6 +751,7 @@ class Client:
|
|
|
726
751
|
for member in list(channel.members):
|
|
727
752
|
if member is not self:
|
|
728
753
|
await member.send(relay)
|
|
754
|
+
self.server.metrics.privmsg_delivered.add(1, {"kind": "channel", "channel": target})
|
|
729
755
|
event_data = {"text": text}
|
|
730
756
|
if is_notice:
|
|
731
757
|
event_data["notice"] = True
|
|
@@ -760,6 +786,7 @@ class Client:
|
|
|
760
786
|
)
|
|
761
787
|
else:
|
|
762
788
|
await recipient.send(relay)
|
|
789
|
+
self.server.metrics.privmsg_delivered.add(1, {"kind": "dm"})
|
|
763
790
|
event_data = {"text": text, "target": target}
|
|
764
791
|
if is_notice:
|
|
765
792
|
event_data["notice"] = True
|
|
@@ -5,6 +5,7 @@ import asyncio
|
|
|
5
5
|
import base64
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import time
|
|
8
9
|
from collections import deque
|
|
9
10
|
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
@@ -25,6 +26,9 @@ from culture.protocol.message import Message
|
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
29
|
+
# Span/metric attribute keys defined once so a future rename has one edit point.
|
|
30
|
+
_ATTR_EVENT_TYPE = "event.type"
|
|
31
|
+
|
|
28
32
|
if TYPE_CHECKING:
|
|
29
33
|
from culture.agentirc.client import Client
|
|
30
34
|
from culture.agentirc.remote_client import RemoteClient
|
|
@@ -35,10 +39,11 @@ class IRCd:
|
|
|
35
39
|
"""The culture IRC server."""
|
|
36
40
|
|
|
37
41
|
def __init__(self, config: ServerConfig):
|
|
38
|
-
from culture.telemetry import init_telemetry
|
|
42
|
+
from culture.telemetry import init_metrics, init_telemetry
|
|
39
43
|
|
|
40
44
|
self.config = config
|
|
41
45
|
self.tracer = init_telemetry(config)
|
|
46
|
+
self.metrics = init_metrics(config)
|
|
42
47
|
self.clients: dict[str, Client | VirtualClient] = {} # nick -> Client
|
|
43
48
|
self.channels: dict[str, Channel] = {} # name -> Channel
|
|
44
49
|
self.skills: list[Skill] = []
|
|
@@ -178,7 +183,7 @@ class IRCd:
|
|
|
178
183
|
# server_link.py).
|
|
179
184
|
event_type_str = event.type.value if hasattr(event.type, "value") else str(event.type)
|
|
180
185
|
attrs: dict[str, str] = {
|
|
181
|
-
|
|
186
|
+
_ATTR_EVENT_TYPE: event_type_str,
|
|
182
187
|
"event.origin": "federated" if origin_tag else "local",
|
|
183
188
|
}
|
|
184
189
|
if event.channel:
|
|
@@ -212,6 +217,12 @@ class IRCd:
|
|
|
212
217
|
async def emit_event(self, event: Event) -> None:
|
|
213
218
|
origin_tag = event.data.get("_origin")
|
|
214
219
|
attrs = self._build_event_span_attrs(event, origin_tag)
|
|
220
|
+
event_type_str = attrs[_ATTR_EVENT_TYPE]
|
|
221
|
+
origin_str = "federated" if origin_tag else "local"
|
|
222
|
+
|
|
223
|
+
self.metrics.events_emitted.add(1, {_ATTR_EVENT_TYPE: event_type_str, "origin": origin_str})
|
|
224
|
+
render_started = time.perf_counter()
|
|
225
|
+
|
|
215
226
|
# Per-call get_tracer: the `tracing_exporter` test fixture swaps the
|
|
216
227
|
# global provider between tests; a cached Tracer would bind to the
|
|
217
228
|
# first test's provider and stop delivering to later ones.
|
|
@@ -226,6 +237,9 @@ class IRCd:
|
|
|
226
237
|
await self._dispatch_to_bots(event)
|
|
227
238
|
await self._surface_event_privmsg(event)
|
|
228
239
|
|
|
240
|
+
render_ms = (time.perf_counter() - render_started) * 1000.0
|
|
241
|
+
self.metrics.events_render_duration.record(render_ms, {_ATTR_EVENT_TYPE: event_type_str})
|
|
242
|
+
|
|
229
243
|
_NO_SURFACE_TYPES = NO_SURFACE_EVENT_TYPES
|
|
230
244
|
|
|
231
245
|
@staticmethod
|