* refactor(line): replace nine-marker prompt DSL with typed rich messages
Delete the LINE plugin's double-bracket marker language (quick_replies,
location, confirm, buttons, media_player, event, agenda, device,
appletv_remote) and its parser. Portable interactions now flow through the
existing presentation-block seam (renderPresentation, matching Discord and
Feishu); LINE-specific cards ride closed channelData.line schemas mapped to
the existing Flex renderers. Prompt section shrinks to four capability
lines and explicitly de-fangs marker text. Removes the stale
assertion-safety baseline entry for the deleted parser.
Production LOC net -69, tests net -433. Suite: 510/510 green.
* fix(line): declare rich message schema dependency
* fix(line): satisfy rich message type checks
* docs(line): mark card fragments as partial
Reject reserved /healthz webhook configurations before registration, preventing silent Telegram update loss. Warn through Doctor and document recovery.
Co-authored-by: Peter Lee <22994703+xialonglee@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(slack): unify the native progress turn into one streamed message
Native progress mode now streams the whole turn into a single Slack message:
narration flows as markdown_text chunks interleaved with plan/task chunks,
task rows carry file-path details and +N/-N output, the terminal task links
the session via url_source, and the final answer lands through stopStream in
the same message. Media/oversized/error finals keep their normal-delivery
fallbacks.
Deletes the finished-card receipt collapse (the '\u{1F6E0} N tool calls · ⏱ Ns' edit)
outright: the card now stays as its finished self.
Live-verified on a real workspace: exactly one bot message per turn.
* fix(slack): serialize native stream updates and keep append-only rendered text monotonic
Overlapping progress updates (compositor render, narration payload, final)
computed their narration delta before awaiting the network and committed
state only afterwards, so concurrent updates re-appended identical
narration into the streamed message (each status line landed 3x live).
A single ordering chain now makes each update's compute -> append -> commit
atomic.
applyAppendOnlyStreamUpdate also replaced the accumulated rendered text
with the incoming cumulative partial once an appended chunk had diverged
rendered from source, dropping content the sink already displayed; rendered
now only ever extends.
* fix(slack): stop refreshing thread status once a turn has visible output
Slack clears the assistant thread status as soon as the app puts anything
in the thread, and renders its own rotating agent-working row ("Generating
response...", "Finding answers...") for every status write after that -- it
ignores the app-supplied string. The typing keepalive re-set the status
every 3s for up to 60s, so each turn painted a duplicate status row under
the streamed card or progress message.
The status write is now gated on the turn having visible output, which the
dispatcher already tracks (delivered reply, committed preview, or posted
draft message). The first status still fires before any output, so slow
turns keep their indicator, and the typing reaction is tracked separately
so a suppressed status write still cleans up its reaction.
* fix(slack): let the plan card own the status line instead of echoing it
The status headline and plan explanation fed both the streamed narration
markdown and the plan card title, so every headline rendered twice: once as
static text and once in the card that keeps updating it in place. Narration
now carries only authored commentary and reasoning, and a preamble payload
whose text the card title already shows is not streamed again.
* feat(slack): make the native agent card the default progress surface
Slack's native plan/task card was opt-in behind streaming.progress
.nativeTaskCards while the Block Kit session card shipped as the default.
The native surface is the better product on every axis we can measure --
one streamed message instead of three artifacts, live task rows with file
paths and diff counts, and Slack's own agent chrome -- so it becomes the
default and an explicit false selects the Block Kit card instead.
The session link is now emitted only when it can actually work: the
operator set gateway.publicOrigin and left the Control UI enabled.
Installations with no externally reachable Gateway get no link rather than
a dead one.
The progress card still only appears for turns that do real work; the
existing compositor start gate keeps plain question-and-answer turns
card-free.
* fix(slack): finish the final inside a buffered native stream
A short narration leaves the SDK session un-flushed, so `delivered` stays
false until `stop` makes its first network call. Requiring delivery before
finishing in-stream sent the final through normal delivery and then
finalized the stream anyway, producing exactly the second message this path
exists to prevent. Stop-time rejection already falls back via
SlackStreamNotDeliveredError, so a live session is enough.
Addresses the ClawSweeper P1/P2 finding on this PR.
* refactor(slack): collapse duplicate streaming surfaces and drop dead code
Cleanup pass over the progress/streaming neighborhood, all verified unused
by exhaustive reference search:
- Deleted buildSlackProgressStreamStartChunks/UpdateChunks: byte-identical
pass-throughs to the same builder, plus the render-module branch that
chose between them. One exported builder now.
- Collapsed slackStreaming.draftMode, a lossless restatement of the mode it
was derived from, and its outbound mapper; nine comparisons now read the
mode directly. Inbound legacy parsing stays for doctor migration.
- Dropped stopSlackStream's text parameter, the draft stream's stop() member
and onMessageSent hook, a redundant nativeStreaming argument, four dead
members on the progress runtime, and two single-expression wrappers.
- Deduped the native card title, which was computed twice per render.
Production LOC for the whole PR drops from +216 to +114.
* chore(config): regenerate bundled channel metadata for the Slack card default
The generated metadata still carried the old opt-in help text and
default-false description for streaming.progress.nativeTaskCards, so
config UI and diagnostics would publish stale guidance.
* fix(slack): un-export the now-internal legacy draft-mode type
Collapsing draftMode removed the type's only external consumer, so knip
flagged it as an unused export. Doctor migration still parses these legacy
values inbound, so the type stays module-local.
* feat(slack): render live session cards as the default Slack progress mode
Slack streaming.mode default flips partial->progress.
Progress mode renders one live Block Kit session card with a status header, narration, plan, activity, diff stat, and elapsed time; it is edited in place and finalized to success or error with an Open in OpenClaw button when gateway.publicOrigin is set.
Final assistant text always delivers separately.
The shared progress compositor gains a success-only additive per-turn diffStat mirroring the task ledger fold.
resolveGatewayPublicOrigin is exported through the plugin SDK.
The diffs viewer URL falls back to publicOrigin.
The old rich/text progress render fork is deleted.
Native task cards remain unchanged and opt-in.
* chore(config): regenerate bundled channel config metadata
* refactor(slack): keep session card state type internal
* refactor(slack): split session-card and diff-stat owners under lint ceilings
* refactor(channels): reuse diff-stat type from its owner module
Import ChannelProgressDraftDiffStat from progress-draft-diffstat instead of
redeclaring it in the compositor, resolving the all-exports deadcode scan.
* chore(plugin-sdk): regenerate api baselines for channel barrels
Baselines drifted after the rebase reconciled them against main; regenerate to
match the branch's actual channel-message/channel-outbound surface.
* fix(slack): drop a session card that cannot terminalize after final delivery
If the final reply is delivered but the terminal card edit fails, the caller
now clears the stale card instead of leaving it stuck in its Working state
(mirrors the pre-card preview cleanup). Adds a transport-failure regression and
corrects three tests that asserted the prior ignore-the-result behavior.
Documents resolveGatewayPublicOrigin as a dependency-light runtime helper on the
config-contracts SDK subpath, which previously described a type-only surface.
Addresses ClawSweeper P2 (unfinalized card) and P1 (runtime SDK contract).
* fix(slack): suppress default tool messages under the default progress card
resolveChannelStreamingSuppressDefaultToolProgressMessages re-derived the stream
mode from config with an "off" default, unlike its sibling resolvers which take
a caller-resolved mode override. After this branch made progress the Slack
default, a default-config channel turn saw mode "off" and left a stray
"Using tool: X" plain message posting alongside the session card. Thread the
caller-resolved mode through (compositor passes params.mode; Slack dispatch passes
slackStreaming.mode), matching resolveChannelStreamingPreviewToolProgress.
Retarget the progress-session-card delivery-trace golden at an EMPTY Slack config
so it proves the real default path; the regenerated golden is byte-identical,
confirming defaults now yield the clean card sequence (one card post, separate
final text, one terminal update with the Open in OpenClaw button, no stray tool
message). Switch the dispatch delivery-mode mock to the real resolver so the card
tests exercise the true channel default (automatic), not a hand-rolled one.
* chore(plugin-sdk): regenerate api baselines for the streaming mode param
resolveChannelStreamingSuppressDefaultToolProgressMessages gained an optional
mode override; the changed signature reflows the surface hash of every barrel
that re-exports it, so regenerate the affected baselines.
* chore(config): regenerate config baselines
The externalization left the docs inventory seed and the channel page
advertising the retired @openclaw/qqbot npm package; the catalog already
resolves qqbot to @tencent-connect/openclaw-qqbot. Regenerates the
plugin reference and inventory pages from the corrected seed.
* refactor(qqbot): remove bundled extension source
Mechanical deletion half of the #107295 squashed rebase; the catalog
repoint and host integration land in the follow-up commit.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): install plugin from Tencent package
Squashed rebase of #107295 onto current main. Repoints the official
external channel catalog at @tencent-connect/openclaw-qqbot@2.0.1 and
adapts onboarding, doctor migrations, secrets, build guards, and tests.
Documents the known limitation that the external package does not
support structured SecretRef clientSecret values; operators move those
to QQBOT_CLIENT_SECRET or clientSecretFile before upgrading.
Co-authored-by: sliverp <870080352@qq.com>
* fix(doctor): reuse shared hasOwnKey record helper
The rebased QQBot migration carried its own hasOwnKey export, colliding
with the one main now ships in legacy-config-record-shared.ts.
Co-authored-by: sliverp <870080352@qq.com>
* fix(plugins): carry catalog integrity through the update bridge
The externalized-bundled-plugin bridge dropped the official catalog's
expectedIntegrity pin, so bundled-user updates installed the external
npm package without integrity verification. The bridge now carries the
pin for the catalog's exact npm spec and both bridge install calls pass
it through; update-channel spec overrides intentionally skip the pin
since it only covers the pinned version.
Co-authored-by: sliverp <870080352@qq.com>
* chore(plugin-sdk): refresh per-entrypoint API baselines
The QQBot compat export and bundled-type removal shift 26 entrypoint
closure hashes in the new split baseline layout.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): drop helper reintroduced during rebase
Main's coercion consolidation added this file after the deletion
commit's base; its only consumers were the removed qqbot sources.
Co-authored-by: sliverp <870080352@qq.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Keep raw commands, paths, and provider errors out of ordinary chat while preserving explicit raw diagnostics and structured admin history.
Default command progress is status-only; `/verbose full` and `commandText: "raw"` retain diagnostic detail.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(imessage): harden remote Mac transport
Route SSH-backed iMessage actions through JSON-RPC, preserve remote database paths, and stage outbound files on the Messages Mac with bounded cleanup. Keep local action semantics intact while failing closed on ambiguous wrappers and surfacing the remaining imsg v0.13.4 limits.
* fix(imessage): remove test-only exports
Use channels.telegram.groups as the shared default when an account omits groups. Explicit account maps, including {}, remain full replacements. This keeps chat admission and sender restrictions on one policy path and fixes silent multi-account authorization failures.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): prioritize configured commands under menu pressure
* fix(telegram): preserve custom commands under localized menu pressure
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: ayeshakhalid192007-dev <ayeshakhalid192007@gmail.com>
* docs(discord): drop retired ui.components.accentColor and fix heartbeat key name
* docs(discord): remove the retired accent-color example block
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs(slack): drop retired socketMode transport tuning
channels.slack.socketMode is rejected by Slack config validation, pinned by
the 'rejects retired Socket Mode ping/pong transport tuning' schema test, and
the client pong timeout is now a hardcoded 15s constant. The page still told
operators to configure it.
* fix(slack): migrate retired socketMode with doctor instead of hand-editing
The retired object fails schema validation at root and account scope, so the
docs previously left manual removal as the only recovery. Slack already owns
legacyConfigRules and normalizeCompatibilityConfig, so the strip belongs there.
* test(slack): narrow the doctor contract config record for check-test-types
normalizeCompatibilityConfig returns a possibly-absent channel entry, so the
socketMode assertions need expectDefined before indexing.
* docs(slack): describe socketMode retirement by observed behavior
openclaw config validate reports a config carrying the retired object as valid,
so the earlier 'validation rejects it / fails to load' wording was wrong. The
observable facts are that it is not read and that doctor --fix removes it.
* revert(slack): drop the duplicate socketMode doctor migration
Core already strips channels.slack.socketMode at root and account scope in
legacy-config-migrations.runtime.retired.ts via visitChannelEntries, so the
plugin-side rule was duplicate policy. Keeps this PR to the docs correction.
* docs(slack): state what doctor actually cleans up for socketMode
Doctor flags retired layout knobs with a general notice, and --fix removes the
three named fields and drops the object only once empty, so an unknown key
inside it survives.
Prevent stale top-level buttons arguments from silently sending text-only Telegram messages. Point model guidance and docs to typed presentation actions, and reject retired input before delivery.
Separate durable ClickClack room identity from replaceable exact-session attachments. Session archive, reset, deletion, and recreation retain the existing room and history while rotating local routing authority and preserving fail-closed exact-incarnation grants.
Pin ambiguous room recovery to the originating account and credential, and retain prior attachments when persistence fails.
Closes#119300
Co-authored-by: Chisel <chisel@psiclawops.dev>
Recover Telegram forum reaction topics from provider-observed message context before authorization, and drop unknown topics instead of routing them to General. Serialize each forum message with its reaction update to avoid cache races. Thanks @vovapetry.