* fix(channels): bundled channels reject the documented responsePrefix override
* fix(feishu): pass channel and account to the reply prefix resolver
The ordinary reply dispatcher built the prefix context without channel or
accountId, so channels.feishu.responsePrefix and its account override fell
through to the global value. The comment dispatcher already passed them.
* fix(channels): accept twitch's root responsePrefix and type ClickClack's
Twitch validates as a two-branch union with both branches closed, so
responsePrefix declared only on the account shape left
channels.twitch.responsePrefix rejected whenever accounts is present. Also
declares responsePrefix on ClickClackAccountConfig so typed callers can express
what its schema already accepts.
* fix(clickclack): apply the configured responsePrefix in model mode
Model mode sends the completion straight to ClickClack instead of going through
the agent reply pipeline, so the documented responsePrefix parsed and then did
nothing. Resolve it through the public reply-pipeline seam so template
variables like {model} render from the completion's own provider and model.
* fix(clickclack): do not double the response prefix in model mode
systemPrompt is operator-owned, so a model can be instructed to emit the
configured prefix itself and the unconditional concatenation then sent it
twice. Match the shared reply normalizer's startsWith guard.
* test(clickclack): resolve inbound access through the real resolver
Main now requires access.channelIngress, which the hand-built fixture did not
provide, so the model-mode messages never dispatched. Drop the fixture and let
handleClickClackInbound resolve access from a DM-shaped message, matching the
sibling bot-loop tests.
* test(channels): reject a key missing from any composed branch
A union alternative is a configuration mode an operator can pick, so a key
present in only one mode is unusable in the other even though the union still
validates. Treat any rejecting alternative as a rejection, and walk allOf when
collecting account schemas.
* fix(channels): offer responsePrefix only where the reply path applies it
Applying the prefix to an outbound reply is per-channel wiring, not a shared
step. Buzz, Nostr, QA Channel, Raft, Reef and SMS never read it, so accepting
the key there validated a setting their delivery path ignores. Anchor the
contract to the channels that consume the prefix.
* fix(channels): complete shared response prefix delivery
Co-authored-by: ayaangazali <ayaangazali.work@gmail.com>
* docs(config): refresh response prefix baseline
* test(tlon): use canonical prefix fixture helpers
* test(channels): prove Twitch reply prefix delivery
Exercise schema admission and the real shared reply dispatcher at the Twitch native send boundary, replacing redundant schema-only coverage. Keep Tlon fixtures on canonical imports and recording options.
* docs(channels): clarify explicit message prefix behavior
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
ClickClack OpenClaw channel
Official OpenClaw channel plugin for ClickClack.
Install
openclaw plugins install @openclaw/clickclack
Setup
The recommended setup path uses the one-time command generated by ClickClack:
openclaw channels add clickclack --code 'https://clickclack.example.com/#XXXX-XXXX-XXXX'
Split-origin and path-mounted ClickClack deployments generate an exact
/api/bot-setup-codes/claim#CODE endpoint. OpenClaw validates the versioned
claim response and saves its canonical API base.
For manual token setup:
openclaw channels add clickclack \
--base-url https://clickclack.example.com \
--token ccb_... \
--workspace default
openclaw gateway
Run openclaw onboard for guided setup. The workspace value can be a
wsp_... id, slug, or display name.
For the default account only, --use-env reads CLICKCLACK_BOT_TOKEN; config
storage is the normal setup path.
Public and private endpoints
baseUrl is the public ClickClack URL used in links that people open. Set the
optional apiBaseUrl when the gateway should call ClickClack through a
different server-to-server endpoint:
{
channels: {
clickclack: {
baseUrl: "https://clack.openclaw.ai",
apiBaseUrl: "http://127.0.0.1:8484",
token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
workspace: "default",
},
},
}
This same-host pattern lets the public hostname stay fully protected by an
authentication gateway such as Cloudflare Access while the local OpenClaw
gateway talks directly to ClickClack. REST requests, setup verification, and
the realtime WebSocket use apiBaseUrl; browser-facing discussion links keep
using baseUrl. When apiBaseUrl is unset, it defaults to baseUrl.
Command menus
ClickClack command menus are enabled by default. At gateway startup, the
extension publishes OpenClaw's native commands for composer autocomplete,
labeled with the bot's handle. The bot token must include commands:write;
current bot:write and bot:admin bundles include it.
Set commandMenu: false on an account to disable menu sync. Sync failures do
not prevent the gateway from starting, so older tokens and ClickClack servers
continue to work without a menu.
Discussions
ClickClack can create one managed channel for each OpenClaw session:
The account token needs channels:write, which is included in bot:admin but
not in the normal bot:write setup token. The ClickClack server must also
support and return the managed-channel fields used by this integration.
{
channels: {
clickclack: {
baseUrl: "https://clickclack.example.com",
token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
workspace: "default",
discussions: {
enabled: true,
workspace: "default",
controlUrlBase: "https://team.openclaw.ai",
section: "Sessions",
},
},
},
}
Opening a session discussion creates a public, externally managed channel and
stores its binding in the ClickClack plugin's SQLite state. Session label and
category changes remain reflected in the channel, but session archive, restore,
reset, and deletion never archive or replace it. ClickClack owns channel archive
and restore independently. workspace
defaults to the account workspace, and section defaults to Sessions.
controlUrlBase adds canonical /chat/<agent>/<session-ref> links to the
OpenClaw Control UI, preserving base paths. Main sessions use /chat/<agent>.
ClickClack-managed embed URLs explicitly advertise host-theme support. The Control UI uses that provider-owned capability to apply its full palette before the sidebar first paints and to stream live palette changes without rewriting opaque or signed discussion URLs from other providers.
Enable discussions on exactly one ClickClack account. Multiple enabled discussion accounts are rejected because the session discussion provider does not have an account selector.
Messages in the managed channel run in a stable side session under the same
agent id as the attached main session. The plugin installs a scoped host grant
for sessions_history, session_status, and sessions_send between that side
session and its attached main session, so tools.sessions.visibility can stay
at its safer default tree. A second host-side policy blocks session discovery
and alternate targets; the side-agent prompt is not the authorization boundary.
The agent still needs those three tools in its effective tool allowlist.
The binding separates durable room identity from a replaceable exact-session attachment. Resetting a reusable session key keeps the ClickClack channel, history, URL, and ownership reference while rotating the side-session identity and scoped grant. The previous side session cannot access the new main session. Messages arriving through an inactive, disabled, or retargeted managed binding are dropped instead of falling back to the account's normal channel routing. Released bindings leave a durable revoked-channel marker so delayed realtime events remain fail-closed. Remote ownership is keyed by ClickClack server and channel id, so renaming the local account cannot turn a managed channel into an ordinary one.
Managed-channel ownership references include a durable per-installation id, so
two OpenClaw gateways using the same ClickClack workspace do not adopt each
other's discussion channels. They also include the destination and a durable
binding generation, so an account or workspace round trip cannot re-adopt a
previous channel. Changing or removing controlUrlBase is reflected on the next
lifecycle reconciliation pass.
If a channel-create response is lost, the pending ownership reservation temporarily quarantines otherwise-unbound events in that workspace. The same coarse reconciler then adopts the created channel or clears the ambiguous attempt; a reset cannot duplicate or archive the durable room.
Workspace and account moves release local attachment authority without archiving the old channel. A workspace-scoped replacement token is never tried against the old channel.
The main session gets a read-only discussion tool that pulls the latest
channel messages, including recent thread replies. The pull uses bounded
history and thread-request budgets; its output says when older active threads
may have been omitted. It never posts, archives, renames, or otherwise mutates
the discussion.
Docs
See docs/channels/clickclack.md in the OpenClaw repository, or the published docs at https://docs.openclaw.ai/channels/clickclack.