Commit Graph

403 Commits

Author SHA1 Message Date
Peter Steinberger
9b565e8d50 refactor(plugins): trim long-tail dead exports (#106230)
* refactor(extensions): trim long-tail plugin exports

* refactor(whatsapp): remove dead media relay
2026-07-13 02:34:06 -07:00
Peter Steinberger
1136757174 refactor: consolidate bounded concurrency on p-limit and p-map (#106002)
* refactor: adopt p-limit and p-map for bounded concurrency

* chore: normalize lockfile ordering

* fix: preserve memory host concurrency export

* refactor: keep media apply within size budget

* fix: drain memory concurrency before failure

* fix: satisfy memory concurrency guards

* chore: scope memory host dead-code checks

* chore: lower concurrency LOC baselines

* fix: preserve concurrency API surfaces

* chore: reconcile concurrency LOC baseline

* test: keep memory concurrency gate portable

* style: simplify concurrency drain

* ci: refresh deadcode export baseline
2026-07-13 01:53:28 -07:00
Peter Steinberger
83bf0379c9 refactor(channels): share plugin contract scaffolds (#105838)
* refactor(channels): share plugin contract scaffolds

* fix: preserve channel config hint metadata

* chore: refresh Plugin SDK API baseline

* ci: refresh plugin SDK surface budget

* fix(ci): allow read-only state database boundary

* fix(ci): dedupe read-only state database boundary
2026-07-13 01:51:32 -07:00
Peter Steinberger
adf6b08ab4 refactor: consolidate duration parsing and formatting (#105988)
* refactor: consolidate duration handling

* docs: leave release notes to release workflow

* fix: preserve build timing precision

* ci: align plugin sdk surface budgets
2026-07-12 23:38:22 -07:00
Peter Steinberger
0d90ca6b5d refactor(config): retire flat and scalar streaming keys from matrix, feishu, and qqbot (#105808)
* feat(doctor): migrate legacy matrix/feishu/qqbot streaming spellings to nested config

* test(matrix): shorten setup fixture tokens below the review secret-scanner threshold

* refactor(config): retire flat/scalar streaming keys from matrix, feishu, and qqbot

* docs: describe nested-only streaming config for matrix, feishu, and qqbot

* fix(qa-matrix): compose nested streaming config for the matrix QA harness

* test(feishu): compose negative webhook fixtures outside the GHSA opengrep pattern

* docs: refresh generated docs map

* test(feishu): compose negative webhook fixtures from lint-clean bases outside the GHSA opengrep pattern

* fix(qa-lab): rewrite crabline flat streaming config into the nested channel shape

* chore(config): regenerate channel config metadata and plugin-sdk API baseline for nested-only streaming

* chore(plugin-sdk): absorb main-side deprecated pairing/conversation pin drift

* fix(qa-lab): consume Crabline nested streaming config
2026-07-12 20:33:14 -07:00
Peter Steinberger
d49acffa5e refactor(infra): consolidate retry scheduling (#105789)
* refactor(infra): consolidate retry scheduling

* fix(build): wire shared retry package

* fix(build): resolve retry package in source graphs

* fix(infra): keep retry adapter dependency-free
2026-07-12 19:58:55 -07:00
NIO
c93f87756a fix(qqbot): reject unsafe mention patterns (#102976)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-12 18:14:01 -07:00
Alix-007
2f3517c73f fix(qqbot): add timeout to remote media URL fetches (#103018)
* fix(qqbot): add timeout to remote media URL fetches

* fix(qqbot): use response header media timeout

---------

Co-authored-by: llagy009 <0668001470@xydigit.com>
2026-07-12 11:53:00 -04:00
Peter Steinberger
81941f2d68 test: enable noUncheckedIndexedAccess for extension tests (#105343) 2026-07-12 12:48:22 +01:00
Peter Steinberger
218dcd815a feat(tooling): enforce noUncheckedIndexedAccess in the extensions lane (NUIA phase 4) (#105132)
* fix(extensions): make indexed access explicit across channel plugins

Transport-payload-safe burn-down: malformed Telegram/Discord/QQ/LINE
and sibling channel input keeps existing skip paths; no synthesized
fields, no new throws in delivery loops. Zalo escape sentinels preserve
literal matches instead of undefined replacements.

* fix(extensions): make indexed access explicit across provider and memory plugins

Stream and model iteration, tool-block guards, capture guards, and
sparse accumulators; singleton model reads carry named invariants.

* fix(extensions): make indexed access explicit across tooling plugins, flip the extensions lane

Remaining plugins (oc-path, qa-lab, browser, logbook, and siblings) plus
the tsconfig.extensions.json flag flip. Cleanup: logbook sampleFrames
NaN index at max=1, QA retry clamp at non-positive attempts, dead Canvas
probe and OpenShell no-op slice removed, twitch test setup leak excluded
from the prod lane.

* refactor(plugin-sdk): expose expectDefined via a focused SDK subpath

Extensions imported @openclaw/normalization-core directly, crossing the
external-plugin packaging boundary (it only worked because the runtime
builder bundles undeclared workspace helpers). expect-runtime joins the
canonical entrypoints JSON, generated exports, API baseline, docs, and
subpath contract test; all 78 extension imports now use the SDK seam.
Two scanner-shaped locals renamed for review-bundle hygiene.

* chore(plugin-sdk): raise surface budgets for the expect-runtime subpath

One new entrypoint with one callable export, added intentionally as the
packaging-honest seam for extension invariant helpers.
2026-07-12 09:17:31 +01:00
Peter Steinberger
7a456e362d feat(channels): add typed cross-surface approval actions (#103679)
* fix(gateway): approval registry hardening and protocol-surface follow-ups

Follow-up delta to the merged #103579 head, rebased onto current main:
- gateway-protocol wire types derive from owner-module schema consts
  (types.ts tombstone) and ProtocolSchemas leaves the package index so the
  public plugin-sdk d.ts graph tree-shakes the registry declaration
- approval access authority follows the operator.approvals scope tier with
  reviewerDeviceIds as the opt-in restriction (cross-surface
  first-answer-wins; requester identity gates only legacy adapters)
- plugin node.invoke approvals register directly so unrenderable
  presentations fail closed before request routing
- exec-approval manager reconciliation with #103515 revocation hardening
  (resolution source attribution, one-shot ask-fallback consumption)
- surface-report pins and plugin-sdk API baseline refreshed; Swift models
  regenerated

* feat(channels): add typed operator approval actions

Squash-rebased #103679 segment onto the durable-approval-registry tip on
current main. Typed approval/command/select presentation actions replace
raw-string inference across slack/telegram/discord/matrix/imessage/whatsapp,
approval.resolve carries an explicit kind, and channel adapters map native
callback envelopes through the typed action registry.

Drift reconciliation: deprecated buildExecApprovalInteractiveReply assertions
dropped (#104650 removed the shims); worker_environments bootstrap-column
migration kept alongside the approval resolution_ref backfill; plugin-sdk API
baseline regenerated.

(cherry picked from commit 68765a5d39d2118c88a7a54d00387337912d4494)
(cherry picked from commit 8642ac12af142e4b751f4f30d4b114615e7e5f66)
(cherry picked from commit 036c4bc39499925fc03de16ec9302e346769350a)
(cherry picked from commit 19dc350d6bc34e29a5169c6bc80971b0ad12adde)
(cherry picked from commit fc978b0bad86aef421c79f6a211b25cc1b743c01)
(cherry picked from commit 10de4d1ed5071f9be6ad1ee5d1e32c0fa8c9d11c)
(cherry picked from commit 9a664ced1b1fa740172b258f355f1a82925ae41c)
(cherry picked from commit c5ff69abbf444139e9e007bfa45beb0f00ffea54)
(cherry picked from commit d466a80795)
(cherry picked from commit f5b4fe40dd5c961322f8553cc80b2fdfb3f6503e)
(cherry picked from commit 7340b4749a4cc4c72f7a41cce1bc9cb550cae038)
(cherry picked from commit a151f41808f23ae60b10305ccd2bc959b9169a86)

* fix(approvals): preserve typed transport ownership

* test(imessage): narrow chunked approval text

* refactor(protocol): remove retired type tombstone

* fix(plugin-sdk): align surface budgets after rebase

* docs(changelog): note typed operator approvals

* docs(changelog): defer typed approval release note
2026-07-11 18:31:05 -07:00
Peter Steinberger
93e5b017bf test(channels): trace-harness follow-ups — scenario names, fault vocabulary, lane sync guard (#104653) 2026-07-11 15:14:02 -07:00
Peter Steinberger
76b8550afc refactor(channels): migrate internal callers onto the message-access ingress engine (#104647) 2026-07-11 12:33:44 -07:00
Peter Steinberger
1879980e52 fix(qqbot): share passive reply budget (#104634)
* fix(qqbot): share passive reply budget

* chore(qqbot): leave release notes to release flow
2026-07-11 12:10:24 -07:00
Peter Steinberger
554f253cb0 test(qqbot): delivery trace goldens for replace-mode streaming and reply budget (#104582) 2026-07-11 10:54:15 -07:00
Peter Steinberger
96f0983a85 fix(onboarding): skip setup for configured gateways and require inference first (#102883)
* fix(crestodian): keep onboarding RPCs restart-safe

* fix(profiles): isolate approval state migrations

* fix(crestodian): bypass configured gateway setup

* test(crestodian): type onboarding mocks

* fix(onboarding): require inference before Crestodian

* fix(onboarding): enforce verified inference handoff

* fix(macos): reset setup on gateway endpoint edits

* chore(i18n): refresh native source inventory

* fix(gateway): keep socket on request cancellation

* test(packaging): require workspace templates

* fix(onboarding): bind setup to verified inference

* fix(onboarding): align inference gate contracts

* fix(crestodian): classify concurrent policy rejection

* test(crestodian): expect registry restoration

* fix(onboarding): bind setup to configured gateways

* fix(codex): preserve startup phase deadlines

* test(crestodian): match fail-closed policy ordering

* test(onboarding): assert bound gateway handoff

* fix(codex): bind runtime resolution to spawn cwd

* test(crestodian): assert policy rejection order

* fix(cli): preserve gateway routing across restarts

* fix(macos): fail closed during gateway edits

* test(macos): cover gateway route generation races

* chore: keep release notes out of onboarding PR

* fix(ci): refresh onboarding generated checks

* style(swift): align gateway channel formatting

* fix(ci): refresh plugin SDK surface budgets

* fix(ci): resync native string inventory

* refactor(swift): split gateway channel support

* test(doctor): isolate plugin compatibility registry

* test(macos): isolate gateway onboarding fixtures

* test(macos): assert gateway lease health ordering

* fix(codex): reconcile computer-use startup changes
2026-07-11 10:25:14 -07:00
Peter Steinberger
3f33f0bb5f refactor(qqbot): adopt core typing keepalive loop with budget accounting intact (#104438)
* refactor(channels): adopt core typing keepalive loop

* chore(plugin-sdk): pin surface budget for typing keepalive re-export
2026-07-11 05:48:33 -07:00
Peter Steinberger
7bf80dc2c6 chore(tooling): enforce formatting and refresh TypeScript checks (#104239)
* chore(tooling): enforce current formatter and refresh checks

* chore(tooling): keep release changelog formatter-owned

* chore(tooling): retain compatible Node type surface

* ci: enforce formatting for docs-only changes

* ci: isolate docs formatter check

* chore(tooling): apply updated lint and format rules

* chore(tooling): satisfy updated switch lint

* style(ui): apply Linux formatter layout

* test(doctor): match quiet local audio contribution

* test(doctor): assert quiet output only

* test(doctor): follow restored information contract
2026-07-11 01:09:51 -07:00
Peter Steinberger
dba64d574f chore(release): set version to 2026.7.2 2026-07-11 04:00:49 +01:00
maweibin
6b460e50a4 fix(qqbot): bound stalled token acquisition (#102897) 2026-07-11 01:45:43 +01:00
Michael Appel
56e97dab3a fix(qqbot): limit channel API targets to configured groups [AI] (#101765)
* fix(qqbot): scope channel API targets to configured groups

* fix(qqbot): fail closed scoped channel API resources

---------

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
2026-07-10 14:11:50 +05:30
Bartok
1b03e51e18 fix(qqbot): render safe exec approval previews (#102119)
Render pending approvals from shared typed views, add marked grapheme-safe display wraps for QQ Desktop, preserve the 300 UTF-16-unit cap, fence command and metadata safely, and report plugin expiry from the runtime clock.

Fixes #101979
2026-07-09 20:31:57 +01:00
Peter Steinberger
47cc6c9667 fix(qqbot): propagate canonical image MIME types (#102795) 2026-07-09 14:48:11 +01:00
Alix-007
64e80888d4 fix(qqbot): bound speech transcription requests (#102028)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-09 13:25:29 +01:00
Masato Hoshino
a49567cdfd fix(qqbot): treat inbound attachment content types case-insensitively (#102753)
* fix(qqbot): treat inbound attachment content types case-insensitively

* test(qqbot): use distinct download paths per image; note voice sentinel in comment
2026-07-09 05:05:34 -07:00
lsr911
1beea4b873 fix(qqbot): use truncateUtf16Safe for debug log URL/key truncation (#102572)
* fix(qqbot): use truncateUtf16Safe for debug log URL/key truncation

Replace naive .slice(0, N) with truncateUtf16Safe() in:
- image-size.ts: URL preview in debug logs (2 sites)
- upload-cache.ts: cache key preview in debug logs (2 sites)

Co-Authored-By: Claude <noreply@anthropic.com>

* test(qqbot): cover UTF-16-safe debug previews

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-09 11:20:38 +01:00
Peter Steinberger
42a8679c4d fix: restore release validation on main (#102516)
* test(qqbot): assert approval fence exactly

* fix(release): wait for ClawHub publish logging

* fix(ci): repair Bun global install smoke

* test(slack): fix app identity fixture
2026-07-09 09:39:08 +01:00
Vincent Koc
6f6c3e2602 refactor(qqbot): localize internal declarations (#102048) 2026-07-07 23:01:33 -07:00
Vincent Koc
b88f80940a refactor: localize internal implementation symbols (#101831) 2026-07-07 10:23:36 -07:00
Vincent Koc
ccbd9eb28d fix(qqbot): keep bounded previews UTF-16 safe (#101516)
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
2026-07-07 02:18:57 -07:00
Peter Steinberger
a2a68d154b fix(agent): preserve explicit recipient sessions (#101507)
* fix(agent): honor recipient session routing

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): preserve canonical recipient routes

* fix(agent): require exact recipient routes

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>

Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): harden recipient route resolution

* fix(imessage): require canonical recipient handles

* fix(agent): refine provider recipient exactness

* fix(agent): bound direct alias session routing

* fix(signal): preserve direct alias route type

* fix(agent): honor binding-scoped recipient sessions

* fix(routing): honor configured main session aliases

* fix(clickclack): align account-owned session routes

* fix(twitch): preserve canonical recipient sessions

* fix(routing): isolate stable outbound identities

* chore: defer recipient session changelog

* fix(sms): use dedicated channel SDK facade

---------

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>
2026-07-07 10:07:13 +01:00
wangmiao0668000666
048fe0850e fix(qqbot): use UTF-16-safe truncation for messaging reply and approval previews (#101421)
* fix(qqbot): use UTF-16-safe truncation for messaging reply and approval previews

* test(qqbot): focus UTF-16 approval regression

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-07 08:22:06 +01:00
Dallin Romney
5af7ba92ce refactor: consolidate byte-size formatting (#99768)
* refactor: consolidate byte-size formatting

* fix: stabilize byte formatter import boundary

* fix: keep byte formatter within SDK budget

* fix: emit byte formatter package entry

* refactor: trim byte formatter surface

* fix: use narrow byte formatter import

* refactor: remove byte formatter dependency changes

* fix: refresh rebased byte formatter baseline
2026-07-06 09:26:04 -07:00
Peter Steinberger
1f484a8dbd test: speed up and stabilize full suite 2026-07-05 08:00:23 -04:00
Masato Hoshino
cbb920c7d9 fix(qqbot): channel status keeps reporting connected after the gateway websocket dies (#100127)
* fix(qqbot): publish disconnected channel status when the gateway closes or gives up

The QQBot channel only ever set connected: true (onReady/onResumed);
no close path updated the status, so a fatal close (bot banned or
offline, 4914/4915) or reconnect exhaustion left channels.status
claiming a live connection forever, and the channel-health monitor
never saw connected=false for a dead gateway.

Thread an onDisconnected callback from GatewayConnection.handleClose
(fatal and pre-reconnect branches) and the reconnect-exhaustion path
through the engine/bridge layers into channel.ts, which now records
connected: false and, for fatal closes, the close reason as lastError.
The running flag stays owned by the gateway lifecycle store, matching
the sibling channel convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): import ChannelAccountSnapshot from the channel-contract subpath

The monolithic openclaw/plugin-sdk root entry is a legacy surface;
plugin-sdk contract guardrails and extension boundary checks require
focused subpath imports in bundled plugin sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): ignore stale socket closes from superseded gateway connections

A server-driven RECONNECT / INVALID_SESSION tears the old socket down
and brings up a replacement; the old socket's close event can arrive
after the replacement is live. Reacting to it again would tear down the
new socket and regress the connected status, so the close handler now
ignores closes from sockets that are no longer current.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): harden disconnect status recovery

* fix(qqbot): report opcode-driven reconnects

* fix(qqbot): keep fatal disconnect health visible

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-05 04:18:01 -07:00
Dallin Romney
bfffa950d7 refactor(plugin-sdk): consolidate tool result helpers (#99740)
* refactor(plugin-sdk): consolidate tool result helpers

* docs(plugin-sdk): tighten tool result guidance

* refactor(feishu): use tool results directly
2026-07-04 16:50:44 -07:00
Peter Steinberger
7a92c473c1 test(qqbot): fix macOS-only outbound media test failures
The scoped outbound media tests added in #92872 assert raw mkdtemp paths,
but prod resolvers canonicalize roots, so on macOS the /var -> /private/var
tmpdir symlink made three tests fail (green on Linux CI). Realpath the tmp
roots like sibling tests, and export the real UploadDailyLimitExceededError
from the outbound-dispatch sender.js mock so prod instanceof checks in error
paths hit real assertions instead of vitest's missing-export guard.

Also add AGENTS.md test guidance covering both patterns.
2026-07-04 19:20:22 +01:00
Dallin Romney
c7aca4f029 refactor: consolidate async timing helpers (#99721) 2026-07-03 17:58:15 -07:00
xingzhou
cd6b67533d fix(qqbot): allow scoped sandbox media sends (#92872)
* 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>
2026-07-03 15:02:58 +08:00
Dallin Romney
59b08b4693 refactor(shared): consolidate remaining channel lazy loaders (#99302) 2026-07-02 19:08:11 -07:00
Peter Steinberger
24d94a54a5 fix: migrate QQBot credential backups to SQLite KV (#89597)
* fix: migrate qqbot credential backups to sqlite kv

* fix: complete qqbot sqlite migration cleanup

* fix: harden qqbot doctor state migration
2026-07-01 20:03:59 +01:00
Vincent Koc
3e2646a786 fix(release): use workspace host deps in release lockfile
(cherry picked from commit d377c0f910)
2026-06-30 15:54:12 -07:00
Vincent Koc
66e676d29b chore(release): close out 2026.6.11 on main 2026-06-30 11:31:08 -07:00
Patrick Erichsen
56c2d637d9 fix(qqbot): tighten bundled skill guardrails (#98032) 2026-06-29 22:48:52 -07:00
Vincent Koc
c0ee7a1d20 test: gate symlink assertions on file symlink capability (#95531)
Co-authored-by: ANIRUDDHA ADAK <127435065+aniruddhaadak80@users.noreply.github.com>
2026-06-28 22:50:37 -07:00
ly-wang19
3e64d84712 fix(qqbot): treat escaped pipes as literal content when splitting table cells (#97429)
splitTableCells/splitPartialTableCells split on every '|', including a GFM
backslash-escaped pipe ('\|'), which is literal cell content rather than a
column delimiter. A cell containing '\|' was therefore mis-counted as multiple
columns, so the oversized-row fallback (renderTableRowAsFields) rendered the
trailing content under the wrong header.

Split via an escape-aware scan that treats '\|' as a literal '|' (and '\\' as
a literal backslash so a following '|' still delimits). Behavior is byte-for-byte
unchanged for any row without an escaped pipe, so existing chunking is preserved.

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:03:52 -07:00
llagy009
d693ed4af3 fix(qqbot): truncate reminder job name on code-point boundary (#96575)
generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
2026-06-27 09:30:52 -07:00
mushuiyu886
6e8f30c0e2 fix(qqbot): bound STT transcription JSON response (#96968) 2026-06-27 07:25:29 -07:00
joshavant
0a14444924 Bound successful provider response reads 2026-06-24 19:08:22 -05:00
Vincent Koc
0671c08900 chore(release): close out 2026.6.10 on main (#96271)
* chore(release): close out 2026.6.10 on main

* chore(release): align native app metadata for 2026.6.10

* chore(release): sync Android 2026.6.10 notes

* docs(changelog): preserve 2026.6.9 history

* docs(changelog): preserve 2026.6.9 history
2026-06-24 11:51:14 +08:00