Session ingest has normalized legacy assistant string content into
[{ type: "text" }] blocks since #98908, which updated the core
cli-runner session-history analog but missed this extension file.
The suite only runs in full local runs and the dispatch-only
plugin-prerelease lane, so push/PR CI stayed green while local
scripts/pr prepare-gates failed on any OS.
refs #99857
* fix(line): truncate outbound altText, location, menu, and code fields on code-point boundaries
* fix(line): use safe truncation for receipt card altText
* fix(line): count rich menu limits by grapheme
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(codex): keep OpenClaw control tools direct
* test(codex): refresh direct-tool prompt snapshots
* fix(codex): keep heartbeat direct only when available
* fix(codex): keep heartbeat tool schema stable
---------
Co-authored-by: Eva <eva@100yen.org>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
* fix(file-transfer): don't inline zero-byte files as image content blocks
file_fetch's remote mime detection falls back to the extension-derived
MIME type when content sniffing finds nothing to sniff (a zero-byte
buffer), so fetching an empty .png/.jpg/.webp/.gif produces
{type: "image", data: "", mimeType: "image/png"}. That payload-less
image block reached the model as an unrecoverable placeholder instead
of the existing "saved at <path>" text fallback used for every other
non-inlined file. Require a non-empty payload before treating a fetch
as an inline image. Refs #98673.
* test(file-transfer): prove empty image fetch fallback
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Telegram no longer sends a generic "Something went wrong" fallback after a
final answer was already delivered and a later dispatch/cleanup step failed.
Failures with only partial or no visible output still send the error fallback
and stay retryable.
Related: #87299Closes#90152
* fix(imessage): recognize bare hex group chat identifiers as chat targets
A 32-char hex iMessage group chat identifier (as returned by `imsg` for
group chats) with no explicit `chat_identifier:` prefix fell through to
E.164 phone-number normalization. normalizeE164 strips non-digit
characters and prepends `+`, turning
`7d5297154d5f436d83dbbdf03fcc8fdd` into `+75297154543683038` -- a
bogus phone number. Cron/announce delivery then reported
`delivered: true` while silently sending to a nonexistent recipient.
Detect bare 32-char hex strings in both the send-path target parser
(extensions/imessage/src/targets.ts) and the outbound delivery-target
normalizer (extensions/imessage/src/normalize.ts) and route them as
chat_identifier targets, consistent with how an explicit
`chat_identifier:<id>` prefix already resolves.
Fixes#89235.
* refactor(imessage): centralize bare chat identifiers
* refactor(imessage): centralize bare chat identifiers
---------
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary:
- The branch adds `forceMeetEnglishUi()` for Google Meet URLs, applies it to join/create browser opens, canonicalizes browser-created meeting links, and updates focused Google Meet tests.
- PR surface: Source +18, Tests +19. Total +37 across 6 files.
- Reproducibility: yes. Current main opens raw Meet URLs while the browser automation matches English-only lab ... R body provides after-fix CDP output showing non-English pages render expected English labels with `hl=en`.
Automerge notes:
- PR branch already contained follow-up commit before automerge: chore(tooling): add unused agent-cache-store files to knip optional a…
- PR branch already contained follow-up commit before automerge: fix(google-meet): repair non-Latin mic regex matching boundary
- PR branch already contained follow-up commit before automerge: revert(tooling): preserve current main deadcode allowlist guard
- PR branch already contained follow-up commit before automerge: revert(tooling): perfectly match origin/main deadcode list
- PR branch already contained follow-up commit before automerge: revert: match origin/main deadcode list exactly
Validation:
- ClawSweeper review passed for head 880a41b6f0.
- Required merge gates passed before the squash merge.
Prepared head SHA: 880a41b6f0
Review: https://github.com/openclaw/openclaw/pull/89671#issuecomment-4608725456
Co-authored-by: Chen Chia Yang <unayung@gmail.com>
Approved-by: hxy91819
* feat(signal): add target aliases
* fix(signal): canonicalize alias delivery targets
* fix(signal): harden alias target resolution
* test(signal): cover formatted media aliases
* test(signal): align approval alias proof with request shape
* Reuse Signal alias map for directory listing
* fix(feishu): catch unhandled promise rejection in streaming card flush timer
## What Problem This Solves
The scheduled flush update in FeishuStreamingSession called
`this.update(pending)` without catching potential rejections, which
could cause unhandled promise rejections when card content updates
fail due to transient network errors.
## Why This Change Was Made
Add a `.catch()` with diagnostic logging to the scheduled flush
timer's `update()` call so transient card update failures are visible
without causing unhandled promise rejections.
## User Impact
Scheduled streaming-card updates that fail transiently will log a
diagnostic instead of producing an unhandled promise rejection.
## Evidence
node scripts/run-vitest.mjs extensions/feishu/src/media.test.ts
Test Files 1 passed (1)
Tests 44 passed (44)
pnpm exec oxfmt --check ...
All matched files use the correct format.
node scripts/run-oxlint.mjs ...
oxlint_exit=0
git diff --check
exit=0
Co-Authored-By: Claude <noreply@anthropic.com>
* test(feishu): cover rejected scheduled card flush
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(qqbot): allow scoped sandbox media sends
* fix(qqbot): thread scoped media access through all outbound paths
Cover the seven QQBot outbound media paths that dropped core scoped
sandbox media access: gateway block/tool/streaming deliver, direct
text qqmedia tags, structured QQBOT_PAYLOAD, and the scoped workspace
relative/missing-voice path resolution. The low-level sendPhoto /
sendVideoMsg / sendDocument now consume core mediaAccess.readFile via
the shared outbound loader with explicit localRoots guard, so
host-mediated workspace media can be sent without widening QQBot FS
access. Voice stays on the existing SILK transcode path to avoid
bypassing QQ-required format conversion.
* fix(qqbot): thread workspaceDir into gateway reply media context
Gateway dispatchOutbound previously built gatewayMediaContext with only
mediaLocalRoots, so a sandboxed block reply that carried a relative
media tag (e.g. <qqmedia>report.docx</qqmedia>) reached the downstream
sender without mediaAccess.workspaceDir and was rejected by containment
checks before the file could be resolved against the agent workspace.
Resolve the agent workspace dir via the public plugin-sdk agent-runtime
helper and pass it through mediaAccess.workspaceDir alongside the
existing mediaLocalRoots so the gateway block reply, tool forwarding,
QQBOT_PAYLOAD, and official C2C streaming paths all share the same
scoped workspace resolution as direct outbound sends. No public plugin
SDK boundary is widened; the helper is an existing export.
Add a regression test that exercises a relative <qqmedia> tag in a
gateway block reply and confirms it resolves against the configured
agent workspace.
* fix(qqbot): wrap host-read media sends with structured error handling
* fix(qqbot): preserve host-read media quota errors
* fix(qqbot): preserve host-read quota fallback path
* fix(qqbot): resolve host-read quota fallback path
* test(qqbot): satisfy host-read media lint
* fix(qqbot): cover host-read voice media
* fix(qqbot): satisfy host-read voice lint
* Drop incidental formatting from PR merge
Keep the conflict-resolution update scoped to the QQBot media send files.
* fix(qqbot): map sandbox workspace media paths
* fix(qqbot): preserve host-read media roots
* fix(qqbot): scope sandbox media delivery roots
* fix(qqbot): map virtual media roots before host read
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(qqbot): scope structured payload media roots
* fix(qqbot): keep auto media helper internal
* docs(changelog): note QQBot scoped sandbox media fix (#92872) (thanks @zhangguiping-xydt)
---------
Co-authored-by: sliverp <870080352@qq.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Ambient transcript watermarks now carry the transcript session id, resolve only for the current session entry, and skip stale room-event hooks that no longer match the prepared transcript session.
This protects Telegram group prompt windows after reset by backfilling rows that are no longer present in the new session transcript, while preserving steady-state watermark filtering within one session.
Fixes#99373
Release-note: fixes Telegram group context loss after session reset when ambient transcript watermarks outlived the transcript they referenced.