Skip to content

fix: emit bridge typing events from chat item changes - #378

Merged
steipete merged 1 commit into
openclaw:mainfrom
logananderson:logan/typing-events
Nov 5, 2026
Merged

steipete merged 1 commit into
openclaw:mainfrom
logananderson:logan/typing-events

Conversation

@logananderson

@logananderson logananderson commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What

imsg watch --bb-events and bridge.events.subscribe (both read .imsg-events.jsonl) never receive the started-typing / stopped-typing events that docs/watch.md, docs/bridge.md, docs/rpc.md and section 5 of Tests/imsgTests/README-live.md ("typing events streaming") describe. The observer that writes them, added with the bridge port in #100, has never fired:

  1. It observes @"IMChatItemsDidChangeNotification". IMCore posts @"__kIMChatItemsDidChangeNotification" (the value of its IMChatItemsDidChangeNotification constant), so the block never runs.
  2. With the name corrected it still emits nothing. It reads inserted items from __kIMChatValueKey or inserted, and the notification has neither. Observed keys, every time: __kIMChatItemsOldItems and __kIMChatItemsNewItems (the chat's items before and after), __kIMChatItemsInserted and __kIMChatItemsRemoved (index sets into those lists), __kIMChatItemsReload and __kIMChatItemsRegenerate.

While someone else is typing, the chat's items include an IMTypingChatItem. The observer now checks the old and new lists for one and emits started-typing when it appears, stopped-typing when it goes. It looks through the list rather than at the last item: a chat whose transcript hasn't been opened in Messages, and the group chat below, end in an IMReportSpamChatItem, with the typing item earlier in the list. IMCore also has IMGroupTypingChatItem, a subclass of IMTypingChatItem (checked at runtime on macOS 26), which the class check covers. The GUID is read with the existing safelyReadObjectSelector, and only when there is an event.

stopped-typing follows the transcript indicator: it comes when the message arrives, when the sender clears the draft, or when the indicator expires (90 seconds after an abandoned draft in the run below). The local account's own typing isn't reported. If a macOS version lacks either list or the IMTypingChatItem class, nothing is emitted, which is the behavior today.

Tests

Tests/IMsgHelperTests/TypingEventTests.m posts the real notification through the registered observers and reads an isolated event log, so it compiles unchanged against main. Failing first on main (5af52a0):

FAIL: A typing item appearing or leaving emits, got (
)
FAIL: A typing item still counts when a banner item follows it
FAIL: A group typing item counts as typing
Bridge typing event tests: 3 failure(s)

Live proof

macOS 26.6.2 (25G83) arm64 VM, iPhone on the other side. One release CLI (0.20.0; this change doesn't touch the CLI), with the injected helper swapped between main (5af52a0) and this commit and Messages relaunched after each swap. Both runs in the same 1:1 chat: type without sending, clear the draft, then type and send. Phone numbers replaced with +1555…, message text omitted.

imsg watch --bb-events --json, main helper (its .imsg-bridge.log shows 2026-11-02T07:20:25Z bootstrap complete, logged after registerEventObservers()):

{"chat_guid":"any;-;+1555…","created_at":"2026-11-02T07:21:39.628Z","is_from_me":false,"text":"…", …}

This commit:

{"event":"started-typing","ts":"2026-11-02T07:55:58Z","kind":"bridge-event","data":{"chatGuid":"any;-;+1555…"}}
{"event":"stopped-typing","ts":"2026-11-02T07:56:11Z","kind":"bridge-event","data":{"chatGuid":"any;-;+1555…"}}
{"event":"started-typing","ts":"2026-11-02T07:56:29Z","kind":"bridge-event","data":{"chatGuid":"any;-;+1555…"}}
{"event":"stopped-typing","ts":"2026-11-02T07:56:30Z","kind":"bridge-event","data":{"chatGuid":"any;-;+1555…"}}
{"chat_guid":"any;-;+1555…","created_at":"2026-11-02T07:56:30.992Z","is_from_me":false,"text":"…", …}

07:56:11 is the cleared draft; 07:56:30 is the send.

Then, with the same watch running, the helper's typing action was sent to that chat for 8 seconds over the bridge. The iPhone showed the indicator and the watch printed nothing:

07:57:19Z handleStartTyping: setLocalUserIsTyping:YES beforeIsTyping=0 afterIsTyping=1 chatClass=IMChat
07:57:27Z handleStopTyping: chatGuid=any;-;+1555…

Four more runs on this commit's code plus a temporary log of each notification's lists:

run events
1:1 chat never opened since relaunch (a group selected) started 07:43:38, stopped 07:43:52 (clear), started 07:44:07, stopped 07:44:09 (send)
local account sends while the peer types started 07:48:44; the local send at 07:48:54 left the typing item in place, no event; stopped 07:49:00 (peer's send)
draft abandoned, phone locked started 07:49:27, stopped 07:50:57 (expired), started 07:51:25, stopped 07:51:28 (clear), started 07:51:30, stopped 07:51:32 (send)
group chat started 07:52:42, stopped 07:52:56 (clear), started 07:53:10, stopped 07:53:13 (send)

Two of those notifications, trimmed (typing is whether the list contains an IMTypingChatItem):

07:43:38Z event=started-typing OldItems=(count=0 typing=0) NewItems=(count=5 last=IMReportSpamChatItem typing=1)
07:50:57Z event=stopped-typing OldItems=(count=59 last=IMTypingChatItem typing=1) NewItems=(count=58 last=IMTextMessagePartChatItem typing=0)

Checks

make lint         Done linting! Found 0 violations, 0 serious in 303 files.
make test         node --test: pass 2, fail 0
                  helper suites: 0 failure(s), including Bridge typing event tests
                  ✔ Test run with 505 tests in 5 suites passed
                  ✔ Test run with 350 tests in 5 suites passed
git diff --check  clean

Not run: macOS 27, and a live run on macOS 14 or 15. On macOS 15.6.1 the dyld shared cache contains __kIMChatItemsDidChangeNotification, __kIMChatItemsNewItems and IMTypingChatItem, but I have no working iMessage number on that machine.

@clawsweeper

clawsweeper Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. labels Nov 2, 2026
@clawsweeper

clawsweeper Bot commented Nov 2, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge.

What this changes

The PR emits bridge typing events when remote typing indicators appear or disappear in IMCore chat-item lists.

Example: A participant types and clears a draft while imsg watch --bb-events --json runs.

  • Before: No typing events appear.
  • After: started-typing appears when typing begins and stopped-typing appears when the draft is cleared.

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) Focused contract repair with shipped CLI proof, meaningful regression coverage, unchanged event format, and no identified merge blocker.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The contributor's live macOS 26.6.2 run exercises the changed injected observer through imsg watch --bb-events --json with an iPhone peer, showing start/stop transitions after the fix, including draft clearing, sending, expiry, group chats, and local-typing exclusion.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Bridge watchers receive no remote typing indicators despite the documented event-stream contract.
Reason: The patch restores an existing promised capability with a narrow producer-side change and directly relevant live evidence.

Merge readiness

✅ Ready for maintainer review

Keep this PR: it restores documented typing events that remain broken on main, with focused regression coverage and convincing live CLI proof; no actionable defects found.

Priority: P2
Reviewed head: 2c1e18b67162f85771d70c95f282ac2115382b29

Before merge

None.

Findings

None.

Agent review details

How this fits together

The injected Messages helper translates IMCore notifications into JSONL events, which the CLI watch command and RPC bridge subscriptions consume through the shared event tailer.

flowchart LR
  A[Remote participant typing] --> B[IMCore chat item notification]
  B --> C[Injected helper observer]
  C --> D[Bridge JSONL event log]
  D --> E[Shared event tailer]
  E --> F[CLI watch]
  E --> G[RPC bridge subscription]
Loading

Technical review

Best possible solution:

Land the focused observer repair while preserving the existing event writer, payload, and consumers.

Do we have a high-confidence way to reproduce the issue?

Main's observer mismatch is source-verifiable and supported by the contributor's before/after CLI output and base-failing regression test; this read-only review did not execute target code.

Is this the best way to solve the issue?

Comparing old/new typing-item presence fixes both the notification mismatch and incorrect payload interpretation at the existing shared producer, without timers, duplicate state, or consumer-specific workarounds.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 5af52a029b3d.

Provenance checked

  • Inbound typing observer keeps the original intent (c56c24d: The original bridge added inbound typing events; the later private-module refactor preserved that intent. GitHub source retrieval verified the original observer after local historical-content searches encountered unavailable blobs.)
  • Bridge event documentation keeps the original intent (1760347: Bounded bridge subscriptions expose the existing event log through RPC while preserving best-effort, nonresumable semantics; the added paragraph clarifies typing-event scope without changing that contract.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

Likely related people:

  • Peter Steinberger: Raw commit cb58dec adds Sources/IMsgHelper/EventObservers.inc:6 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: cb58dec17663; files: Sources/IMsgHelper/EventObservers.inc)

Labels

Label changes:

  • add P2: Restores missing remote typing events on the optional injected bridge without affecting core message delivery.
  • add proof: sufficient: Contributor real behavior proof is sufficient.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Label justifications:

  • P2: Restores missing remote typing events on the optional injected bridge without affecting core message delivery.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • proof: sufficient: Contributor real behavior proof is sufficient.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

Reviewed November 2, 2026, 3:22 AM ET / 08:22 UTC.

Emit incoming typing events when the chat transcript's typing indicator appears or disappears (openclaw#378, thanks @logananderson).

Co-authored-by: Logan Anderson <poggus@gmail.com>
@steipete
steipete force-pushed the logan/typing-events branch from 2c1e18b to 43b00f2 Compare November 5, 2026 06:11
@steipete
steipete merged commit e4935f6 into openclaw:main Nov 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants