agentirc-cli 9.4.1__tar.gz → 9.5.0a1__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-9.4.1 → agentirc_cli-9.5.0a1}/CHANGELOG.md +17 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/PKG-INFO +1 -1
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/cli.py +4 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/config.py +10 -2
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/protocol.py +130 -2
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skill.py +10 -37
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/api-stability.md +56 -0
- agentirc_cli-9.5.0a1/docs/extension-api.md +243 -0
- agentirc_cli-9.5.0a1/docs/superpowers/specs/2026-05-01-bot-extension-api-design.md +458 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/pyproject.toml +1 -1
- agentirc_cli-9.5.0a1/tests/test_protocol_bot_exports.py +214 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/uv.lock +1 -1
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/portability-lint.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/pr-batch.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/pr-comments.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/pr-reply.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/pr-sonar.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/pr-status.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills/pr-review/scripts/workflow.sh +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.claude/skills.local.yaml.example +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/.gitignore +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/CLAUDE.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/LICENSE +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/README.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/__main__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/aio.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/bots/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/bots/bot_manager.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/bots/http_listener.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/cli_shared/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/cli_shared/constants.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/cli_shared/mesh.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/constants.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/pidfile.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/protocol/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/protocol/message.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/protocol/replies.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/telemetry/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/telemetry/audit.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/telemetry/context.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/telemetry/metrics.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/telemetry/tracing.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/_internal/virtual_client.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/channel.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/client.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/events.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/history_store.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/ircd.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/remote_client.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/room_store.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/rooms_util.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/server_link.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skills/history.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skills/icon.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/skills/threads.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/agentirc/thread_store.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/cli.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/deployment.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/steward/onboarding.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/superpowers/specs/2026-04-30-bootstrap-design.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/docs/superpowers/specs/2026-05-01-task14-audit.md +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/_helpers.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/conftest.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/__init__.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/_fakes.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/_metrics_helpers.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_audit_emit.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_audit_lifecycle.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_audit_module.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_audit_parse_error.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_config.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_dispatch_span.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_emit_event_span.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_metrics_init.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_metrics_s2s.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_outbound_inject.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_parse_error.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_s2s_relay_span.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_server_init.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_server_link_inject.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/telemetry/test_tracing.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_channel.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_cli.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_config_loader.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_connection.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_discovery.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_basic.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_catalog.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_federation.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_history.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_lifecycle.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_events_reserved_nick.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_federation.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_history.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_mentions.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_messaging.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_modes.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_skills.py +0 -0
- {agentirc_cli-9.4.1 → agentirc_cli-9.5.0a1}/tests/test_threads.py +0 -0
|
@@ -4,6 +4,23 @@ 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
|
+
## [9.5.0a1] - 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Public `agentirc.protocol` exports for the bot extension API: the `Event` dataclass, the `EventType` enum, 20 per-type `EVENT_TYPE_*` string constants, the `EVENTSUB` / `EVENTUNSUB` / `EVENT` / `EVENTERR` / `EVENTPUB` verb constants, and the `BOT_CAP = "agentirc.io/bot"` capability identifier. `Event.type` is widened to `EventType | str` so federation peers can deliver event types this version doesn't recognise. See `docs/superpowers/specs/2026-05-01-bot-extension-api-design.md` for the full design and `docs/extension-api.md` for the bot-author quick reference.
|
|
12
|
+
- `ServerConfig.event_subscription_queue_max: int = 1024` — per-subscription queue bound. Recognised by `ServerConfig.from_yaml` and `cli._resolve_config()` as a top-level YAML key. Consumed by the subscription registry that lands in 9.5.0a3.
|
|
13
|
+
- `agentirc.skill` keeps re-exporting `Event` and `EventType` for backward compat; the re-export shim is removed in 9.6.0 once Phase A2 confirms no consumer relies on the path.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `EventType` upgraded from `enum.Enum` to `enum.StrEnum`. Members keep their `.value` strings unchanged, but Python consumers now observe new identity semantics: `isinstance(EventType.JOIN, str)` is `True`, `EventType.JOIN == "user.join"` is `True`, JSON serialization emits the bare string. Internal call sites verified — none compare `EventType.X` against a bare string today, so the truthier equality is pure improvement; downstream consumers that did string-equality round-trips against `EventType` see strictly more matches, never fewer.
|
|
18
|
+
|
|
19
|
+
### Notes
|
|
20
|
+
|
|
21
|
+
- This is the **declarations slice** of the bot extension API. No daemon-level behavior changes: `EVENTSUB` etc. are reserved verb constants but the daemon does not yet handle them; `BOT_CAP` is exported but not yet advertised in `CAP LS` output. The wire-format envelope refactor lands in 9.5.0a2; the bot CAP behavior, subscription verbs, `EVENTPUB`, and `webhook_port` unbinding land in 9.5.0a3 / 9.5.0 final.
|
|
22
|
+
- Tracks [agentculture/agentirc#15](https://github.com/agentculture/agentirc/issues/15).
|
|
23
|
+
|
|
7
24
|
## [9.4.1] - 2026-05-01
|
|
8
25
|
|
|
9
26
|
### Documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentirc-cli
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.5.0a1
|
|
4
4
|
Summary: Agent-friendly IRCd: server core for AI agent meshes
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/agentirc
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/agentirc/issues
|
|
@@ -185,6 +185,9 @@ def _resolve_config(args: argparse.Namespace) -> "ServerConfig": # noqa: F821 (
|
|
|
185
185
|
args.data_dir, raw.get("data_dir"), os.path.expanduser("~/.culture/data")
|
|
186
186
|
)
|
|
187
187
|
)
|
|
188
|
+
event_subscription_queue_max = _pick(
|
|
189
|
+
None, raw.get("event_subscription_queue_max"), 1024
|
|
190
|
+
)
|
|
188
191
|
|
|
189
192
|
cfg = ServerConfig(
|
|
190
193
|
name=name or "agentirc",
|
|
@@ -195,6 +198,7 @@ def _resolve_config(args: argparse.Namespace) -> "ServerConfig": # noqa: F821 (
|
|
|
195
198
|
links=_resolve_links(getattr(args, "link", None), raw.get("links") or []),
|
|
196
199
|
system_bots=raw.get("system_bots") or {},
|
|
197
200
|
telemetry=_build_telemetry(raw.get("telemetry") or {}),
|
|
201
|
+
event_subscription_queue_max=event_subscription_queue_max,
|
|
198
202
|
)
|
|
199
203
|
|
|
200
204
|
args.name = name # may be None — handler resolves via default-server file
|
|
@@ -53,13 +53,21 @@ class ServerConfig:
|
|
|
53
53
|
links: list[LinkConfig] = field(default_factory=list)
|
|
54
54
|
system_bots: dict = field(default_factory=dict)
|
|
55
55
|
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
|
56
|
+
# Bot extension API (9.5.0): per-subscription event-queue bound. When
|
|
57
|
+
# exceeded, the subscription is dropped with EVENTERR :backpressure-overflow
|
|
58
|
+
# and the bot reconciles via re-subscribe + BACKFILL. Behavior wires up in
|
|
59
|
+
# 9.5.0a3; the field is exposed in 9.5.0a1 so consumers can pin against the
|
|
60
|
+
# public surface.
|
|
61
|
+
event_subscription_queue_max: int = 1024
|
|
56
62
|
|
|
57
63
|
@classmethod
|
|
58
64
|
def from_yaml(cls, path: str | Path) -> "ServerConfig":
|
|
59
65
|
"""Load a ServerConfig from a YAML file.
|
|
60
66
|
|
|
61
67
|
Recognises top-level ``server`` (host/port/name), ``telemetry``,
|
|
62
|
-
``links``, ``webhook_port``, ``data_dir``,
|
|
68
|
+
``links``, ``webhook_port``, ``data_dir``, ``system_bots``, and
|
|
69
|
+
``event_subscription_queue_max`` (added in 9.5.0a1; consumed by
|
|
70
|
+
the subscription registry that lands in 9.5.0a3).
|
|
63
71
|
Unknown top-level keys (``supervisor``, ``agents``, ``buffer_size``,
|
|
64
72
|
``poll_interval``, ``sleep_start``, ``sleep_end``) are silently
|
|
65
73
|
ignored — those belong to culture's broader process supervisor,
|
|
@@ -104,7 +112,7 @@ def _yaml_kwargs(raw: dict[str, Any]) -> dict[str, Any]:
|
|
|
104
112
|
for key in ("name", "host", "port"):
|
|
105
113
|
if key in server_section:
|
|
106
114
|
kwargs[key] = server_section[key]
|
|
107
|
-
for key in ("webhook_port", "data_dir"):
|
|
115
|
+
for key in ("webhook_port", "data_dir", "event_subscription_queue_max"):
|
|
108
116
|
if key in raw:
|
|
109
117
|
kwargs[key] = raw[key]
|
|
110
118
|
links_section = raw.get("links") or []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Public protocol surface for agentirc — verbs, numerics, and
|
|
1
|
+
"""Public protocol surface for agentirc — verbs, numerics, tags, and the bot extension API.
|
|
2
2
|
|
|
3
|
-
Semver-tracked module.
|
|
3
|
+
Semver-tracked module. Five categories of public symbols live here:
|
|
4
4
|
|
|
5
5
|
1. **Verb names** — IRC command verbs as bare uppercase tokens. Mostly
|
|
6
6
|
RFC 2812 (PRIVMSG, JOIN, QUIT, ...), plus agentirc skill verbs
|
|
@@ -14,6 +14,16 @@ Semver-tracked module. Three categories of constants live here:
|
|
|
14
14
|
consumers don't reach into the underscore namespace.
|
|
15
15
|
3. **Message tag names** — IRCv3 tag keys for traceparent/tracestate
|
|
16
16
|
and agentirc-specific event tags.
|
|
17
|
+
4. **Event types and the Event dataclass** — :class:`EventType`
|
|
18
|
+
(a :class:`enum.StrEnum` of 20 dotted-lowercase wire strings) and
|
|
19
|
+
:class:`Event` (a frozen-shape dataclass). Plus 20 ``EVENT_TYPE_*``
|
|
20
|
+
per-type string constants for callers that prefer bare strings over
|
|
21
|
+
enum-coercion at JSON boundaries. Added in 9.5.0a1 as part of the
|
|
22
|
+
bot extension API.
|
|
23
|
+
5. **Bot extension verbs and capability** — ``EVENTSUB``, ``EVENTUNSUB``,
|
|
24
|
+
``EVENT``, ``EVENTERR``, ``EVENTPUB`` verb constants and
|
|
25
|
+
``BOT_CAP = "agentirc.io/bot"``. Reserved in 9.5.0a1; daemon
|
|
26
|
+
behavior wires up in 9.5.0a3 / 9.5.0 final.
|
|
17
27
|
|
|
18
28
|
Existing call sites under ``agentirc.ircd``, ``agentirc.server_link``
|
|
19
29
|
and the skills modules still use inline string literals. Migrating them
|
|
@@ -31,6 +41,11 @@ clients and federation. They need a coordinated cross-repo bump.
|
|
|
31
41
|
|
|
32
42
|
from __future__ import annotations
|
|
33
43
|
|
|
44
|
+
import time
|
|
45
|
+
from dataclasses import dataclass, field
|
|
46
|
+
from enum import StrEnum
|
|
47
|
+
from typing import Any
|
|
48
|
+
|
|
34
49
|
# ---------------------------------------------------------------------------
|
|
35
50
|
# Numeric reply codes (re-exported from the internal module)
|
|
36
51
|
# ---------------------------------------------------------------------------
|
|
@@ -164,6 +179,90 @@ ROOMETAEND = "ROOMETAEND" # SIC: typo preserved for wire compat (ROOMMETAEND ta
|
|
|
164
179
|
ROOMETASET = "ROOMETASET" # SIC: typo preserved for wire compat (ROOMMETASET target)
|
|
165
180
|
|
|
166
181
|
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Bot extension API (9.5.0)
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# Public Event dataclass + EventType enum, per-type string constants, the
|
|
186
|
+
# EVENTSUB / EVENTUNSUB / EVENT / EVENTERR / EVENTPUB verb names, and the
|
|
187
|
+
# bot-CAP token. See docs/superpowers/specs/2026-05-01-bot-extension-api-design.md
|
|
188
|
+
# for the wire format and verb syntax. Behavior wiring lands in 9.5.0a2/a3;
|
|
189
|
+
# 9.5.0a1 ships these symbols only.
|
|
190
|
+
|
|
191
|
+
# `EventType` is `StrEnum` so `EventType.JOIN == "user.join"` is True at JSON
|
|
192
|
+
# boundaries. Adding a new member is a minor bump; renaming or removing one
|
|
193
|
+
# is a major bump.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class EventType(StrEnum):
|
|
197
|
+
MESSAGE = "message"
|
|
198
|
+
JOIN = "user.join"
|
|
199
|
+
PART = "user.part"
|
|
200
|
+
QUIT = "user.quit"
|
|
201
|
+
TOPIC = "topic"
|
|
202
|
+
ROOMMETA = "room.meta"
|
|
203
|
+
TAGS = "tags.update"
|
|
204
|
+
ROOMARCHIVE = "room.archive"
|
|
205
|
+
THREAD_CREATE = "thread.create"
|
|
206
|
+
THREAD_MESSAGE = "thread.message"
|
|
207
|
+
THREAD_CLOSE = "thread.close"
|
|
208
|
+
AGENT_CONNECT = "agent.connect"
|
|
209
|
+
AGENT_DISCONNECT = "agent.disconnect"
|
|
210
|
+
CONSOLE_OPEN = "console.open"
|
|
211
|
+
CONSOLE_CLOSE = "console.close"
|
|
212
|
+
SERVER_WAKE = "server.wake"
|
|
213
|
+
SERVER_SLEEP = "server.sleep"
|
|
214
|
+
SERVER_LINK = "server.link"
|
|
215
|
+
SERVER_UNLINK = "server.unlink"
|
|
216
|
+
ROOM_CREATE = "room.create"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class Event:
|
|
221
|
+
# `type` is widened to `EventType | str` so federation peers can deliver
|
|
222
|
+
# event types this version doesn't recognise without raising. Subscribers
|
|
223
|
+
# must tolerate unknown types (forward-compat).
|
|
224
|
+
type: EventType | str
|
|
225
|
+
channel: str | None
|
|
226
|
+
nick: str
|
|
227
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
228
|
+
timestamp: float = field(default_factory=time.time)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Per-type string constants — parallel to `EventType` for callers that prefer
|
|
232
|
+
# bare strings (e.g. comparing JSON-decoded `type` field without enum-coercing).
|
|
233
|
+
EVENT_TYPE_MESSAGE = "message"
|
|
234
|
+
EVENT_TYPE_USER_JOIN = "user.join"
|
|
235
|
+
EVENT_TYPE_USER_PART = "user.part"
|
|
236
|
+
EVENT_TYPE_USER_QUIT = "user.quit"
|
|
237
|
+
EVENT_TYPE_TOPIC = "topic"
|
|
238
|
+
EVENT_TYPE_ROOM_META = "room.meta"
|
|
239
|
+
EVENT_TYPE_TAGS_UPDATE = "tags.update"
|
|
240
|
+
EVENT_TYPE_ROOM_ARCHIVE = "room.archive"
|
|
241
|
+
EVENT_TYPE_THREAD_CREATE = "thread.create"
|
|
242
|
+
EVENT_TYPE_THREAD_MESSAGE = "thread.message"
|
|
243
|
+
EVENT_TYPE_THREAD_CLOSE = "thread.close"
|
|
244
|
+
EVENT_TYPE_AGENT_CONNECT = "agent.connect"
|
|
245
|
+
EVENT_TYPE_AGENT_DISCONNECT = "agent.disconnect"
|
|
246
|
+
EVENT_TYPE_CONSOLE_OPEN = "console.open"
|
|
247
|
+
EVENT_TYPE_CONSOLE_CLOSE = "console.close"
|
|
248
|
+
EVENT_TYPE_SERVER_WAKE = "server.wake"
|
|
249
|
+
EVENT_TYPE_SERVER_SLEEP = "server.sleep"
|
|
250
|
+
EVENT_TYPE_SERVER_LINK = "server.link"
|
|
251
|
+
EVENT_TYPE_SERVER_UNLINK = "server.unlink"
|
|
252
|
+
EVENT_TYPE_ROOM_CREATE = "room.create"
|
|
253
|
+
|
|
254
|
+
# Bot extension verbs.
|
|
255
|
+
EVENTSUB = "EVENTSUB"
|
|
256
|
+
EVENTUNSUB = "EVENTUNSUB"
|
|
257
|
+
EVENT = "EVENT"
|
|
258
|
+
EVENTERR = "EVENTERR"
|
|
259
|
+
EVENTPUB = "EVENTPUB"
|
|
260
|
+
|
|
261
|
+
# Bot-CAP token. Vendored namespace per IRCv3 conventions, prevents collision
|
|
262
|
+
# with hypothetical bare-`bot` caps from non-agentirc IRC servers.
|
|
263
|
+
BOT_CAP = "agentirc.io/bot"
|
|
264
|
+
|
|
265
|
+
|
|
167
266
|
__all__ = [
|
|
168
267
|
# Numerics
|
|
169
268
|
"ERR_ALREADYREGISTRED",
|
|
@@ -256,4 +355,33 @@ __all__ = [
|
|
|
256
355
|
"STAGS",
|
|
257
356
|
"STHREAD",
|
|
258
357
|
"STOPIC",
|
|
358
|
+
# Bot extension API (9.5.0)
|
|
359
|
+
"BOT_CAP",
|
|
360
|
+
"EVENT",
|
|
361
|
+
"EVENTERR",
|
|
362
|
+
"EVENTPUB",
|
|
363
|
+
"EVENTSUB",
|
|
364
|
+
"EVENTUNSUB",
|
|
365
|
+
"Event",
|
|
366
|
+
"EventType",
|
|
367
|
+
"EVENT_TYPE_AGENT_CONNECT",
|
|
368
|
+
"EVENT_TYPE_AGENT_DISCONNECT",
|
|
369
|
+
"EVENT_TYPE_CONSOLE_CLOSE",
|
|
370
|
+
"EVENT_TYPE_CONSOLE_OPEN",
|
|
371
|
+
"EVENT_TYPE_MESSAGE",
|
|
372
|
+
"EVENT_TYPE_ROOM_ARCHIVE",
|
|
373
|
+
"EVENT_TYPE_ROOM_CREATE",
|
|
374
|
+
"EVENT_TYPE_ROOM_META",
|
|
375
|
+
"EVENT_TYPE_SERVER_LINK",
|
|
376
|
+
"EVENT_TYPE_SERVER_SLEEP",
|
|
377
|
+
"EVENT_TYPE_SERVER_UNLINK",
|
|
378
|
+
"EVENT_TYPE_SERVER_WAKE",
|
|
379
|
+
"EVENT_TYPE_TAGS_UPDATE",
|
|
380
|
+
"EVENT_TYPE_THREAD_CLOSE",
|
|
381
|
+
"EVENT_TYPE_THREAD_CREATE",
|
|
382
|
+
"EVENT_TYPE_THREAD_MESSAGE",
|
|
383
|
+
"EVENT_TYPE_TOPIC",
|
|
384
|
+
"EVENT_TYPE_USER_JOIN",
|
|
385
|
+
"EVENT_TYPE_USER_PART",
|
|
386
|
+
"EVENT_TYPE_USER_QUIT",
|
|
259
387
|
]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
# Event and EventType moved to agentirc.protocol in 9.5.0a1 as part of the
|
|
6
|
+
# bot extension API public surface. This module keeps re-exporting them so
|
|
7
|
+
# internal call sites and any pre-9.5 vendored consumers keep working; the
|
|
8
|
+
# re-export is removed in 9.6.0 once Phase A2 confirms no consumer relies on
|
|
9
|
+
# this path.
|
|
10
|
+
from agentirc.protocol import Event, EventType
|
|
11
|
+
|
|
12
|
+
__all__ = ["Event", "EventType", "Skill"]
|
|
7
13
|
|
|
8
14
|
if TYPE_CHECKING:
|
|
9
15
|
from agentirc.client import Client
|
|
@@ -11,39 +17,6 @@ if TYPE_CHECKING:
|
|
|
11
17
|
from agentirc._internal.protocol.message import Message
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
class EventType(Enum):
|
|
15
|
-
MESSAGE = "message"
|
|
16
|
-
JOIN = "user.join"
|
|
17
|
-
PART = "user.part"
|
|
18
|
-
QUIT = "user.quit"
|
|
19
|
-
TOPIC = "topic"
|
|
20
|
-
ROOMMETA = "room.meta"
|
|
21
|
-
TAGS = "tags.update"
|
|
22
|
-
ROOMARCHIVE = "room.archive"
|
|
23
|
-
THREAD_CREATE = "thread.create"
|
|
24
|
-
THREAD_MESSAGE = "thread.message"
|
|
25
|
-
THREAD_CLOSE = "thread.close"
|
|
26
|
-
# Lifecycle + link events introduced by mesh-events feature.
|
|
27
|
-
AGENT_CONNECT = "agent.connect"
|
|
28
|
-
AGENT_DISCONNECT = "agent.disconnect"
|
|
29
|
-
CONSOLE_OPEN = "console.open"
|
|
30
|
-
CONSOLE_CLOSE = "console.close"
|
|
31
|
-
SERVER_WAKE = "server.wake"
|
|
32
|
-
SERVER_SLEEP = "server.sleep"
|
|
33
|
-
SERVER_LINK = "server.link"
|
|
34
|
-
SERVER_UNLINK = "server.unlink"
|
|
35
|
-
ROOM_CREATE = "room.create"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@dataclass
|
|
39
|
-
class Event:
|
|
40
|
-
type: EventType
|
|
41
|
-
channel: str | None
|
|
42
|
-
nick: str
|
|
43
|
-
data: dict[str, Any] = field(default_factory=dict)
|
|
44
|
-
timestamp: float = field(default_factory=time.time)
|
|
45
|
-
|
|
46
|
-
|
|
47
20
|
class Skill:
|
|
48
21
|
name: str = ""
|
|
49
22
|
commands: set[str] = set()
|
|
@@ -12,6 +12,33 @@ import only from these three modules.
|
|
|
12
12
|
| [`agentirc.cli`](#agentirccli) | `main()`, `dispatch(argv) -> int` | Public, semver-tracked |
|
|
13
13
|
| [`agentirc.protocol`](#agentircprotocol) | Verb constants, numeric reply codes, IRCv3/extension tag names | Public, semver-tracked |
|
|
14
14
|
|
|
15
|
+
> **Bot extension API — phased rollout:**
|
|
16
|
+
>
|
|
17
|
+
> - **9.5.0a1 (current alpha — declarations slice):** `agentirc.protocol`
|
|
18
|
+
> exports the `Event` dataclass, the `EventType` enum (now `StrEnum`),
|
|
19
|
+
> 20 per-type `EVENT_TYPE_*` string constants, the
|
|
20
|
+
> `EVENTSUB`/`EVENTUNSUB`/`EVENT`/`EVENTERR`/`EVENTPUB` verb constants,
|
|
21
|
+
> and the `BOT_CAP = "agentirc.io/bot"` capability identifier.
|
|
22
|
+
> `ServerConfig` gains the `event_subscription_queue_max: int = 1024`
|
|
23
|
+
> field. **The symbols are importable; the daemon does not yet handle
|
|
24
|
+
> the verbs and does not advertise `BOT_CAP`** — calling them is a
|
|
25
|
+
> no-op until the behavior slices land.
|
|
26
|
+
> - **9.5.0a2 (planned):** wire-format envelope refactor —
|
|
27
|
+
> `_build_event_payload`/`_encode_event_data` emit the 5-field envelope
|
|
28
|
+
> `{type, channel, nick, data, timestamp}`; federated `SEVENT` shifts
|
|
29
|
+
> to the new shape. Internal change; no new public symbols.
|
|
30
|
+
> - **9.5.0a3 (planned):** bot-CAP behavior, `EVENTSUB`/`EVENTUNSUB`
|
|
31
|
+
> handlers, the in-memory `SubscriptionRegistry`, and `EVENTPUB`
|
|
32
|
+
> handler. Daemon starts advertising `BOT_CAP` in `CAP LS` output.
|
|
33
|
+
> - **9.5.0 (final):** `webhook_port` no longer bound; `cli.md` /
|
|
34
|
+
> `deployment.md` updated; this block flips from "phased rollout" to
|
|
35
|
+
> "current," and the version-history table picks up a 9.5.0 row.
|
|
36
|
+
>
|
|
37
|
+
> Wire format and verb syntax are specified in
|
|
38
|
+
> [`docs/superpowers/specs/2026-05-01-bot-extension-api-design.md`](superpowers/specs/2026-05-01-bot-extension-api-design.md);
|
|
39
|
+
> a quick reference for bot authors is at [`docs/extension-api.md`](extension-api.md).
|
|
40
|
+
> Tracking issue: [agentculture/agentirc#15](https://github.com/agentculture/agentirc/issues/15).
|
|
41
|
+
|
|
15
42
|
## Semver contract
|
|
16
43
|
|
|
17
44
|
Following [SemVer 2.0](https://semver.org/):
|
|
@@ -179,6 +206,35 @@ Re-exported from `agentirc._internal.protocol.replies`. About 33 names:
|
|
|
179
206
|
Re-exported from `agentirc._internal.telemetry.context`:
|
|
180
207
|
`TRACEPARENT_TAG`, `TRACESTATE_TAG`, `EVENT_TAG_TYPE`, `EVENT_TAG_DATA`.
|
|
181
208
|
|
|
209
|
+
### Reserved for 9.5.0: bot extension surface
|
|
210
|
+
|
|
211
|
+
These additions are **specified but not yet implemented**. They will land
|
|
212
|
+
together as a single minor bump in 9.5.0 — see the design spec at
|
|
213
|
+
[`docs/superpowers/specs/2026-05-01-bot-extension-api-design.md`](superpowers/specs/2026-05-01-bot-extension-api-design.md)
|
|
214
|
+
for rationale, federation behavior, and acceptance criteria, and
|
|
215
|
+
[`docs/extension-api.md`](extension-api.md) for the bot-author quick
|
|
216
|
+
reference.
|
|
217
|
+
|
|
218
|
+
- **Event verbs:** `EVENTSUB`, `EVENTUNSUB`, `EVENT`, `EVENTERR`, `EVENTPUB`. Subscribers stream events with filter syntax (`type=`/`channel=`/`nick=` AND-ed globs); `EVENTPUB` lets a bot emit its own typed events back into the stream (server-side validation of `type` against `EVENT_TYPE_RE`; `nick` and `timestamp` derived server-side, not trusted from the client).
|
|
219
|
+
- **Bot capability:** `BOT_CAP = "agentirc.io/bot"`. When negotiated via
|
|
220
|
+
the existing CAP REQ/ACK flow, the connection is treated as a bot:
|
|
221
|
+
silent JOIN/PART/QUIT broadcasts, no auto-op on channel creation,
|
|
222
|
+
`+` prefix in NAMES output, `B` flag in WHO output, authorized to
|
|
223
|
+
issue `EVENTSUB`.
|
|
224
|
+
- **Event dataclass and enum:** `Event` and `EventType` (currently
|
|
225
|
+
internal in `agentirc.skill`). Promoted to public for Python consumers.
|
|
226
|
+
Wire format — not the Python class names — is the contract; non-Python
|
|
227
|
+
bots pin against the JSON shape documented in `extension-api.md`.
|
|
228
|
+
- **Per-type string constants:** `EVENT_TYPE_MESSAGE`,
|
|
229
|
+
`EVENT_TYPE_USER_JOIN`, …, one per type-string in the canonical
|
|
230
|
+
vocabulary. Convenience for callers that prefer non-enum-aware
|
|
231
|
+
constants.
|
|
232
|
+
|
|
233
|
+
The `ServerConfig` additions (one new field
|
|
234
|
+
`event_subscription_queue_max: int = 1024`) and the `webhook_port`
|
|
235
|
+
binding-removal are described under
|
|
236
|
+
[`agentirc.config`](#agentircconfig) once 9.5.0 lands.
|
|
237
|
+
|
|
182
238
|
### Wire-format quirks (preserved verbatim)
|
|
183
239
|
|
|
184
240
|
Four known wire-format issues are **preserved** rather than fixed,
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Extension API for out-of-process bots
|
|
2
|
+
|
|
3
|
+
**Status:** Proposed for 9.5.0. The wire format and verbs described here are
|
|
4
|
+
**reserved** in `docs/api-stability.md` but **not yet implemented**. Track the
|
|
5
|
+
landing PR via [agentculture/agentirc#15](https://github.com/agentculture/agentirc/issues/15).
|
|
6
|
+
The full design lives at
|
|
7
|
+
[`docs/superpowers/specs/2026-05-01-bot-extension-api-design.md`](superpowers/specs/2026-05-01-bot-extension-api-design.md).
|
|
8
|
+
|
|
9
|
+
This page is a quick reference for bot authors. For rationale, semver
|
|
10
|
+
implications, and federation behavior, read the design spec.
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
A "bot" is any TCP client that negotiates the `agentirc.io/bot` capability.
|
|
15
|
+
Once negotiated, the client:
|
|
16
|
+
|
|
17
|
+
- Joins channels silently (no JOIN broadcast to other channel members).
|
|
18
|
+
- Never gets auto-op on a newly created channel.
|
|
19
|
+
- Appears in `NAMES` prefixed with `+` and in `WHO` with a `B` flag.
|
|
20
|
+
- May issue `EVENTSUB` to stream events and `EVENTPUB` to emit custom events.
|
|
21
|
+
|
|
22
|
+
Everything else (`PRIVMSG`, `NOTICE`, mention notifications, channel ops,
|
|
23
|
+
threads, rooms) works exactly the same as for a human client.
|
|
24
|
+
|
|
25
|
+
### Porting note: JOIN before PRIVMSG
|
|
26
|
+
|
|
27
|
+
`PRIVMSG <#channel>` requires channel membership. Culture's in-process
|
|
28
|
+
`VirtualClient.broadcast_to_channel` lets a bot post to a channel it
|
|
29
|
+
hasn't joined; TCP-connected bots get no such shortcut. The canonical
|
|
30
|
+
pattern under bot CAP is **JOIN, PRIVMSG, then optionally PART** — all
|
|
31
|
+
silent (no broadcasts to other members). For event-triggered bots that
|
|
32
|
+
post into channels they discover at runtime (e.g. a welcome bot reacting
|
|
33
|
+
to `user.join`), the JOIN-broadcast-PART sequence is cheap because each
|
|
34
|
+
step is silent. Decide per bot whether to stay joined for low-latency
|
|
35
|
+
posting or PART after each emission to avoid occupying a member slot.
|
|
36
|
+
|
|
37
|
+
## Connecting
|
|
38
|
+
|
|
39
|
+
Standard IRCv3 capability handshake:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
C: CAP LS
|
|
43
|
+
S: :server CAP * LS :message-tags agentirc.io/bot
|
|
44
|
+
C: CAP REQ :agentirc.io/bot message-tags
|
|
45
|
+
S: :server CAP * ACK :agentirc.io/bot message-tags
|
|
46
|
+
C: CAP END
|
|
47
|
+
C: NICK mybot
|
|
48
|
+
C: USER mybot 0 * :My Bot
|
|
49
|
+
S: :server 001 mybot :Welcome to agentirc, mybot
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Most bots will request both `agentirc.io/bot` (silent presence + EVENTSUB
|
|
53
|
+
authorization) and `message-tags` (read IRCv3 tags on PRIVMSGs, including the
|
|
54
|
+
`event-data` tag on `#system` PRIVMSGs).
|
|
55
|
+
|
|
56
|
+
## Subscribing to events
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
EVENTSUB <sub-id> [type=<glob>] [channel=<name>] [nick=<glob>]
|
|
60
|
+
EVENTUNSUB <sub-id>
|
|
61
|
+
EVENT <sub-id> <type> <channel-or-*> <nick> :<base64-json-payload>
|
|
62
|
+
EVENTERR <sub-id> :<reason>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `<sub-id>` is a client-chosen ASCII token, 1–32 chars from
|
|
66
|
+
`[A-Za-z0-9._:-]`. Pick something memorable for debugging.
|
|
67
|
+
- All three filters are optional. Multiple filters are AND-ed. Missing filter
|
|
68
|
+
means match-all.
|
|
69
|
+
- `type=` and `nick=` accept `*` glob wildcards (e.g. `type=user.*`).
|
|
70
|
+
- `channel=` accepts an exact channel name or `*`. Empty value matches only
|
|
71
|
+
events with `channel: null`.
|
|
72
|
+
- Multiple concurrent subscriptions per client are allowed; each gets a
|
|
73
|
+
distinct `sub-id`.
|
|
74
|
+
- `EVENTSUB` requires the `agentirc.io/bot` capability. Without it,
|
|
75
|
+
the server replies `EVENTERR <sub-id> :bot-capability-required`.
|
|
76
|
+
- Subscriptions die when the client disconnects.
|
|
77
|
+
|
|
78
|
+
### Example
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
C: EVENTSUB joins type=user.join channel=#room
|
|
82
|
+
S: :server EVENT joins user.join #room alice :eyJ0eXBlIjogInVzZXIuam9pbiIsIC4uLn0=
|
|
83
|
+
S: :server EVENT joins user.join #room bob :eyJ0eXBlIjogInVzZXIuam9pbiIsIC4uLn0=
|
|
84
|
+
C: EVENTUNSUB joins
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `EVENT` line carries the canonical event payload, base64-JSON-encoded,
|
|
88
|
+
in the trailing parameter. Decode with any JSON parser.
|
|
89
|
+
|
|
90
|
+
## Event JSON shape
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"type": "user.join",
|
|
95
|
+
"channel": "#room",
|
|
96
|
+
"nick": "alice",
|
|
97
|
+
"data": {"text": "hi"},
|
|
98
|
+
"timestamp": 1714568400.123
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Field | Type | Required | Description |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `type` | string | yes | One of the canonical event-type strings (see vocabulary below). Unknown types are tolerated — forward-compat. |
|
|
105
|
+
| `channel` | string-or-null | yes | Channel name for channel-scoped events, `null` otherwise. |
|
|
106
|
+
| `nick` | string | yes | Actor's nickname, or empty string for purely-server-emitted events. |
|
|
107
|
+
| `data` | object | yes | Type-specific payload. Always an object. Keys starting with `_` are reserved metadata (e.g. `_origin` is the originating server name across federation links). |
|
|
108
|
+
| `timestamp` | number | yes | Unix epoch seconds with sub-second precision. |
|
|
109
|
+
|
|
110
|
+
JSON encoding is canonical: keys sorted lexicographically, separators `","`
|
|
111
|
+
and `":"` (no spaces), UTF-8.
|
|
112
|
+
|
|
113
|
+
## Event-type vocabulary
|
|
114
|
+
|
|
115
|
+
| Type string | Channel-scoped | Description |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `message` | yes | `PRIVMSG` to a channel. |
|
|
118
|
+
| `user.join` | yes | A user joined a channel. |
|
|
119
|
+
| `user.part` | yes | A user left a channel. |
|
|
120
|
+
| `user.quit` | no | A user quit the server. |
|
|
121
|
+
| `topic` | yes | Channel topic changed. |
|
|
122
|
+
| `room.create` | yes | Room created via `ROOMCREATE` skill. |
|
|
123
|
+
| `room.archive` | yes | Room archived via `ROOMARCHIVE` skill. |
|
|
124
|
+
| `room.meta` | yes | Room metadata updated via `ROOMMETA` skill. |
|
|
125
|
+
| `tags.update` | yes | User tags changed via `TAGS` skill. |
|
|
126
|
+
| `thread.create` | yes | Thread created. |
|
|
127
|
+
| `thread.message` | yes | Message posted to a thread. |
|
|
128
|
+
| `thread.close` | yes | Thread closed. |
|
|
129
|
+
| `agent.connect` | no | An agent (CAP-bot) finished registration. |
|
|
130
|
+
| `agent.disconnect` | no | An agent disconnected. |
|
|
131
|
+
| `console.open` | no | Console session opened. |
|
|
132
|
+
| `console.close` | no | Console session closed. |
|
|
133
|
+
| `server.wake` | no | This server finished startup. |
|
|
134
|
+
| `server.sleep` | no | This server is shutting down. |
|
|
135
|
+
| `server.link` | no | A federation peer linked. |
|
|
136
|
+
| `server.unlink` | no | A federation peer link dropped. |
|
|
137
|
+
|
|
138
|
+
Adding new type strings is a minor bump. Renaming or removing a type string
|
|
139
|
+
is a major bump. Bot code must tolerate unknown types and forward-skip them.
|
|
140
|
+
|
|
141
|
+
### Already-delivered events
|
|
142
|
+
|
|
143
|
+
`message`, `topic`, `thread.create`, `thread.message`, and `thread.close` are
|
|
144
|
+
already delivered to channel members via the normal IRC path (`PRIVMSG`,
|
|
145
|
+
`TOPIC`) or have dedicated storage (threads). Subscribers will see them once
|
|
146
|
+
via `EVENTSUB`; they are not double-delivered as `PRIVMSG`s to `#system`
|
|
147
|
+
carrying the `@event=<type>` and `@event-data=<base64-json>` IRCv3 tags.
|
|
148
|
+
|
|
149
|
+
## Backpressure
|
|
150
|
+
|
|
151
|
+
Each subscription owns a bounded send queue (default 1024 events, configurable
|
|
152
|
+
server-side via `ServerConfig.event_subscription_queue_max`).
|
|
153
|
+
|
|
154
|
+
When the queue overflows:
|
|
155
|
+
|
|
156
|
+
1. Server sends `EVENTERR <sub-id> :backpressure-overflow`.
|
|
157
|
+
2. The subscription is removed.
|
|
158
|
+
3. The client connection itself stays open.
|
|
159
|
+
4. To recover: re-subscribe with the same or a fresh `<sub-id>`, then issue
|
|
160
|
+
`BACKFILL` to catch up on missed history.
|
|
161
|
+
|
|
162
|
+
Bots should aim to drain `EVENT` lines as fast as they arrive. If a bot
|
|
163
|
+
genuinely cannot keep up, the right response is to widen the filter (subscribe
|
|
164
|
+
to fewer types/channels), not to ignore overflow.
|
|
165
|
+
|
|
166
|
+
## Emitting custom events (`EVENTPUB`)
|
|
167
|
+
|
|
168
|
+
Bots can emit their own typed events back into the stream — useful for
|
|
169
|
+
chained-bot patterns where one bot's emission triggers another bot's logic
|
|
170
|
+
(e.g. a welcome bot fires `welcome.greeted`, an onboarding logger
|
|
171
|
+
subscribes to it).
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
EVENTPUB <type> <channel-or-*> :<base64-json-data>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- `<type>` — must match `^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)+$` (at
|
|
178
|
+
least one dot segment). Single-segment names like `message` or `topic`
|
|
179
|
+
are reserved for the built-in vocabulary and rejected with
|
|
180
|
+
`EVENTERR <type> :invalid-type`.
|
|
181
|
+
- `<channel-or-*>` — exact channel name or `*` for non-channel-scoped.
|
|
182
|
+
- `:<base64-json-data>` — type-specific payload; must be a JSON object.
|
|
183
|
+
|
|
184
|
+
The server fills in `nick` (from your connection — bots cannot spoof it)
|
|
185
|
+
and `timestamp` (server-side, so federation peers see consistent values),
|
|
186
|
+
constructs the full `Event`, and feeds it into the same emit pipeline that
|
|
187
|
+
handles built-in events. Subscribers see it as an `EVENT` line; peers
|
|
188
|
+
across federation receive a `SEVENT` relay.
|
|
189
|
+
|
|
190
|
+
Reflexive: a bot subscribed to a filter that matches its own emission
|
|
191
|
+
receives the `EVENT` line for it. Filter on `nick` if you want to ignore
|
|
192
|
+
self-emissions.
|
|
193
|
+
|
|
194
|
+
`EVENTPUB` requires the `agentirc.io/bot` capability (same gate as
|
|
195
|
+
`EVENTSUB`).
|
|
196
|
+
|
|
197
|
+
## Mentioning, DMs, ops
|
|
198
|
+
|
|
199
|
+
Unchanged for bot-CAP clients:
|
|
200
|
+
|
|
201
|
+
- **Mentions** — when another user `PRIVMSG`s a channel containing `@yourbot`,
|
|
202
|
+
the server sends a `NOTICE` to your bot with the mention context.
|
|
203
|
+
- **DMs** — `PRIVMSG mybot :hi` from any other user reaches the bot
|
|
204
|
+
normally.
|
|
205
|
+
- **Channel ops** — bots are not granted ops on auto-op (the first non-bot
|
|
206
|
+
human in a new channel becomes op). A channel operator may explicitly grant
|
|
207
|
+
ops to a bot via `MODE`.
|
|
208
|
+
|
|
209
|
+
## Identifying yourself in NAMES / WHO
|
|
210
|
+
|
|
211
|
+
Bots appear in `NAMES` prefixed with `+`:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
:server 353 mynick = #room :alice @bob +mybot
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
And in `WHO` with a `B` flag:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
:server 352 mynick #room mybot bothost server mybot HB :0 My Bot
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Both flags are derived from the negotiated CAP at output time. They cannot
|
|
224
|
+
be set or unset by `MODE`. Human IRC clients that filter on these flags will
|
|
225
|
+
hide bots from presence lists.
|
|
226
|
+
|
|
227
|
+
## What the server does *not* expose
|
|
228
|
+
|
|
229
|
+
- **No bot manager.** `agentirc` does not host or supervise bot processes.
|
|
230
|
+
Run your bot wherever you like; it is just a TCP client.
|
|
231
|
+
- **No HTTP webhook listener.** As of 9.5.0, `agentirc` does not bind
|
|
232
|
+
`webhook_port`. The field stays in `ServerConfig` for backward
|
|
233
|
+
compatibility, but webhook→bot dispatch is the consumer's responsibility.
|
|
234
|
+
See [`deployment.md`](deployment.md) for details.
|
|
235
|
+
- **No SASL or token auth on bot CAP.** Bots authenticate the same way
|
|
236
|
+
human clients do, using whatever client authentication the server
|
|
237
|
+
currently supports. Per-bot ACLs are a future issue.
|
|
238
|
+
|
|
239
|
+
## Reference
|
|
240
|
+
|
|
241
|
+
- Full design: [`docs/superpowers/specs/2026-05-01-bot-extension-api-design.md`](superpowers/specs/2026-05-01-bot-extension-api-design.md)
|
|
242
|
+
- Public-API contract: [`docs/api-stability.md`](api-stability.md)
|
|
243
|
+
- Tracking issue: [agentculture/agentirc#15](https://github.com/agentculture/agentirc/issues/15)
|