Commit Graph

139 Commits

Author SHA1 Message Date
Peter Steinberger 3852a75803 improve(extensions): reduce test hotspot setup costs [AI] (#109346)
* test(extensions): reduce hotspot setup costs

* test(signal): track reply reset usage

* test(signal): group reply state helpers

* test(signal): isolate reply author state

* test(active-memory): preserve fixture isolation
2026-07-16 16:07:11 -07:00
Peter Steinberger a168ffcd5e improve: reduce extension monitor test import overhead (#109345)
* test(feishu): consolidate monitor helper coverage

* test(imessage): consolidate monitor helper coverage

* test(mattermost): consolidate monitor helper coverage

* test(zalo): consolidate monitor lifecycle coverage

* test(imessage): classify merged monitor support correctly
2026-07-16 15:20:08 -07:00
Peter Steinberger c46cc1e2d4 refactor(channels): unify inbound replay protection (#109117)
* refactor(channels): unify inbound replay-guard orchestration on shared factory

* fix(plugin-sdk): bind replay-guard settlement to claim handles

* fix(plugin-sdk): claim-handle settlement (rest)

* fix(discord): align skipped queue cleanup

* style(feishu): simplify bot menu claim settlement

* refactor(plugin-sdk): extract replay dedupe contracts
2026-07-16 08:46:34 -07:00
Peter Steinberger 5579f009fb refactor(channels): converge internal group-policy drift onto the scope tree (#107865)
* refactor(channels): converge internal group-policy drift onto the scope tree

* chore(plugin-sdk): refresh API baseline hash for scope-key exports

* fix(config): avoid shadowing the scopeKey export in the case-insensitive lookup
2026-07-14 18:30:04 -07:00
Peter Steinberger d91073eb1d refactor(plugins): remove internal test exports (#107859)
* refactor(imessage): privatize reply-cache reset

* refactor(vault): privatize CLI test helpers

* refactor(voice-call): privatize test seams

* refactor(zalo): privatize monitor test seams

* refactor(zalouser): test through provider boundaries

* chore(deadcode): refresh export baseline
2026-07-14 17:13:38 -07:00
Peter Steinberger f81f9d8570 chore: enforce max-lines suppression ratchet (#107315)
* ci: enforce max-lines suppression ratchet

* chore: prune stale max-lines suppression

* fix: close max-lines ratchet enforcement gaps

* fix: harden max-lines ratchet checks

* fix(ci): satisfy max-lines ratchet checks

* style: format max-lines declarations

* fix(ci): match oxlint suppression grammar

* test: isolate max-lines git fixtures

* chore: prune resolved max-lines debt

* test: skip newline path fixture on Windows

* fix: harden max-lines suppression ratchet

* chore: refresh max-lines baseline

* fix: close max-lines ratchet bypasses

* fix: derive ratchet base from PR merge tree

* fix: support older Git in staged ratchet

* fix: align max-lines declarations and baseline

* chore: refresh max-lines baseline for current main

* fix: exclude generated wizard locales from max-lines

* chore: prune resolved max-lines debt
2026-07-14 09:27:02 -07:00
Peter Steinberger 834810b3d6 refactor(imessage): trim internal exports (#107196)
* refactor(imessage): trim internal exports

* test(imessage): type inbound dispatch mock
2026-07-13 23:05:24 -07:00
Peter Steinberger 8a2da4b1bf fix(imessage): narrow recovered projection before use (#106022) 2026-07-12 21:43:17 -07:00
zw-xysk 6ef3e87583 fix(imessage): apply authoritative projection in anchorless recovery (#104218)
* fix(imessage): apply authoritative projection in anchorless recovery

Rebuild on latest main with imessage-only changes: authoritative history
projection (optional destination_caller_id), whole-path inbound proof tests,
and no unrelated restart.ts diff.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(imessage): restore recovery cursor API and format tests

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(imessage): add monitor whole-path L3 proof for #104136 recovery

Exercise issue synthetic payloads through monitorIMessageProvider for
authoritative remote reply routing and from-me suppression before dispatch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(imessage): clear stale destination_caller_id when history omits it

Exact-GUID history is authoritative for destination_caller_id. When the
history row omits that outgoing-only field, clear any stale notification
value instead of inheriting it into the recovered inbound projection.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: sm <sm@zwdeMacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 21:22:05 -07: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
Wynne668 baa009e26f fix(imessage): cap per-chat group-allowlist warn-once cache (#102658)
* fix(imessage): cap per-chat group-allowlist warn-once cache

Replace unbounded perChatWarned Set with createDedupeCache(maxSize=512)
to keep long-running iMessage monitor memory stable. The cache grows
with every distinct group chat the gateway sees; without a cap it can
accumulate entries indefinitely.

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

* fix(imessage): prove warning cache eviction

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-09 16:16:03 +01:00
Vincent Koc ef91bb370b refactor(imessage): localize internal helper symbols (#101936) 2026-07-07 18:00:44 -07:00
Vincent Koc b88f80940a refactor: localize internal implementation symbols (#101831) 2026-07-07 10:23:36 -07:00
Vincent Koc 3d206140f3 refactor: localize internal implementation constants (#101758) 2026-07-07 08:34:13 -07:00
Vincent Koc 17777b3a9f refactor: remove proven dead code across runtime surfaces (#100823)
* refactor: remove unused internal helpers

* refactor(ui): remove unused compatibility helpers

* refactor(android): remove unused talk cleanup shim

* refactor(android): remove orphaned legacy UI

* refactor: remove unused private exports

* refactor(docs-i18n): remove dead matcher and satisfy lint
2026-07-06 03:31:19 -07:00
clawSean d84aabf642 feat: add channel pairing request hook (#97733)
* feat: add channel pairing request hook

* fix(plugin-sdk): keep pairing hook types internal

* docs: regenerate docs map

* docs: regenerate docs map

* docs: regenerate docs map

* fix(plugin-sdk): keep pairing hook types internal

* fix(plugin-sdk): keep pairing hook types internal

* docs: refresh plugin hook docs

* docs: refresh plugin hook docs

* docs: refresh plugin hook docs

---------

Co-authored-by: clawSean <260045960+clawSean@users.noreply.github.com>
Co-authored-by: Omar Shahine <omarshahine@users.noreply.github.com>
2026-07-05 19:11:58 -07:00
Omar Shahine 91f5a65df9 fix(imessage): plain-send fallback for threaded replies + db-scoped recovery cursor (#99638) (#100446)
* fix(imessage): retry unthreaded when a threaded reply is unsupported (#99638)

* fix(imessage): scope downtime-recovery cursor to the watched database (#99638)

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-07-05 14:58:38 -07:00
Peter Steinberger c00d79a1d1 fix(imessage): false group drop-all startup warning when groupAllowFrom is set without groups (#100046)
* fix(imessage): only warn about empty group allowlist when messages actually drop

With groupPolicy="allowlist", a non-empty effective groupAllowFrom admits
group messages even when channels.imessage.groups is empty (senderFilterBypass
in src/config/group-policy.ts), so the startup warning "Every inbound group
message will be dropped" fired as a false positive for that configuration.

The warning now mirrors the runtime gate's effective sender allowlist (same
allowFrom fallback + legacy chat-target merge) and fires only when both the
groups registry and the effective group sender allowlist are empty - the only
startup-provable drop-all config. The message now names groupAllowFrom as the
fix, since adding groups entries alone leaves the sender gate blocking.

Docs: describe the two group gates' warnings separately with per-warning
remedies instead of implying both fire for the same config.

* docs(imessage): format migration guide

* fix(imessage): warn on empty group sender allowlist

* docs: move iMessage fix to unreleased
2026-07-05 04:21:22 -07:00
Peter Steinberger 73fc0f5c5f fix(channels): expose inbound media download failures [AI-assisted] (#100119)
* fix(channels): expose inbound media download failures

* fix(msteams): correlate attachment references
2026-07-04 20:28:52 -04:00
Dallin Romney c2fc7aa28a refactor: consolidate exact keyed async queues (#99691)
* refactor: consolidate keyed async queues

* chore: refresh merge-ref CI

* refactor: encapsulate keyed queue ownership
2026-07-04 16:45:07 -07:00
LZY3538 e272da5c55 fix: keep always-on group fallback messages in dispatch (#99506)
* fix(auto-reply): honor always-on plugin fallback groups

* fix(channels): preserve mention policy across bindings

* test(discord): type mention policy dispatch context

* test(discord): type mention policy dispatch context

* fix(whatsapp): preserve effective mention policy

* fix(whatsapp): preserve effective mention policy

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
2026-07-03 07:26:45 -07:00
Omar Shahine 8c7ac9b9b9 fix(imessage): native poll vote-cue, echo suppression, and same-sender comment fold
Makes native iMessage polls behave correctly end to end.

What changed:
- Inbound polls render with a numbered-options vote cue so the agent casts a native vote instead of answering the poll in prose.
- poll-vote resolves the poll reference from pollId/pollGuid/messageId and now defaults to the current inbound poll message when the model omits it; still errors when no reference exists.
- poll-vote echo suppression is session-scoped, so the redundant spoken answer is dropped across the separate poll and comment runs.
- A poll's inline-reply caption is folded (not delivered as a standalone question) only when the poll creator and reply sender are both known and equal; unknown/mismatched sender falls through to the normal inbound decision gate, so no inbound reply is silently dropped.

Evidence:
- 64 passing tests in the poll suites (poll-comment, poll-render, actions), incl. sender fail-closed regressions; pnpm build clean.
- Two Codex autoreviews clean (patch is correct); ClawSweeper re-review rated it platinum hermit.
- Live-verified on macOS 26.4.1 on the deployed gateway: poll "What color pill?" -> native vote delivered with a 7333-byte payload, caption folded, zero echo.

Note: vote delivery also depends on the imsg vote-stamp fix (openclaw/imsg#150); OpenClaw ships ahead of imsg per owner decision.
2026-07-01 21:30:29 -07:00
Omar Shahine c92c33d108 feat(imessage): native poll support — create, read, vote (#98421)
* feat(imessage): add native poll action

Wire the imsg CLI 'poll send' bridge command into the iMessage channel
message-tool action surface, mirroring the existing Discord poll action.
Adds the 'poll' action (gate: polls), a sendPoll runtime, selector-gated
capability advertisement (pollPayloadMessage), config type + zod schema,
regenerated channel metadata, docs, and tests.

* feat(imessage): read inbound polls, vote, and suppress vote echo

Builds on the native poll send action:

- Inbound polls now render to the agent as a readable line (question +
  numbered options + tallies) instead of the raw 0xFFFD balloon placeholder,
  so a received poll no longer reads as an empty message.
- New `poll-vote` action casts a vote via `imsg poll vote`, resolving a
  1-based option index / text / UUID to the poll's option identifier.
- message_tool_only echo guard: the model tends to narrate its choice in a
  text reply right after voting ("Blue."), which is redundant since the vote
  shows on the poll. A new `poll_vote_echo` suppression reason (alongside
  inbound_metadata_echo / internal_runtime_context_echo) drops a send/reply
  that exactly restates the just-cast vote, using the option label imsg
  returns. Extra content passes through untouched.

* fix(imessage): gate poll-vote on imsg poll.vote rpc capability

Released imsg carries the pollPayloadMessage selector (poll create) but
predates the poll.vote CLI/RPC. Gating both poll and poll-vote on that
selector alone would advertise a vote action the released CLI rejects.
Gate poll-vote additionally on the advertised poll.vote rpc method so this
plugin can ship ahead of the imsg release.

* fix(imessage): enforce poll.vote capability at execution, not just discovery

Codex review flagged the discovery gate as bypassable: a caller that already
knows action=poll-vote skips describeMessageTool and reaches handleAction
directly. Add the same imessageRpcSupportsMethod(status, 'poll.vote') check in
the poll-vote execution path (after assertPrivateApiEnabled), so a direct
dispatch on released imsg fails closed with a clear message instead of an
opaque CLI rejection. Adds a negative handleAction test.

* fix(imessage): harden native poll support

* fix(message): validate targets before channel discovery

* fix(message): validate targets before channel discovery

---------

Co-authored-by: Omar Shahine <lobster@users.noreply.github.com>
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-01 10:48:32 +01:00
Alix-007 076da567f4 fix(imessage): recognize MiniMax mm: reasoning tags in reflection guard (completes #93767) (#93820)
* fix(imessage): detect MiniMax mm: namespaced reasoning tags in reflection guard

The iMessage reflection guard's THINKING_TAG_RE only matched bare
`<think>/<thought>` tags, so MiniMax's `<mm:think>` namespaced reasoning
tags (and Anthropic's `antml:` prefix) were not recognized as reflected
assistant content. When such reasoning leaked back inbound, the guard
treated it as a normal user message instead of dropping it, allowing
recursive echo amplification.

Accept the known reasoning-tag namespace prefixes `(?:antml:|mm:)?` on
the think/thought alternatives, mirroring the shared reasoning-tag
contract in src/shared/text/reasoning-tags.ts (PR #93767). This is the
remaining channel-monitor complement of #93767 for the iMessage path;
the optional prefix is a pure recognition enhancement with no change to
the bare-tag, code-region exemption, or other reflection patterns.

Verified by driving the real detectReflectedContent export:
  <mm:think>secret</mm:think>visible  -> isReflection=true (was false)
  <think>...</think>       -> isReflection=true
  <think>secret</think>                -> isReflection=true (no regression)
  "thinking about minimax algorithms"  -> isReflection=false (no false-positive)
  `<mm:think>x</mm:think>` in code     -> isReflection=false (code exemption kept)

* chore: retrigger CI for real behavior proof check

* refactor(imessage): tighten reflection guard coverage

---------

Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-06-30 12:41:42 -07:00
Vincent Koc c16bb8725a fix(channels): keep text previews UTF-16 safe 2026-06-30 11:04:32 -07:00
llagy009 352f47f888 fix(imessage): coalesce merged text on UTF-16 code-point boundary 2026-06-28 19:08:39 -07:00
ly-wang19 770b19f496 fix(imessage): only strip standalone role-turn markers, not prose ending in a role word (#96392)
ROLE_TURN_MARKER_RE anchored only the end of the line (\b...:\s*$), so any
outbound line that merely ended with 'user:'/'system:'/'assistant:' was
truncated — e.g. 'Please send this reply to the user:' lost its last word.
Anchor the marker to the whole line so only a standalone leaked turn marker
(its own line) is stripped; standalone-marker behavior is unchanged.

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-25 16:05:36 +08:00
Omar Shahine f2af052cee perf(imessage): show typing sooner for slow replies (#95621)
Merged via squash.

Prepared head SHA: 65e9ad10fd
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-24 15:31:48 -07:00
ly-wang19 81e1ec467c fix(imessage): strip leading echo corruption markers in the persisted echo cache (#94442)
The persisted iMessage echo-dedupe cache normalized text with CRLF->LF + trim only, not the leading attributedBody corruption-marker stripping the in-memory echo cache applies (#93511). The persisted 12h cache is the only matcher once the 4s in-memory text TTL expires, so a delayed reflected own-message echo whose text decoded with a leading NUL/replacement/BOM marker did not match the clean stored send -- the agent's own message was re-ingested as fresh inbound, causing a self-reply loop.

Extract the marker-stripping into a leaf module shared by both echo caches (the in-memory cache already imports the persisted one, so importing back would be a cycle) and apply it in the persisted normalizeText, so both caches strip identically.

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-22 19:07:45 +00:00
Omar Shahine a0714a3d68 fix(imessage): keep split-send coalescing opt-in (#93143)
Merged via squash.

Prepared head SHA: 7fc3eca084
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-21 17:12:21 -07:00
Vincent Koc 4a0e376d1f fix(imessage): normalize leading NUL echo-cache prefixes (#93511)
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: jason <3031622+drvoss@users.noreply.github.com>
2026-06-16 14:17:42 +08:00
Vincent Koc 54288a1e7c test(imessage): align echo cache assertion 2026-06-10 15:20:05 +09:00
Omar Shahine 6c045c5ca3 fix(imessage): surface inbound startup diagnostics (#91785)
Merged via squash.

Prepared head SHA: 597684c365

Proof:
- Focused tests, lint/type/diff checks, and autoreview passed before merge.
- ClawSweeper re-review marked proof and patch quality platinum after lobster live monitor proof.
- Maintainer accepted the diagnostics-only default-log privacy/noise tradeoff.

Lobster proof id: openclaw-lobster-live-monitor-proof-ada22165-6306-46b6-8ed0-6c94fcab6bbc

Reviewed-by: @omarshahine
2026-06-09 19:10:09 -07:00
Omar Shahine bfccbc3fee fix(imessage): harden outbound send transport (#91783)
Merged via squash.

Prepared head SHA: 39ea25767b

Proof:
- Focused tests, docs/config generation, lint/type/doc checks passed before merge.
- ClawSweeper re-review marked proof and patch quality platinum after lobster live send proof.
- Maintainer accepted the `channels.imessage.sendTransport` config surface and compatibility-risk tradeoff.

Lobster proof id: openclaw-lobster-live-proof-c74895c2-b629-4bb0-abcb-e6521069b3d8

Reviewed-by: @omarshahine
2026-06-09 19:09:15 -07:00
colmbrogan 7e3100a120 fix(imessage): persist echo markers before send (#88969)
Persist short-lived pending iMessage echo markers before bridge sends so self-chat reflected rows cannot race ahead of post-send echo persistence. Keep monitor cache writes post-send, keep pending text out of generic echo matching, and observe skipped from-me catchup rows for self-chat dedupe.\n\nThanks @colmbrogan.
2026-06-08 22:25:25 -07:00
Julian Missig a7847ac484 fix(imessage): honor block streaming config (#91449)
Merged via squash.

Prepared head SHA: 6e4e04fb2d
Co-authored-by: jmissig <1448107+jmissig@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-08 12:07:03 -07:00
Omar Shahine fc6400ede3 fix(imessage): always-on inbound recovery and dedupe (#91335)
* feat(imessage): always-on inbound recovery, deprecate catchup

Replaces the opt-in catchup subsystem with always-on inbound replay
protection that brings iMessage in line with the other channels, and
fixes #89237 (stale backlog dispatched as fresh after bridge recovery).

- New inbound-dedupe.ts: persistent claimable GUID dedupe (claim/commit/
  release) plus a stale-backlog age fence that suppresses live rows whose
  send date is materially older than arrival (logged, never silent).
- monitor-provider: claim at ingestion, carry the exact claimed key on the
  debouncer entry, commit on successful flush / release on dispatch failure
  (per-unit so a coalesced bucket cannot strand a sibling claim). Keeps the
  local startup since_rowid watermark so startup-window rows are not skipped.
- Deprecate catchup: delete catchup.ts + catchup-bridge.ts, remove the
  channels.imessage.catchup schema, cursor migration, and config-guard nag.
  Back-compat: strip the retired key before validation; new imessage doctor
  contract reports + removes it on doctor --fix.
- Docs updated for the new recovery model.

Net -947 prod LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(imessage): recover downtime messages via since_rowid replay

Builds downtime recovery on the new inbound dedupe instead of restoring the
old catchup subsystem. On startup the monitor passes the last dispatched rowid
(a persisted per-account cursor) to imsg watch.subscribe as since_rowid, so imsg
replays the messages that landed while the gateway was down, then tails live.
The GUID dedupe drops anything already handled, so no cursor/retry bookkeeping
is needed.

- recovery-cursor.ts: minimal persisted per-account lastDispatchedRowid.
- monitor-provider: since_rowid = cursor (capped to the most recent
  IMESSAGE_RECOVERY_MAX_ROWS); split the age fence on the startup rowid boundary
  so replayed rows (<= boundary) use the wider recovery window and live rows
  (> boundary) keep the tight #89237 fence; advance the cursor on commit.
- Local only: remote SSH cliPath cannot read chat.db, so it tails from the
  current rowid (suppress-and-move-on) as before.

Restores missed-message recovery that the catchup removal dropped, with no
config and a fraction of the old LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): make recovery cursor advance failure- and suppression-safe

Addresses two cursor-state regressions in the downtime-recovery path:

- Failed replay rows could be skipped forever: a released (failed) row keeps
  its dedupe claim for retry, but a later successful row in the same flush
  advanced the cursor past it, so the next startup's since_rowid skipped it.
  Hold a per-session floor at the lowest released rowid and never advance the
  cursor past it.
- Suppressed live backlog could be re-delivered after a restart: a live row
  suppressed under the tight live fence was not recorded, so after a restart it
  fell under the wider recovery window (its rowid now below the new boundary)
  and was delivered. Commit its dedupe key on suppression so the recovery
  replay treats it as already handled.

Both caught by Codex autoreview. Adds regression tests for the floor and the
suppression record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): bound the GUID-less replay key length

Hash the composite fallback key's variable parts (conversation, sender,
created_at, text) so the key is length-bounded regardless of message text.
The persistent dedupe store already hashes keys internally, so this was not a
live overflow, but the bounded key removes the dependency on that and keeps the
fallback fail-open. Flagged by autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): recover downtime messages on remote cliPath setups too

The since_rowid replay runs over the imsg RPC client, so driving it from the
persisted recovery cursor (not the local chat.db boundary) makes downtime
recovery work for remote SSH cliPath gateways — the topology the old RPC-based
catchup served and that the rowid-boundary-only version regressed. Local setups
keep the wider, capped recovery window via the chat.db boundary; remote uses the
live age-fence window. Flagged by autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): seed recovery cursor from retired catchup cursor on upgrade

A one-time, self-cleaning migration: when the recovery cursor is empty on the
first startup after upgrade, seed it from the retired imessage.catchup-cursors
lastSeenRowid and consume the legacy entry. Without this a user who had catchup
enabled would not replay messages missed across the upgrade restart. Flagged by
autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): preserve catchup recovery on upgrade

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-08 16:54:10 +09:00
Omar Shahine 9caff5f873 fix(imessage): gate split-send coalescing on imsg balloon metadata with back-compat (#90858)
Gate iMessage same-sender DM split-send coalescing on imsg's structural
`balloon_bundle_id` URL-balloon marker (openclaw/imsg#137) instead of timing/
text-shape inference, with a session capability latch and a back-compat path:

- URL-balloon marker present -> merge (precise split-send).
- Build known to emit balloon metadata (session latch) -> keep non-marker
  buckets separate (the precision win).
- Build that never emits balloon metadata (older imsg) -> preserve the legacy
  unconditional merge, so split-send users do not regress to two turns.

Never merges more than shipped main already did. Verified live end-to-end: the
patched gateway, watching a real chat.db via an imsg #137 build, merged a real
iPhone-sent `Dump <url>` split-send into one turn. Client-side removal once imsg
coalesces upstream is tracked in #91243 (openclaw/imsg#141).

Closes #90795
2026-06-07 19:14:13 -07:00
Peter Steinberger 6868cde4d4 docs: document large extension sources 2026-06-04 21:40:44 -04:00
Omar Shahine 02192bd27f fix(imessage): keep typing active during tool work (#88948)
Keep iMessage native typing indicators alive through long tool-running gaps by bridging tool-start activity into the existing typing controller, while preserving typingMode and sendPolicy suppression semantics.

Real behavior proof was added from the live iMessage generated-image run: inbound id 5805, outgoing media reply id 5806, and requester-observed typing during the 84s tool path.

Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @lobster
2026-06-01 16:45:46 -07:00
Peter Steinberger 27dde7a4d6 chore(lint): enable stricter error rules 2026-06-01 01:12:21 +01:00
Peter Steinberger 3491834d49 Migrate iMessage monitor state to SQLite (#88797)
* refactor: move imessage monitor state to sqlite

* test: use OpenClaw temp root in iMessage state helper

* test: avoid pending promise lint in chat tests

* test: harden gateway ci flakes

* test: align session list merge expectation
2026-06-01 00:19:51 +01:00
colmbrogan 421ad93203 fix(imessage): tolerate self-chat timestamp skew
Fixes iMessage self-chat reflection dedupe when reflected rows arrive with sub-second `created_at` skew, while keeping ambiguous normal-DM suppression exact-match only.

Maintainer follow-ups scoped skew tolerance to confirmed self-chat remembered rows and bounded cache cleanup so TTL-only expiry cannot leave the insertion-order queue growing indefinitely.

Verification:
- `node scripts/run-vitest.mjs extensions/imessage/src/monitor/self-chat-cache.test.ts extensions/imessage/src/monitor/self-chat-dedupe.test.ts extensions/imessage/src/monitor/inbound-processing.test.ts`
- `pnpm oxlint extensions/imessage/src/monitor/self-chat-cache.ts extensions/imessage/src/monitor/self-chat-cache.test.ts extensions/imessage/src/monitor/self-chat-dedupe.test.ts`
- `git diff --check origin/main...HEAD`
- autoreview clean on branch tip
- CI run 26727192244 green; Real behavior proof run 26727196218 green

Co-authored-by: Colm O Brogain <73212305+colmbrogan@users.noreply.github.com>
2026-06-01 00:14:47 +01:00
Peter Steinberger 304e2c83c0 chore(lint): enable stricter oxlint rules 2026-05-31 18:59:02 +01:00
Ayaan Zaidi 87664ed096 Fix iMessage startup watch replay (#88406)
* fix(imessage): start watch after existing local rows

* test(imessage): cover stale startup watch rows

* fix(imessage): keep watch startup tolerant of db probes

* fix(imessage): watermark default local watch startup

* fix(imessage): tolerate missing sqlite startup probe

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 08:33:22 +01:00
Peter Steinberger adf981de89 fix(imessage): tolerate invalid catchup cursor timestamps 2026-05-30 06:46:09 -04:00
Kevin Lin dc4f3b57cf fix(imessage): preserve SMS approval reply routes
Preserve iMessage SMS reply routes for approval replies so a direct SMS /approve response can acknowledge and return results to the same SMS conversation.

Verification: gateway-only build, extension type checks, CI build-artifacts/check-prod-types/check-test-types/check-lint/check-additional-extension-package-boundary, and live prod iMessage SMS approval proof. checks-node-core-fast was waived by maintainer request after unrelated flaky failures in non-iMessage tests.
2026-05-29 19:00:45 -07:00
Kevin Lin 40bca6d8bb fix(imessage): suppress duplicate native exec approvals
Fix iMessage native exec approval routing so approval prompts bind to the sent GUID without duplicate sends after RPC timeout. Also keeps chat.db GUID recovery on the local imsg path while avoiding local DB recovery for configured or detected SSH wrappers.

Thanks @kevinslin.
2026-05-27 23:55:28 +01:00
Peter Steinberger 1507a9701b refactor: centralize inbound supplemental context
* refactor: centralize inbound supplemental context

* refactor: trim supplemental finalizer typing

* docs: clarify supplemental context projection

* refactor: move inbound finalization into core

* refactor: simplify channel inbound facts

* refactor: fold supplemental media into inbound finalizer

* refactor: migrate channel inbound callers to builder

* docs: mark inbound finalizer compat types deprecated

* refactor: wire runtime turn context builder

* refactor: replace channel turn runtime API

* fix: respect discord quote visibility

* fix: avoid deprecated line dispatch helper

* refactor: deprecate channel message SDK seams

* docs: trim channel outbound SDK page

* test: migrate irc inbound assertion

* refactor: deprecate outbound SDK facades

* refactor: deprecate channel helper SDK facades

* refactor: deprecate channel streaming SDK facade

* refactor: move direct dm helpers into inbound SDK

* chore: mark legacy test-utils SDK alias deprecated

* refactor: remove unused allow-from read helper

* refactor: route remaining channel dispatch through core

* refactor: enforce modern extension SDK imports

* test: give slow image root tests more time

* ci: support node fallback on windows

* fix: add transcripts tool display metadata

* refactor: trim legacy channel test seams

* fix: preserve channel compat after rebase

* fix: keep deprecated channel inbound aliases

* fix: preserve discord thread context visibility

* fix: clean final rebase conflicts

* fix: preserve channel message dispatch aliases

* fix: sync channel refactor after rebase

* fix: sync channel refactor after latest main

* fix: dedupe memory-core subagent mock

* test: align clickclack inbound dispatch assertions

* fix: sync plugin sdk api hash after rebase

* fix: sync channel refactor after latest main

* fix: sync plugin sdk api hash after rebase

* fix: sync plugin sdk api hash after latest main

* test: remove stale inbound context awaits
2026-05-27 09:26:06 +01:00
Omar Shahine 3452382cc0 fix(imessage): seed direct DM history (#86706)
* fix(imessage): seed direct DM history

* docs(imessage): clarify DM history override seeding

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-25 22:38:32 -07:00