Commit Graph

70668 Commits

Author SHA1 Message Date
Peter Steinberger
d4ed08994d fix(onboard): validate reset preflight (#111348) 2026-07-19 02:42:49 -07:00
Alex Knight
a868d34804 Fix fractional restart handoff migration (#110231)
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-07-19 19:42:27 +10:00
Peter Steinberger
e0ecdc47be feat(chat-ui): tool-diff follow-ups — cached diffs, apply_patch, isError, long lines (#111326)
* feat(chat-ui): tool-diff follow-ups from PR review

Persist bounded applied-diff details in the transcript cache so cold
opens keep inline diffs; parse apply_patch envelopes into per-file diff
sections with stats; thread tool-result isError so failed calls suppress
proposal diffs and read as failures; replace per-line truncation with
horizontal scrolling for long diff lines.

* chore(i18n): sync native inventory for tool-diff follow-ups

* fix(chat-ui): render move-only patches and drop zero diffstats
2026-07-19 02:40:37 -07:00
Peter Steinberger
c7d2d111d8 fix(ci): enforce plugin SDK API baseline (#111289)
* fix(ci): enforce plugin SDK API baseline

* fix(plugin-sdk): refresh fs-safe API baseline
2026-07-19 02:38:58 -07:00
Peter Steinberger
b9fa1ffa27 feat(gateway): system change history RPC and Ask OpenClaw recent-changes panel (#111286)
* feat(gateway): system change history RPC and Ask OpenClaw recent-changes panel

* test(gateway): fix journal fixtures, split custodian page tests, refresh docs map

* chore: quiet lint on journal reader tests and custodian harness
2026-07-19 02:38:05 -07:00
Peter Steinberger
b3bb40551b fix(mac): opening General Settings crashes in packaged app (#111300)
* fix(macos): package KeyboardShortcuts resources

* fix(macos): keep dependency patch whitespace-safe

* fix(macos): preserve locked Swift resolution

* fix(macos): guard Swift lock across builds

* fix(macos): simplify KeyboardShortcuts packaging
2026-07-19 02:30:49 -07:00
Peter Steinberger
4e7ef6223c fix(cron): keep paced schedules stable across force runs and edits (#111331)
* fix(cron): preserve cadence ownership

* fix(cron): satisfy maintenance lint
2026-07-19 02:26:28 -07:00
Peter Steinberger
a5a05466fc fix(mcp): bound probe initialization (#111318) 2026-07-19 02:26:14 -07:00
Peter Steinberger
10313e3eee refactor(sessions): keep late-media attachment text out of persisted transcripts (#111293)
* refactor(sessions): keep late-media attachment text out of persisted transcripts

Late-resolved media appended a second user turn whose content was literal
"[media attached: <path>]" lines, so clients rendered wire scaffolding
under the image — same leak class as #111204. Persist those turns bare
(media fields + a protected __openclaw.lateMedia marker, image blocks
kept), and project the attachment lines at the LLM boundary and the
history-image-prune stage instead, byte-identical for providers across
string, array, and legacy forms. Also dedupes media-field sniffing into
a shared hasPersistedMedia helper.

* fix(sessions): satisfy type gates for late-media boundary projection

* fix(agents): narrow prune content cast to array element type

* test(sessions): split transcript persistence suite under max-lines cap
2026-07-19 02:14:19 -07:00
Peter Steinberger
84e54ab264 chore(pr): landing-UX — aggregated validation, lock classes, ci-dispatch, committer guard (#111287)
* chore(pr): landing-UX — aggregate artifact validation, lock classes, ci-dispatch, committer worktree guard

* fix(pr): CI lane cleanup for landing-ux — lint causes, hermetic tests, private helpers

* chore(deadcode): register pr-lib CLI entries in knip config
2026-07-19 02:09:55 -07:00
Peter Steinberger
ba06fe1541 feat(ui): author avatar chips on user messages (incl. queued) (#111309)
* feat(ui): author avatar chips on user messages with gravatar and initials fallback

Renders a small author avatar on user-authored chat messages, including
queued/pending sends, so multi-user gateways show at a glance who wrote
which prompt. Resolution: profile avatar URL (when available) -> Gravatar
by SHA-256 email hash (d=404) -> deterministic initials with stable color.
Assistant/tool output stays unmarked, and messages without sender identity
render unchanged, keeping single-user gateways visually identical.

* fix(ui): clear avatar fallback state on image load; document gravatar tradeoff

* fix(ui): gate email-hash avatars behind a gateway proxy base URL

Without a same-origin proxy base the email tier is disabled outright, so
rendering a transcript can never disclose hashed sender emails or viewer
IPs to a third-party avatar host. The gateway-side proxy route (follow-up)
supplies the base and becomes the only avatar fetch path.

* fix(ui): restrict profile avatar URLs to same-origin relative paths

* fix(ui): validate profile avatar URLs with the URL parser, not string prefixes

Backslashes normalize to slashes and tabs/newlines are stripped by browser
URL parsing, so startsWith checks can be bypassed into cross-origin fetches.
Resolving against a probe origin and requiring the origin to survive is
parser-equivalent and closes the class.
2026-07-19 02:08:05 -07:00
xingzhou
e2ca148d4b fix(codex): preserve JSONL framing across WebSocket writes (#110384)
* fix(codex): preserve UTF-8 websocket frames

* test(codex): satisfy websocket transport lint

* fix(codex): preserve websocket JSONL framing
2026-07-19 17:07:03 +08:00
xingzhou
48ecb31bc6 fix(discord): time out stalled Activity token bodies (#110667) 2026-07-19 17:05:58 +08:00
Peter Steinberger
11beef19e2 fix(cli): keep plugin help transport-free (#111322) 2026-07-19 02:00:35 -07:00
zhang-guiping
dd16befe58 fix(logs): bound stalled journal subprocesses 2026-07-19 16:56:54 +08:00
Peter Steinberger
e20d4cd450 fix(media): gate inbound audio on structured media facts (#111315)
hasInboundAudio previously accepted <media:audio> placeholder bodies,
[Audio transcript headers, and filename sniffing alongside MediaTypes.
It now consumes only the structured facts channels already carry
(MediaType/MediaTypes, kind audio or audio/*), removing the string
reverse-engineering ahead of the placeholder-elimination program.
Discord — the one channel with a coverage hole — now carries its native
audio classification (MIME, voice fields, constrained filename fallback)
as structured kind: "audio", with definitive fetched/declared MIME
precedence so generic container types cannot erase declared audio and
filename hints cannot override definitive non-audio MIME.
2026-07-19 01:55:26 -07:00
Mason Huang
c26d3cfbd0 fix(ci): skip Periphery scans for unrelated PR changes (#107991)
* fix(ci): scope Periphery to PR merge changes

* test(ci): align Periphery changed-test routing
2026-07-19 16:52:44 +08:00
Peter Steinberger
8631832048 fix: workspace divergence no longer wedges cloud-worker sessions (#111244)
* fix(cloud-workers): resolve workspace divergence with keep-local conflicts

* fix: retain workspace conflicts during worker reclaim

* style: satisfy workspace reconciliation lint

* chore: defer cloud worker changelog to release

* refactor: split cloud workspace reconciliation modules

* fix: satisfy workspace module boundary checks
2026-07-19 01:49:35 -07:00
Peter Steinberger
2e8b042f9f feat(gateway): resolve durable profile identity at connection setup (#111311) 2026-07-19 01:41:35 -07:00
Mason Huang
8c0e876e85 fix: prevent cumulative usage from inflating session context (#108323)
* fix: use last-call usage for session context

* fix: preserve final nonzero call usage

* fix: keep exact usage snapshots context-scoped

* refactor(agents): remove unused last-call accumulator

* fix(agents): scope usage fallback to uncompacted attempts

* test(agents): type compaction usage fixture

* test(agents): use provider usage fixture shape
2026-07-19 16:40:55 +08:00
Peter Steinberger
f07a1fb502 refactor: centralize bounded file reads in fs-safe (#111104)
* refactor: use fs-safe bounded descriptor reads

* build: update fs-safe to 0.4.2

* build: refresh root npm shrinkwrap

* fix: satisfy bounded read return paths

* fix: update fs-safe integration for latest main

* fix: adopt fs-safe overflow compatibility release

* build: complete fs-safe lockfile update

* build: update fs-safe to 0.4.4

* build: refresh plugin SDK API baseline

* test: follow fs-safe bounded read seam
2026-07-19 01:29:23 -07:00
Peter Steinberger
59b2fbbb4b feat(ios): Talk camera flip control and mic input selection (#111044)
* feat(ios): add Talk camera flip and mic selection

* chore(ios): sync native i18n inventory and locale artifacts for talk camera/mic strings

* chore(ios): commit regenerated Localizable.xcstrings for new talk strings

* fix(ios): extract Talk composer controls

* chore(ios): resync native i18n inventory and locale artifacts atop landed Android entries

* fix(ios): repair Talk composer CI checks

* chore(i18n): commit regenerated Android locale outputs from catalog reconciliation

* chore(i18n): sync Android locale outputs after rebase
2026-07-19 01:24:53 -07:00
Peter Steinberger
96c9cc112c fix(cron): preserve script state and stop stale runs after restart (#111292)
* fix(cron): preserve script execution boundaries

* chore: keep release changelog unchanged
2026-07-19 01:24:19 -07:00
Peter Steinberger
3a0d6b327b feat(chat-ui): inline diffs for edit and write tool rows (#111039)
* feat(chat-ui): inline diffs for edit and write tool rows

Port the web Control UI's kind-aware diff rendering to the shared SwiftUI
tool rows: persisted details.diff parses into numbered add/del/ctx lines,
edit/write/text-editor args fall back to a local capped line diff, the
collapsed row shows a +N/-M diffstat, and the expanded surface renders the
diff with the result summary or error diagnostic beneath it.

* fix(chat-ui): preserve tool-result details through message reconciliation

Canonical-adoption rebuilds enumerated message fields and silently dropped
details, so inline diffs vanished after cache-warm history reconciliation
(every app relaunch). Carry details through adoptingCanonicalMessage and
messageWithTimestampIfNeeded and lock it in with regression tests.

* chore(i18n): sync native inventory for tool diff rows

* fix(chat-ui): render diffstat and line numbers as verbatim text

* fix(chat-ui): use theme tokens for diff colors

* refactor(chat-ui): drop test-only parseDetailsDiff wrapper

* fix(chat-ui): address diff-row review findings

Interpret details.diff only for edit/write/patch tool names so plugin
detail schemas stay opaque; number write previews from line 1 with an
80-line cap and a subtractive char budget while keeping the exact stat;
bound per-line rendering to 2000 unicode scalars; announce add/del rows
and collapsed skip markers to assistive tech.

* chore(i18n): sync native artifacts for diff-row labels

* fix(chat-ui): announce diff line numbers to assistive tech
2026-07-19 01:21:51 -07:00
Peter Steinberger
918f6615d0 feat(ui): Swarm progress widget — dot grid per swarm group (#110325) (#111297)
* feat(ui): Swarm progress widget — dot grid per swarm group (#110325)

* fix(ui): keep swarm renderer internals private
2026-07-19 01:19:56 -07:00
Peter Steinberger
430147fc89 feat(ui): approval UX overhaul — inline cards, fair queue, shortcuts, badges (#110989)
* feat(ui): overhaul approval experience

* fix(ui): guard approval shortcuts against key auto-repeat and keep scoped errors on enqueue

* fix(ui): arm approval shortcuts after focus settles and scope error clearing to the settled request

* fix(ui): approval hardening — modifier-chord shortcuts, pinned active card, badge fallback, quiet countdown

* fix(ui): scope approval errors per request

* style(ui): format approval queue test

* fix(ui): tolerate missing approval overlays

* perf(ui): lazy-load approval modal controls

* fix(ui): satisfy approval lazy-load guards

* style(ui): satisfy approval card lint
2026-07-19 01:18:09 -07:00
xingzhou
7b3a8dba14 fix(meeting-bot): preserve spawn failure diagnostics (#107614) 2026-07-19 16:17:46 +08:00
xingzhou
59124313f4 fix(release): plugin npm preflight no longer hangs on stalled registries (#110862)
* fix(release): bound plugin npm freshness lookups

* fix(release): fail closed on npm view timeouts

* test(release): fix npm timeout mock types
2026-07-19 16:10:49 +08:00
Peter Steinberger
3a5da4faa8 feat(ui): live agent activity subtitles on running sidebar sessions (#111221)
* feat(ui): live agent activity subtitles on running sidebar sessions

* refactor(ui): lazy-load narration controller off the startup chunk, unexport test-only symbols

* chore(ui): raise startup JS budget to 312 KiB for the lazy narration feature

* test(ui): restore real timers after narration controller cases
2026-07-19 01:08:06 -07:00
Peter Steinberger
4efcea1fd2 feat(browser): send pages to OpenClaw from the Chrome extension (#111158)
* feat(browser): send pages to the main session from the Chrome extension

One-click page share in the OpenClaw Chrome extension: toolbar popup with an
optional note, page/selection context menu, and Alt+Shift+S. Capture is
selection-first with a readability heuristic, X/Twitter thread extraction, and
Google Docs plain-text export via the user's session cookies. Payloads ride the
existing paired relay WebSocket as a new pageShare message; the gateway-only
page-share sink wraps page text in the external-content safety boundary, then
enqueues a main-session system event and requests an immediate heartbeat
(hooks/wake semantics). Node-hosted relays report a clear unsupported error.

Capture heuristics adapted from Nat Eliason's MIT-licensed send-to-openclaw.

Co-authored-by: Codex <codex@openai.com>

* fix(browser): keep page-controlled metadata inside the share safety boundary

Review findings: move title/URL inside wrapExternalContent (a hostile <title>
must not become trusted header text), prefer the user's selection over the
full Google Docs export, and pass the context-menu selectionText through so
iframe selections and selections cleared during relay reconnect still win.

* fix(browser): bind context-menu shares to the click-time document

Selection shares from the context menu now send the click snapshot directly
(no recapture), so navigations during relay reconnect cannot mislabel the
source and iframe selections are preserved. The Google Docs selection probe
scans all accessible frames before falling back to the full-document export.

* test(browser): expect the page-share handler in relay server args

* fix(browser): probe only the main frame for Google Docs selections

All-frame injection rejects wholesale when one frame is inaccessible and
returns child frames in nondeterministic order, so the probe now reads the
main frame only. Child-frame selections still share correctly through the
context menu's click-time selectionText; toolbar/shortcut entry sends the
full page for that case (named tradeoff in the code comment).

* fix(browser): satisfy page-share CI gates

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-19 01:07:28 -07:00
Peter Steinberger
0f95e66b7f feat(talk): add durable client voice sessions (#111216)
Live-append voice transcripts into the agent session and persist a per-agent SQLite call record across relay and client transcript paths.

Add run-scoped spoken confirmation for high-impact actions, mutation digests, bootstrap-context injection, talk.client.transcript and talk.client.close protocol methods, and Control UI adoption. This adds zero new configuration.

Co-authored-by: Clifton King <clifton@users.noreply.github.com>
2026-07-19 01:06:49 -07:00
Peter Steinberger
83fc53a22f feat(android): approve, reject, and remove device pairings from the phone (#111014)
The Nodes & Devices screen showed pending pairing counts but pointed users
at a CLI command on the gateway host. An already-trusted phone can now act:
approve/reject pending requests (gated on hello methods plus the
operator.pairing or admin scope, mirroring the gateway's second
authorization gate for requested roles/scopes) and remove paired devices
(admin-gated, matching the gateway's removal contract). The confirm dialog
is the consent surface: it renders the complete requested roles/scopes
unbounded and scrollable with control/bidi sanitization, keyed to the
gateway identity so a pending confirmation cannot survive a gateway switch.
Mutations capture the gateway scope at claim time, require the exact write
acknowledgement plus a canonical device.pair.list readback before claiming
success, surface definitive gateway denials verbatim, and take over the
refresh generation with the verified snapshot so stale refreshes cannot
republish pre-mutation state. Bootstrap-requested scopes stay on the
canonical limited profile; extending it with operator.pairing is a
deliberate gateway-side product decision left open.
2026-07-19 01:03:45 -07:00
Peter Steinberger
a8b7290f34 feat(gateway): durable user profiles with email aliases and avatars (#111224)
* feat(gateway): durable user profiles with email aliases and avatars

* fix(gateway): compress merge tombstones, content-hash avatar ETags, users CLI json output

* fix(gateway): lean profile listing, scoped avatar routing, typed email validation

* fix(gateway): protocol-complete profile payloads and consistent store reads

* fix(gateway): mark profile schema ensured only after commit

* fix(gateway): avatar endpoint HEAD support and RFC If-None-Match

* fix(gateway): profiles CI conformance — bindings, lint, knip, sql boundary

* feat(gateway): self-service profile edits for authenticated users

* fix(gateway): users.self bootstrap, tombstone-aware ownership, escaped CLI output

* fix(gateway): raw-DDL allowlist entry and lean profile exports
2026-07-19 00:58:43 -07:00
Peter Steinberger
58452de711 refactor(config): config-surface reduction tranche 1 — retire dead keys, dedupe channel schemas, add growth ratchet (#111142)
* refactor(config): retire dead and aliased config keys via doctor migrations

* refactor(config): dedupe bundled channel config schemas into shared builders

* feat(config): add config-surface count ratchet to doc-baseline check

* test(config): drop stale fixtures for retired config keys

* fix(doctor): migrate only positive finite MCP timeout aliases

* fix(migrate-hermes): emit canonical MCP timeouts only

* fix(config): satisfy lint and contract gates
2026-07-19 00:52:37 -07:00
Peter Steinberger
33b50089cd feat(android): platform trust for public gateway certs and manual fingerprint pinning (#110976)
Publicly-CA-valid gateway hosts (e.g. Tailscale Funnel with Let's Encrypt)
no longer force fingerprint pinning: a real platform-trust handshake with
HTTPS hostname verification and SNI connects without prompting, ending the
changed-pin re-prompt on every cert renewal. Candidate gating excludes
.local, IP literals, and single-label hosts so LAN endpoints keep pinning.
Existing pins stay authoritative: a valid public chain never silently clears
a stored pin - the changed-pin prompt gains an explicit one-time 'Use system
trust' switch instead. Discovered endpoints still require a stored pin for
reconnect eligibility (discovery data is attacker-controllable). When the
TLS probe cannot capture a certificate, the trust dialog now accepts an
out-of-band SHA-256 fingerprint paste (sha256:/colon tolerant, 64-hex
validated) which is then enforced on connect. Malformed stored pins now
fail closed instead of silently degrading. Both probe attempts share one
connect+handshake budget so stalled hosts cannot double the probe latency.
2026-07-19 00:48:06 -07:00
Peter Steinberger
716f507564 fix(doctor): preserve config repairs during locked session import (#111280) 2026-07-19 00:44:48 -07:00
Jason (Json)
9c7800467c feat(mcp): open App views from channel replies (#111211)
* feat(mcp): add portable channel app actions

* test(gateway): keep origin reset private

* fix(mcp): require a resolved reply channel
2026-07-19 01:44:15 -06:00
Peter Steinberger
53d600ab83 feat(android): accept audio and document attachments via share sheet and composer (#110941)
* feat(android): accept audio and document attachments via share sheet and composer

Extends Android share-sheet intake beyond text/images: audio plus a curated
document set (PDF, OOXML, CSV, Markdown) with code-side mime enforcement,
provider-type revalidation while the URI grant is live, bounded streaming
reads, per-kind size budgets aligned with the outbox command limit, and a
document/audio picker in the composer. Share parsing moves off the main
thread behind a bounded slot queue; draft-not-autosend and drop/failure
reporting behavior unchanged.

* test(android): cover audio and document share intake

* test(android): align composer draft test with attachment kinds

* chore(android): refresh native i18n inventory

* chore(android): rebuild native locale artifacts for inventory order
2026-07-19 00:32:24 -07:00
Peter Steinberger
9d2f5d4dbb feat(ui): enable Swarm toggle in Labs (#110325) (#111267) 2026-07-19 00:27:06 -07:00
Peter Steinberger
c163daa4ed refactor(channels): share durable ingress monitor (#111249)
* refactor(mattermost): share ingress monitor

* refactor(nextcloud-talk): share ingress monitor

* refactor(msteams): share ingress monitor

* refactor(zalo): share ingress monitor

* refactor(sms): share ingress monitor

* refactor(line): share ingress monitor
2026-07-19 00:26:28 -07:00
Peter Steinberger
7c4292ee96 fix(agents): reject identity updates for unknown agents (#111268)
* fix(agents): reject identity updates for unknown agents

* fix(agents): preserve implicit default identity updates
2026-07-19 00:18:25 -07:00
Peter Steinberger
9aa6e672a7 fix(custodian): show mode-appropriate guidance and defaults (#111263)
* fix(custodian): align option-card defaults

* test(custodian): preserve default confirm choice
2026-07-19 00:17:09 -07:00
Peter Steinberger
8028288f05 fix(docs): make Mintlify anchor audit reliable (#111265) 2026-07-19 00:15:40 -07:00
Peter Steinberger
8f2ec62917 fix(streams): release guarded response reader locks (#111259)
* fix(streams): release guarded source reader locks

* test(streams): avoid unbounded response helpers
2026-07-19 00:15:23 -07:00
Peter Steinberger
c84a59284b fix(android): stop push-to-talk from dropping speech after mid-hold pauses (#110995)
* fix(android): stop push-to-talk from dropping speech after mid-hold pauses

The SpeechRecognizer PTT path ran one session with a 2.5s silence window,
kept only the last final, and never restarted during a hold - speech after
a mid-hold pause was silently dropped. Capture now uses an ordered rung
ladder: API 33+ app-owned AudioRecord piped via EXTRA_AUDIO_SOURCE with
EXTRA_SEGMENTED_SESSION, then silence-keyed segmented sessions, then a
restart-during-hold single-session fallback for older devices; failed holds
only descend. Final segments accumulate and merge locale-aware (no ASCII
separators after CJK/Arabic punctuation), with a trailing live partial
deduped against the last final. Release waits one bounded grace for the
terminal recognizer callback so late finals are not truncated; rapid
re-press drains the prior release instead of cancelling it; cancellation
mid-release still tears capture down, resumes realtime talk, and resolves
the stop payload. Raw-rung release also stops the recognizer since
EXTRA_AUDIO_SOURCE is optional and a service may run its own microphone.
Input level meters from our PCM stream in the raw rung where onRmsChanged
never fires.

* fix(android): API-33 recognizer extras behind RequiresApi and inventory refresh

Min-SDK lint (InlinedApi) flagged the API 33 RecognizerIntent extras; they
now live in RequiresApi(TIRAMISU) helpers behind explicit SDK gates, and
the native i18n inventory is refreshed for the moved lines.

* chore(android): rebuild native locale artifacts for inventory order
2026-07-19 00:13:19 -07:00
Peter Steinberger
ea54060223 feat(codex): fork upstream-linked sessions at a message via thread/fork (#111149)
* feat(codex): fork upstream-linked sessions at a message via thread/fork

* fix(gateway): fail closed for rewind and branch switch on upstream-linked sessions

* fix(codex): fail closed on first-message forks, image-only prompts, and orphan archival

* fix(codex): baseline retained history and reject paginated threads on upstream fork

* fix(codex): validate the full fork prefix and fail closed across crash windows

* fix(codex): treat all non-text inputs as unverifiable in fork drift checks

* fix(codex): support first-message forks as empty-history upstream cuts

* fix(codex): reject source-id reuse and unverifiable hidden inputs in fork boundaries

* refactor(codex): materialize upstream forks from verified thread read-back

* fix(codex): satisfy strict type lanes and knip for upstream fork
2026-07-19 00:11:10 -07:00
Peter Steinberger
aae5b0f041 feat(ui): hold-to-record dictation on the composer mic button (#111114)
* feat(ui): add hold-to-record dictation

* chore: drop changelog edit (release generation owns CHANGELOG.md)

* fix(ui): preserve composer controls after rebase

* fix(ui): preserve dictation pointer capture

* fix(ui): finish dictation on cancellation

* docs(ui): note transcription-relay event envelope contract at the dictation parser

* test(ui): complete realtime-talk-input mock factory for shared-registry workers

* test(ui): isolate composer mocks across workers

* test(ui): isolate shared chat module mocks

* test(ui): type shared chat spies
2026-07-19 00:03:09 -07:00
Alix-007
aaacee8166 fix(ui): prevent stale sidebar attention refreshes (#111061) 2026-07-18 23:37:20 -07:00
jincheng-xydt
5a81e9fa81 fix(agents): preserve ANSI sanitizer state across bash chunks (#103706)
* fix(agents): preserve ANSI sanitizer state across bash chunks

* fix(agents): harden streaming ANSI sanitization

Keep incremental parser state in the canonical terminal owner, avoid a second sanitizer pass, and leave OutputAccumulator and public terminal APIs unchanged.\n\nCo-authored-by: Jicheng Xu <xu.jincheng@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-18 23:33:40 -07:00
Peter Steinberger
ee0e3a4d47 refactor(channels): share durable ingress monitor (#111214)
* refactor(telegram): share durable ingress monitor

* refactor(whatsapp): share durable ingress monitor

* refactor(feishu): share durable ingress monitor

* refactor(qqbot): share durable ingress monitor

* refactor(zalouser): share durable ingress monitor

* refactor(nostr): share durable ingress monitor
2026-07-18 23:33:24 -07:00