@mapier/imsg-sdk 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/chat-background.d.ts +40 -0
- package/dist/gateway/chat-background.js +219 -0
- package/dist/gateway/chat-background.js.map +1 -0
- package/dist/gateway/edit-message.d.ts +17 -0
- package/dist/gateway/edit-message.js +70 -0
- package/dist/gateway/edit-message.js.map +1 -0
- package/dist/gateway/external-ids.d.ts +1 -0
- package/dist/gateway/external-ids.js +3 -1
- package/dist/gateway/external-ids.js.map +1 -1
- package/dist/gateway/fake.d.ts +13 -5
- package/dist/gateway/fake.js +83 -10
- package/dist/gateway/fake.js.map +1 -1
- package/dist/gateway/imsg.d.ts +6 -5
- package/dist/gateway/imsg.js +100 -39
- package/dist/gateway/imsg.js.map +1 -1
- package/dist/gateway/send-rich.d.ts +17 -0
- package/dist/gateway/send-rich.js +43 -0
- package/dist/gateway/send-rich.js.map +1 -0
- package/dist/gateway/types.d.ts +54 -11
- package/dist/gateway/types.js +143 -0
- package/dist/gateway/types.js.map +1 -1
- package/dist/imsg/rpc.d.ts +34 -9
- package/dist/imsg/rpc.js +32 -5
- package/dist/imsg/rpc.js.map +1 -1
- package/dist/imsg/status.js +17 -0
- package/dist/imsg/status.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/docs/api.md +1 -0
- package/docs/bridge-contract.md +7 -3
- package/docs/capability-matrix.md +4 -1
- package/docs/compatibility.md +4 -1
- package/docs/gateway-contract.md +326 -13
- package/docs/imcore_selectors.md +67 -33
- package/package.json +1 -1
package/docs/gateway-contract.md
CHANGED
|
@@ -57,6 +57,10 @@ interface Gateway {
|
|
|
57
57
|
groupPhoto: boolean;
|
|
58
58
|
groupParticipants: boolean;
|
|
59
59
|
namePhotoSharing: boolean;
|
|
60
|
+
chatBackgroundSet: boolean;
|
|
61
|
+
chatBackgroundRemove: boolean;
|
|
62
|
+
editMessage: boolean;
|
|
63
|
+
mentionFormatting: boolean;
|
|
60
64
|
};
|
|
61
65
|
subscribe(sinceId?: number): AsyncIterable<GatewayEvent>;
|
|
62
66
|
history(chatId: number, limit?: number): Promise<ImsgMessage[]>;
|
|
@@ -91,7 +95,11 @@ interface Gateway {
|
|
|
91
95
|
): Promise<SendResult>;
|
|
92
96
|
sendPoll(chatId: number, question: string, options: string[]): Promise<SendResult>;
|
|
93
97
|
sendRichLink(chatId: number, url: string): Promise<SendResult>;
|
|
94
|
-
editMessage(chatId: number, targetGuid: string, text: string): Promise<{
|
|
98
|
+
editMessage(chatId: number, targetGuid: string, text: string): Promise<{
|
|
99
|
+
ok: boolean;
|
|
100
|
+
skipped?: 'unchanged';
|
|
101
|
+
effectStarted?: boolean;
|
|
102
|
+
}>;
|
|
95
103
|
unsendMessage(chatId: number, targetGuid: string): Promise<{ ok: boolean }>;
|
|
96
104
|
deleteMessage(chatId: number, targetGuid: string): Promise<{ ok: boolean }>;
|
|
97
105
|
setTyping(chatId: number, on: boolean): Promise<{ ok: boolean }>;
|
|
@@ -106,6 +114,21 @@ interface Gateway {
|
|
|
106
114
|
skipped?: 'not-offered';
|
|
107
115
|
effectStarted?: boolean;
|
|
108
116
|
}>;
|
|
117
|
+
chatBackgroundStatus(chatId: number): Promise<{
|
|
118
|
+
chatId: number;
|
|
119
|
+
backgroundSet: boolean;
|
|
120
|
+
backgroundGuid: string | null;
|
|
121
|
+
} | null>;
|
|
122
|
+
setChatBackground(chatId: number, preset?: 'gradient'): Promise<{
|
|
123
|
+
ok: boolean;
|
|
124
|
+
backgroundGuid?: string;
|
|
125
|
+
effectStarted?: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
removeChatBackground(chatId: number, expectedGuid?: string): Promise<{
|
|
128
|
+
ok: boolean;
|
|
129
|
+
skipped?: 'no-background';
|
|
130
|
+
effectStarted?: boolean;
|
|
131
|
+
}>;
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
interface ReactionNote {
|
|
@@ -160,7 +183,16 @@ both the long-lived RPC process and its one-time `status --json` probe.
|
|
|
160
183
|
Patched verbs require the injected bridge's explicit selector markers
|
|
161
184
|
`emojiTapbackSend`, `stickerSend`, `groupPhotoUpdate`, `groupAddParticipant`, and
|
|
162
185
|
`groupRemoveParticipant`; Name & Photo requires both `namePhotoShouldOffer`
|
|
163
|
-
and `namePhotoShare`;
|
|
186
|
+
and `namePhotoShare`; chat backgrounds are gated per verb on
|
|
187
|
+
`chatBackgroundSetGradient` (setter + PosterBoard/ChatKit runtime) and
|
|
188
|
+
`chatBackgroundRemove` (setter + live-GUID getter), which the injected helper
|
|
189
|
+
already composes; edit message requires any one of `editMessageItemTranslation`
|
|
190
|
+
(macOS 27's five-argument `-[IMChat editMessageItem:…newPartTranslation:…]`,
|
|
191
|
+
imsg#3), `editMessageItem` (macOS 26) or `editMessage` (macOS 13) — the helper
|
|
192
|
+
uses the first present one; confirmed @mentions on `sendRich` require
|
|
193
|
+
`mentionFormatting`, which the helper advertises only when IMSharedUtilities'
|
|
194
|
+
confirmed-mention attribute key resolves at runtime (imsg#13); emoji
|
|
195
|
+
additionally requires the selected RPC binary
|
|
164
196
|
to report `rpc_features:["tapback.emoji"]`. That second half prevents a
|
|
165
197
|
patched dylib behind a stock CLI from false-advertising custom emoji support.
|
|
166
198
|
Missing status, malformed JSON, or absent markers fail closed.
|
|
@@ -175,10 +207,11 @@ guid by scanning `history(chatId, 30)` — the same 30-message ceiling
|
|
|
175
207
|
`recentReactions` already documents above — so a guid older than that is as
|
|
176
208
|
invisible to these methods as it is to `recentReactions`. `tapback` and
|
|
177
209
|
`deleteMessage` pre-check against that scan before firing, so they return
|
|
178
|
-
`{ ok: false }` without touching anything
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
`{ ok: false }` without touching anything, and so does `editMessage` (it also
|
|
211
|
+
refuses a message that is not `is_from_me`); `unsendMessage` fires the RPC
|
|
212
|
+
first and only finds the ceiling on the VERIFY pass, so a guid outside the
|
|
213
|
+
window still reports `{ ok: false }` but may have mutated the real row on the
|
|
214
|
+
way there — "no confirmable effect," not a guaranteed no-op. FakeGateway
|
|
182
215
|
must enforce the identical window regardless: finding the target guid by
|
|
183
216
|
scanning ALL of `this.messages` unconditionally would let the fake succeed
|
|
184
217
|
at targeting a message the real path can't reach (parity rule 2) — a
|
|
@@ -219,6 +252,46 @@ prevent.
|
|
|
219
252
|
this Gateway because `ImsgMessage` has no subject field. The `url` submode is
|
|
220
253
|
exposed separately as `sendRichLink` because openclaw treats URL and
|
|
221
254
|
text/subject/effect/reply as mutually exclusive modes.
|
|
255
|
+
`opts.textFormatting` is a list of ranges over the text in **UTF-16 code
|
|
256
|
+
units** (a JS string index; an emoji is two units): `styles`
|
|
257
|
+
(bold/italic/underline/strikethrough, macOS 15+, silently dropped on older
|
|
258
|
+
hosts), `effect` (a named macOS 27 text effect, resolved to Apple's id in
|
|
259
|
+
`resolveTextFormatting`), and `mention` — a **confirmed @mention**
|
|
260
|
+
(imsg#13, merged into `mapier/deploy` as `7c31772` — the pinned submodule
|
|
261
|
+
— `docs/send.md` "Text formatting and mentions"; the rules below mirror
|
|
262
|
+
`IMsgMentionFormatting.m` at that revision). `mention` is the EXACT `IMHandle.ID` of a participant of the
|
|
263
|
+
target chat as IMCore reports it (E.164 phone or the email), matched
|
|
264
|
+
byte-for-byte by the helper against the live participant list; the visible
|
|
265
|
+
text in the range must be the person's display name, never the handle (a
|
|
266
|
+
handle as visible text renders as a phone link with no highlight and no
|
|
267
|
+
notification, verified 2026-09-03 on an iPhone). Do not prefix the name
|
|
268
|
+
with "@": Messages' composer swallows the typed "@" and a genuine mention
|
|
269
|
+
row carries only the highlighted name (an "@" in the range simply renders
|
|
270
|
+
as text, as the 2026-09-03 SDK run showed). Formatting is **never
|
|
271
|
+
degraded**: the shared preflight `sendRichPreflight` /
|
|
272
|
+
`textFormattingRefusal` runs before the bridge is touched and refuses, with
|
|
273
|
+
a typed `SendResult.refused`, a mention range on a bridge without the
|
|
274
|
+
`mentionFormatting` marker (`mention-capability`), bad offsets on ANY range
|
|
275
|
+
— non-integer, zero length, outside the text, or splitting a surrogate pair
|
|
276
|
+
(`text-formatting-range`; the helper would otherwise silently drop a style
|
|
277
|
+
range and send plain text, which a caller cannot detect since formatting
|
|
278
|
+
is write-only), and a mention identifier that is not a bounded id or whose
|
|
279
|
+
visible text is the handle itself (`mention-identifier`). The helper
|
|
280
|
+
refuses a non-participant handle (`-32603` "Mention identifier is not a
|
|
281
|
+
participant handle of this chat", no row written), which `ImsgGateway`
|
|
282
|
+
classifies as `mention-not-participant`; a chat whose live participant
|
|
283
|
+
list is unreadable refuses every mention the same way. FakeGateway applies
|
|
284
|
+
the same preflight, checks membership against its chat record, and refuses
|
|
285
|
+
all mentions on an unreported-directory group. **A bare `ok:false` without
|
|
286
|
+
`refused` is NOT a no-send guarantee** (bridge timeout, transport loss);
|
|
287
|
+
only a `refused` result proves nothing was sent. Formatting and mentions
|
|
288
|
+
are write-only through this Gateway: `ImsgMessage` exposes neither, so the
|
|
289
|
+
smoke reads the confirmed-mention attribute (`__kIMMentionConfirmedMention`
|
|
290
|
+
followed by the handle) from `chat.db message.attributedBody` directly. The
|
|
291
|
+
`--only-mention` leg is opt-in via `--mention-handle` even in the full
|
|
292
|
+
Tier-2 run, because a confirmed mention notifies a real person.
|
|
293
|
+
**Host-verified 2026-09-03** through `ImsgGateway` on the shared Mac,
|
|
294
|
+
including the recipient-side highlight on an iPhone (see the ledger).
|
|
222
295
|
- **`sendAttachment(chatId, filePath, opts?)`** — `send.attachment`
|
|
223
296
|
(`handleSendAttachment`). Targets an **existing** chat only. `opts.audio:true`
|
|
224
297
|
sends the file as a native voice-note bubble; `opts.replyToGuid` sets an
|
|
@@ -265,8 +338,44 @@ prevent.
|
|
|
265
338
|
balloon lands as its own row later. `SendResult.guid` is therefore always
|
|
266
339
|
undefined here; FakeGateway must not return one either.
|
|
267
340
|
- **`editMessage(chatId, targetGuid, text)`** — `message.edit`
|
|
268
|
-
(`handleMessageEdit`).
|
|
269
|
-
|
|
341
|
+
(`handleMessageEdit`; imsg `docs/edit.md`). Capability-gated on the
|
|
342
|
+
`editMessage` marker (see above): on macOS 27 the helper calls the
|
|
343
|
+
five-argument `editMessageItem:atPartIndex:withNewPartText:newPartTranslation:backwardCompatabilityText:`
|
|
344
|
+
with part index 0 and the new text as backwards-compatibility text, and the
|
|
345
|
+
RPC answers `{ok:true}` on invocation only. **Preflight before firing**, same
|
|
346
|
+
discipline as `tapback`/`deleteMessage`: the guid must be inside the scan
|
|
347
|
+
window — located by a short poll (3 × 1 s, sleeping only between reads),
|
|
348
|
+
because a just-sent guid can lag history by a beat and a one-shot read
|
|
349
|
+
would fail closed on a message about to appear —
|
|
350
|
+
and the message must be an **editable own plain-text message**
|
|
351
|
+
(`isEditableOwnTextMessage` in `gateway/types.ts`, shared by both
|
|
352
|
+
implementations): `is_from_me` (Apple only permits editing your own
|
|
353
|
+
messages and the helper does not check), non-empty `text`, and no
|
|
354
|
+
attachments/poll — the RPC always edits part 0, which on those rows is not
|
|
355
|
+
the text, and a retracted row has nothing left to edit. Anything else is
|
|
356
|
+
refused as `ok:false, effectStarted:false` without touching the bridge, as
|
|
357
|
+
is empty or whitespace-only text. Text identical to the current row is the
|
|
358
|
+
idempotent `skipped:'unchanged'` (`ok:true`, nothing fired). The whole
|
|
359
|
+
decision is one shared function (`decideEditMessagePreflight`) so the two
|
|
360
|
+
implementations cannot drift. Known gap: a rich-link balloon carries its
|
|
361
|
+
URL as `text` and nothing on `ImsgMessage` marks it, so the real path
|
|
362
|
+
fires on it and the read-back decides; FakeGateway's rich-link row has no
|
|
363
|
+
text and is refused. After the RPC fires, a structured `-32603` whose
|
|
364
|
+
`data` is one of the helper's pre-invoke refusals (chat/message not found,
|
|
365
|
+
no selector, bad signature) is `effectStarted:false`; a bridge timeout or
|
|
366
|
+
any other `-32603` (an exception after IMChat was invoked) and an
|
|
367
|
+
unconfirmed read-back are `ok:false, effectStarted:true` — a consumer
|
|
368
|
+
reconciles those rather than treating them as a no-op. **Host-verified
|
|
369
|
+
2026-09-03** through `ImsgGateway` on the shared Mac (see the ledger
|
|
370
|
+
below); recipient-side rendering remains unobserved. `unsendMessage`
|
|
371
|
+
deliberately keeps its fire-first shape: retracting a just-sent row is a
|
|
372
|
+
legitimate use and its verify pass is the existing contract; a matching
|
|
373
|
+
preflight is a follow-up, not part of this change. Native CLI/RPC E2E on macOS 27 `26A5421a` (imsg#11, chat 8): both
|
|
374
|
+
paths edited a fresh own message and read the new text back on the same
|
|
375
|
+
guid/ROWID, with `message.date_edited` stamped — that column is not exposed
|
|
376
|
+
through `messages.history`, so the SDK smoke reads it from chat.db directly
|
|
377
|
+
while the gateway verifies by text read-back. **Edits mutate the existing
|
|
378
|
+
row's text IN PLACE: no new row, no `subscribe()` event.** `MessageWatcher`'s cursor is a ROWID
|
|
270
379
|
high-water mark (`WHERE ROWID > cursor`), so an UPDATE to an existing row is
|
|
271
380
|
structurally invisible to the watch stream — the only way to observe an
|
|
272
381
|
edit is re-reading `history()` and comparing text for a guid you already
|
|
@@ -340,6 +449,84 @@ both implementations — none of Apple's group primitives apply to a 1:1 chat.
|
|
|
340
449
|
and the receiving iPhone displayed Mapi's new contact info, closing the
|
|
341
450
|
receiver-side verification gap.
|
|
342
451
|
|
|
452
|
+
- **Chat backgrounds** — `chatBackgroundStatus` / `setChatBackground` /
|
|
453
|
+
`removeChatBackground` (Mapier-Labs/imsg#12, `chat.background.status|set|remove`).
|
|
454
|
+
A background is chat-scoped state recorded in chat.db's background tables,
|
|
455
|
+
not a message row: there is no `subscribe()` event and no history row on
|
|
456
|
+
either implementation. DM and group chats both accept all three verbs.
|
|
457
|
+
`chatBackgroundStatus` is a plain chat.db read (no selector marker, but it
|
|
458
|
+
needs the imsg#12 binary: an older build answers `-32601` and the call
|
|
459
|
+
throws). `null` means the chat does not exist (`-32602`); any other failure
|
|
460
|
+
is thrown, so a transient rpc/bridge fault can never masquerade as "no such
|
|
461
|
+
chat". It returns the persisted background channel GUID, which **lags
|
|
462
|
+
imagent** — a durable snapshot, never the live IMChat state.
|
|
463
|
+
`setChatBackground` supports the single runtime-verified `gradient` preset.
|
|
464
|
+
On the native side the RPC returns on helper acceptance — its response
|
|
465
|
+
embeds the pre-mutation chat.db snapshot, so no separate preflight read —
|
|
466
|
+
and the pipeline (PosterBoard builder → hidden `OverFullScreen` editor →
|
|
467
|
+
ChatKit poster/watch payloads → `setTranscriptBackgroundAndSendToChat:transferID:`)
|
|
468
|
+
runs asynchronously, bounded by per-stage watchdogs and a 90 s overall cap.
|
|
469
|
+
`ImsgGateway` therefore polls `chat.background.status` under a 90 s
|
|
470
|
+
deadline on a **monotonic** clock (`performance.now()`; a wall-clock
|
|
471
|
+
rollback cannot stretch the wait — the native CLI's own window; read first,
|
|
472
|
+
sleep 1 s between), in the pure loop `src/gateway/chat-background.ts`. Each poll is a
|
|
473
|
+
local chat.db read. The helper's operation record is a bridge round-trip on
|
|
474
|
+
Messages' main thread — it competes with the pipeline itself and, because
|
|
475
|
+
the native JSON-RPC loop is strictly serial, holds every other gateway verb
|
|
476
|
+
for up to 10 s on a stall — so it is read only (a) once, pulled forward,
|
|
477
|
+
when a NEW candidate appears (a background GUID different from the
|
|
478
|
+
pre-mutation snapshot) and (b) on a 15 s cadence for early failure
|
|
479
|
+
detection; a helper read that times out doubles that gap, up to 60 s, and
|
|
480
|
+
chat.db polling carries on meanwhile. **Success requires operation
|
|
481
|
+
correlation**: a candidate is accepted only when it equals the record's
|
|
482
|
+
`transfer_id` (the identifier IMChat persists as the background GUID, imsg
|
|
483
|
+
`docs/chat-background.md`). A changed guid, a newer `set` event, or both
|
|
484
|
+
are NOT evidence — a delayed write from an earlier operation satisfies them
|
|
485
|
+
— and a helper that no longer tracks the operation (bounded record table,
|
|
486
|
+
Messages relaunch) leaves nothing to correlate with, so that case ends the
|
|
487
|
+
wait as ambiguous rather than guessing. A bridge-timeout on a verification
|
|
488
|
+
read keeps polling; any other read failure (dead rpc child, transport loss)
|
|
489
|
+
ends the wait as ambiguous. `ok:true` carries the persisted GUID; a re-set
|
|
490
|
+
replaces the background and the GUID changes. FakeGateway mints a fresh GUID
|
|
491
|
+
per set from a sequence it persists in its snapshot. Result semantics
|
|
492
|
+
follow `shareNamePhoto`: `effectStarted:false` is a proven preflight
|
|
493
|
+
rejection — capability absent, `-32602` (unknown chat, bad preset), or a
|
|
494
|
+
helper refusal the SDK recognises in the JSON-RPC `error.data` (busy with
|
|
495
|
+
another background operation — the helper holds one in-process lock —,
|
|
496
|
+
guard mismatch, missing selector/runtime, "did not queue"); `ok:false,
|
|
497
|
+
effectStarted:true` means the effect may have started and persistence was
|
|
498
|
+
not proven — helper record `failed`, the deadline passed, an
|
|
499
|
+
rpc-child/transport failure, a bridge timeout, or any other `-32603` such as
|
|
500
|
+
an exception raised after IMChat was invoked (the native server reports all
|
|
501
|
+
of these under one code with the reason only in `data`) — and must be
|
|
502
|
+
reconciled, never reported as a proven no-op.
|
|
503
|
+
`removeChatBackground` is driven by the helper's **live** IMChat GUID, never
|
|
504
|
+
the caller's chat.db snapshot. The optional `expectedGuid` is a
|
|
505
|
+
compare-and-clear guard evaluated against that live GUID **first**, before
|
|
506
|
+
the no-background check: a mismatch — including a guard on a chat with no
|
|
507
|
+
live background — is `ok:false, effectStarted:false` and leaves the
|
|
508
|
+
background in place. An empty string is no guard (`normalizeChatBackgroundGuard`
|
|
509
|
+
in `gateway/types.ts`, shared by both implementations; the wire only
|
|
510
|
+
carries a non-empty one). Only then is a chat with no live background the
|
|
511
|
+
explicit `skipped:'no-background'` no-op (`ok:true`, IMChat never invoked).
|
|
512
|
+
The gateway reads chat.db before firing (the remove response's own snapshot
|
|
513
|
+
is post-invoke) and after a real clear checks that response, then polls
|
|
514
|
+
under a 30 s deadline for a `clear` event newer than the baseline; an unchanged empty
|
|
515
|
+
snapshot counts only when the pre-mutation read recorded the background
|
|
516
|
+
that is now gone, because an already-lagging empty snapshot proves nothing.
|
|
517
|
+
Native Set/Remove semantics were smoke-verified on macOS 27 `26A5421a`
|
|
518
|
+
(matching live GUID, chat.db event, cache, watch cache, upload metadata),
|
|
519
|
+
and the production CLI/RPC path went through its matched-build E2E before
|
|
520
|
+
imsg#12 merged. The SDK verbs were **host-verified 2026-09-03** through
|
|
521
|
+
`ImsgGateway` on the shared Mac (`--only-chat-background` leg of
|
|
522
|
+
`scripts/tier2-smoke.ts`; see the ledger below); recipient-side rendering,
|
|
523
|
+
group chats and the replace path were **eyeballed 2026-09-04** (ledger
|
|
524
|
+
entry below), with one receiver-side condition: a brand-new thread from a
|
|
525
|
+
sender the iPhone has never seen may render no background until the
|
|
526
|
+
thread is "known" (see the entry). The leg
|
|
527
|
+
(`scripts/tier2-chat-background-leg.ts`) stops at the first failed check
|
|
528
|
+
and never clears a background whose guid it did not read back.
|
|
529
|
+
|
|
343
530
|
**Host-verification ledger.** Exercised through `ImsgGateway` on the host Mac
|
|
344
531
|
2026-07-14 (`scripts/tier2-smoke.ts`, chat 3996):
|
|
345
532
|
|
|
@@ -361,6 +548,118 @@ Each leg prints an EYEBALL step because a local write is not proof of
|
|
|
361
548
|
receiver-visible delivery (the group-photo lesson). The `sendRich`
|
|
362
549
|
text/effect/reply path was already live-verified.
|
|
363
550
|
|
|
551
|
+
Exercised 2026-09-03 on the shared Mac (macOS 27 `26A5421a`, user
|
|
552
|
+
`mapidev`, disposable DM chat 8, SDK `bf92147` against the deployed imsg#12
|
|
553
|
+
CLI/helper: `bridge_version 2`, `v2_ready true`, `chatBackgroundSetGradient
|
|
554
|
+
true`, `chatBackgroundRemove true`; `scripts/tier2-smoke.ts 8
|
|
555
|
+
--only-chat-background`, run dir `map175-sdk-bg-e2e.PCVLad`):
|
|
556
|
+
|
|
557
|
+
- **`setChatBackground` / `removeChatBackground` / `chatBackgroundStatus`**
|
|
558
|
+
— live-verified: both capability rows; baseline `backgroundSet:false`;
|
|
559
|
+
Set Gradient persisted a NEW background guid in ~7 s and a separate status
|
|
560
|
+
read returned the same guid; Remove with `NOT-THE-LIVE-GUID` →
|
|
561
|
+
`ok:false, effectStarted:false` with the background unchanged (the logged
|
|
562
|
+
`ImsgRpcError` is the deliberate mismatch); guarded Remove cleared it,
|
|
563
|
+
status read `backgroundSet:false`, and an unguarded Remove on the bare
|
|
564
|
+
chat → `skipped:'no-background'`. `ALL PASS`, exit 0; the audit recorded
|
|
565
|
+
two events, both in chat 8; native status identical before and after and
|
|
566
|
+
the fixture back at its no-background baseline. The `live-smoke.ts 8
|
|
567
|
+
--read-only` gate passed first. Not observed: the receiving device
|
|
568
|
+
rendering the Gradient; not covered: group chats, the replace (re-set)
|
|
569
|
+
path, relay/public-API integration.
|
|
570
|
+
|
|
571
|
+
Exercised 2026-09-03 on the same host (SDK `6cf6ac5` against the same
|
|
572
|
+
deployed helper: `editMessageItemTranslation true`, the two four-argument
|
|
573
|
+
markers false; `scripts/tier2-smoke.ts 8 --only-edit-message`, run dir
|
|
574
|
+
`map175-sdk-edit-e2e.fgUq4t`):
|
|
575
|
+
|
|
576
|
+
Exercised 2026-09-03 on the same host (SDK `6e28c82` against the deployed
|
|
577
|
+
imsg#13 helper, `mentionFormatting true`; disposable group chat 12 with
|
|
578
|
+
three phone-handle participants; `scripts/tier2-smoke.ts 12 --only-mention
|
|
579
|
+
--mention-handle <participant IMHandle.ID> --mention-name <display name>`, run dir
|
|
580
|
+
`map175-sdk-mention-e2e.PuauKG`):
|
|
581
|
+
|
|
582
|
+
- **`sendRich` confirmed @mention** — live-verified: capability row;
|
|
583
|
+
`@Neo tier2-smoke 21:13:08 mention` sent
|
|
584
|
+
(`86246294-B16D-4E5C-9E15-D5E80C90CB14`, row 3143) and read back on the
|
|
585
|
+
same guid; `chat.db attributedBody` carries `__kIMMentionConfirmedMention`
|
|
586
|
+
at byte 266 followed by the handle at byte 300; a non-participant handle
|
|
587
|
+
refused by the helper (`-32603` "Mention identifier is not a participant
|
|
588
|
+
handle of this chat") → `refused:'mention-not-participant'`, a range
|
|
589
|
+
outside the text → `'text-formatting-range'`, and the handle as visible
|
|
590
|
+
text → `'mention-identifier'`, none of the three texts in history
|
|
591
|
+
afterwards. 9/9 PASS, `ALL PASS`, exit 0; the audit recorded exactly one
|
|
592
|
+
event, in chat 12; native status identical before and after. The
|
|
593
|
+
`live-smoke.ts 12 --read-only` gate passed first (6/6). **Recipient-side
|
|
594
|
+
observed** this time: on the mentioned participant's iPhone the message
|
|
595
|
+
rendered with "@Neo" highlighted in the same style as a native mention.
|
|
596
|
+
Not covered: email-handle mentions (not covered natively either), the
|
|
597
|
+
`send-attachment` / multipart mention legs (no SDK surface).
|
|
598
|
+
|
|
599
|
+
- **`editMessage`** — live-verified: capability row from the three-marker
|
|
600
|
+
fold; marker message sent (`73263626-5A07-4B3C-8244-D3133B8A8EA6`), edited,
|
|
601
|
+
new text read back on the SAME guid, `chat.db message.date_edited`
|
|
602
|
+
stamped (`810141791007747072`); unchanged text → `skipped:'unchanged'`;
|
|
603
|
+
whitespace text → `ok:false, effectStarted:false` with the edited text
|
|
604
|
+
still in place; the chat's newest inbound row (the DM partner's echo of
|
|
605
|
+
the marker) → `ok:false, effectStarted:false` and untouched. 10/10 PASS,
|
|
606
|
+
`ALL PASS`, exit 0; native status identical before and after. The
|
|
607
|
+
`live-smoke.ts 8 --read-only` gate passed first (6/6). Not observed: the
|
|
608
|
+
receiving device rendering "Edited" (the inbound echo carried the original
|
|
609
|
+
text, which is the partner's copy, not evidence either way).
|
|
610
|
+
|
|
611
|
+
Eyeballed 2026-09-04 on the same host (SDK `5826316` = v0.3.0, deployed
|
|
612
|
+
`imsg` 0.13.0 helper, all four 0.3.0 capability flags true) with a human
|
|
613
|
+
watching the receiving iPhone (iOS 26.6.1) between steps, one gateway call
|
|
614
|
+
per invocation (`scripts/eyeball.ts`, checked into no branch — it is an
|
|
615
|
+
observation aid, not a check). Fixtures: DM chat 13 (the host's account →
|
|
616
|
+
the phone, created that day) and group chat 12 (three phone handles):
|
|
617
|
+
|
|
618
|
+
- **`setChatBackground` → receiver** — observed on the phone in the DM and
|
|
619
|
+
in the group: the conversation switched to the Gradient background and
|
|
620
|
+
Messages posted "<sender> changed the background." The phone's own
|
|
621
|
+
`imagent` log (Console.app, device streaming) showed the full inbound
|
|
622
|
+
path each time: `Received incoming transcript background` → asset fetch
|
|
623
|
+
→ `Successfully blastdoor'd data` → `Writing poster` → `Update type:
|
|
624
|
+
setNewBackground`, ~6 s after the call. **Receiver-side condition (sticky,
|
|
625
|
+
trigger not isolated):** the first four Sets into the brand-new DM were
|
|
626
|
+
processed by the phone's `imagent` exactly the same way but Messages
|
|
627
|
+
rendered nothing and offered no Backgrounds entry in that DM; the host's
|
|
628
|
+
account was an email the phone had never seen. Right after that account
|
|
629
|
+
was saved as a contact the next Set rendered — and every later Set kept
|
|
630
|
+
rendering after the contact was deleted again, in the DM and in the
|
|
631
|
+
group, so whatever flips the thread from "does not render" to "renders"
|
|
632
|
+
stays flipped. Saving the contact is the time-correlated candidate, and
|
|
633
|
+
the phone's own background action in the group (before the group's first
|
|
634
|
+
successful Set) the other; separating them needs a fresh DM to a fresh
|
|
635
|
+
number. The Mac side gives no hint of any of this: chat.db, the upload
|
|
636
|
+
and the IDS send
|
|
637
|
+
(command 138, sealed for every endpoint of the number that advertises
|
|
638
|
+
`supports-transcript-backgrounds`) all succeed either way, so a
|
|
639
|
+
`setChatBackground` `ok:true` is proof the Mac set it, not that the
|
|
640
|
+
recipient can see it.
|
|
641
|
+
- **`removeChatBackground` → receiver** — observed in the DM and the group:
|
|
642
|
+
the background returned to default on the phone. The compare-and-clear
|
|
643
|
+
guard was exercised for real along the way: the phone had cleared and the
|
|
644
|
+
host re-set the background between two steps, so a Remove carrying the
|
|
645
|
+
earlier guid answered `ok:false, effectStarted:false` (the helper's
|
|
646
|
+
"does not match expected GUID" refusal) and left the live background in
|
|
647
|
+
place; the Remove with the live guid cleared it.
|
|
648
|
+
- **Reverse direction** — a background set and a background cleared from
|
|
649
|
+
the phone both arrived in the host's chat.db (`item_type 3`,
|
|
650
|
+
`group_action_type 4` / `6`, `handle_id` = the phone) and
|
|
651
|
+
`chatBackgroundStatus` tracked them. Host-initiated actions land as
|
|
652
|
+
`is_from_me 0, handle_id 0` rows; the status read keys off the chat's
|
|
653
|
+
live background guid, not that column.
|
|
654
|
+
- **`editMessage` → receiver** — observed in the DM: the phone showed the
|
|
655
|
+
SAME bubble with the new text and the "Edited" label, no new bubble
|
|
656
|
+
(marker `6026CE4C-1CF1-4B97-8C90-925834F59272`).
|
|
657
|
+
- Three IDS endpoints are registered for the receiving number; one of them
|
|
658
|
+
(a macOS 15 Mac, which has no transcript backgrounds) is dropped by the
|
|
659
|
+
sender's capability policy on every Set (`IF lacks(supports-transcript-
|
|
660
|
+
backgrounds) THEN failure`). That is per-endpoint pruning, not a failure
|
|
661
|
+
of the send, and the phone's endpoints are unaffected.
|
|
662
|
+
|
|
364
663
|
- **subscribe(sinceId?)** — long-lived event stream of new messages/reactions across all
|
|
365
664
|
chats the host Mac's Messages account can see. `sinceId` is an **exclusive** cursor: only
|
|
366
665
|
events with `id > sinceId` are delivered (catch-up semantics, not "starting at"). Today
|
|
@@ -783,8 +1082,16 @@ harness (`tsx --test`, see `package.json`). Required cases:
|
|
|
783
1082
|
returns `ok:false` for fewer than two options or an unknown chat
|
|
784
1083
|
- `sendRichLink` echoes an outbound row and returns `ok:false` for an unknown chat; preview
|
|
785
1084
|
metadata is not queryable through `ImsgMessage`
|
|
1085
|
+
- `sendRich` with a confirmed mention of a participant sends once (DM and group, alongside
|
|
1086
|
+
styles); a non-participant handle, a wrongly formatted participant, a range outside the
|
|
1087
|
+
text (mention OR style), an empty or padded handle, the handle as visible text, an
|
|
1088
|
+
unreported group directory, and a missing `mentionFormatting` marker each return
|
|
1089
|
+
`ok:false` with the matching typed `refused` reason and nothing sent
|
|
786
1090
|
- `editMessage` changes the target row's text in place — same row count before/after, no
|
|
787
|
-
new row
|
|
1091
|
+
new row; refuses an inbound (not `is_from_me`) message, empty/whitespace text, and our own
|
|
1092
|
+
message outside the scan window without mutation;
|
|
1093
|
+
unchanged text is the idempotent `skipped:'unchanged'`; returns `ok:false` without
|
|
1094
|
+
mutation when the `editMessage` capability marker is absent
|
|
788
1095
|
- `unsendMessage` retracts the target row (text clears, row persists);
|
|
789
1096
|
`deleteMessage` is dispatch-only with no stable history postcondition (the
|
|
790
1097
|
fake conservatively preserves the row); both return `ok: false` for an
|
|
@@ -794,13 +1101,19 @@ harness (`tsx --test`, see `package.json`). Required cases:
|
|
|
794
1101
|
- `renameGroup`/`addParticipant`/`removeParticipant` are reflected in a message sent
|
|
795
1102
|
afterward (`chat_name`/`participants`) and return `ok: false` against a DM
|
|
796
1103
|
- `setGroupPhoto`/`leaveGroup` return `ok: true` against a group and `ok: false` against a DM
|
|
797
|
-
- patched sticker, group-photo, and
|
|
798
|
-
when their `imsg status` capability markers are absent
|
|
1104
|
+
- patched sticker, group-photo, participant, and chat-background verbs return `ok:false`
|
|
1105
|
+
without mutation when their `imsg status` capability markers are absent
|
|
1106
|
+
- chat background: `setChatBackground` persists a NEW guid on a DM and a group (a re-set
|
|
1107
|
+
changes the guid), `chatBackgroundStatus` reflects it, `removeChatBackground` clears it;
|
|
1108
|
+
remove on a bare chat is the explicit `no-background` skip (also with an empty guard), a
|
|
1109
|
+
mismatched `expectedGuid` is a preflight rejection that leaves the background (and is
|
|
1110
|
+
checked before the no-background branch, so a stale guard on a bare chat rejects), and an
|
|
1111
|
+
unknown chat never starts an effect
|
|
799
1112
|
- `recentReactions` surfaces an inbound custom-emoji reaction (`reaction: 'custom'`) with the
|
|
800
1113
|
real `emoji` intact
|
|
801
1114
|
- Fake-only restart coverage snapshots and restores a world, then proves
|
|
802
|
-
`sendStatus`, `checkHandle`, Name & Photo idempotency,
|
|
803
|
-
fixture state, and monotonic IDs survive exactly
|
|
1115
|
+
`sendStatus`, `checkHandle`, Name & Photo idempotency, chat backgrounds (and their
|
|
1116
|
+
guid sequence), current group metadata, fixture state, and monotonic IDs survive exactly
|
|
804
1117
|
|
|
805
1118
|
FakeGateway runs this suite in CI on every PR. ImsgGateway runs it as a manual runbook step on
|
|
806
1119
|
the host Mac (see `docs/host-mac-control.md`) until a host-Mac CI runner exists — at that point
|
package/docs/imcore_selectors.md
CHANGED
|
@@ -66,8 +66,9 @@ for per-host fragility, and it's the reason for nearly every defensive choice in
|
|
|
66
66
|
version, and why "wired ≠ host-verified."
|
|
67
67
|
|
|
68
68
|
**The upside:** runtime resolution also buys adaptivity. One dylib can carry fallbacks
|
|
69
|
-
and pick per host — which is exactly why `handleEditMessage` tries
|
|
70
|
-
|
|
69
|
+
and pick per host — which is exactly why `handleEditMessage` tries the macOS 27
|
|
70
|
+
five-argument `editMessageItem:…newPartTranslation:…` first, then `editMessageItem:…`,
|
|
71
|
+
then `editMessage:…`, and why a fixed `leaveChat` would probe candidates
|
|
71
72
|
and choose whichever the host has. A statically-linked binary couldn't branch like that;
|
|
72
73
|
a runtime-resolved one straddles multiple macOS versions from a single build.
|
|
73
74
|
|
|
@@ -97,7 +98,7 @@ Ex.
|
|
|
97
98
|
### Mutate a message
|
|
98
99
|
| Verb | Handler | Call | Host |
|
|
99
100
|
|---|---|---|---|
|
|
100
|
-
| `edit-message` | `handleEditMessage` | **IMChat** `editMessageItem:atPartIndex:withNewPartText:backwardCompatabilityText:` (
|
|
101
|
+
| `edit-message` | `handleEditMessage` | **IMChat** `editMessageItem:atPartIndex:withNewPartText:newPartTranslation:backwardCompatabilityText:` (macOS 27; fallbacks `editMessageItem:…` / `editMessage:…`) | ✓ wired on macOS 27 (imsg#3), native CLI/RPC E2E'd (imsg#11); SDK verb capability-gated + preflighted, host `tier2-smoke --only-edit-message` pending |
|
|
101
102
|
| `unsend-message` | `handleUnsendMessage` | **IMChat** `retractMessagePart:` | ✓ |
|
|
102
103
|
| `delete-message` | `handleDeleteMessage` | **IMChat** `deleteChatItems:` (device-local only) | ✓ |
|
|
103
104
|
|
|
@@ -109,7 +110,7 @@ Ex.
|
|
|
109
110
|
| `set-display-name` | `handleSetDisplayName` | **IMChat** `_setDisplayName:` (+ `sendGroupPhotoUpdate:`) | ✓ |
|
|
110
111
|
| `update-group-photo` | `handleUpdateGroupPhoto` | **IMChat** `sendGroupPhotoUpdate:` + **IMFileTransferCenter** `createNewOutgoingGroupPhotoTransferWithLocalFileURL:` | ✓ |
|
|
111
112
|
| `create-chat` | `handleCreateChat` | **IMChatRegistry** `chatForIMHandle:` / `chatForIMHandles:` (+ `_setDisplayName:`) | ✓ |
|
|
112
|
-
| **`leave-chat`** | `handleLeaveChat` | **IMChat** `leaveChat` |
|
|
113
|
+
| **`leave-chat`** | `handleLeaveChat` | **IMChat** `leave` (macOS 27; `leaveChat` fallback for older hosts) | **⚠ call site updated to `leave`; not yet tested on relay e2e** |
|
|
113
114
|
| `delete-chat` | `handleDeleteChat` | **IMChatRegistry** `sharedInstance` (delete path selector-gated) | ✗ not wired above |
|
|
114
115
|
|
|
115
116
|
### Read / presence
|
|
@@ -129,8 +130,10 @@ Ex.
|
|
|
129
130
|
|
|
130
131
|
The selector **markers** reported by `imsg status --json` are probed in
|
|
131
132
|
`IMsgInjected.m` near the top of the file: it checks `IMChat` for
|
|
133
|
+
`editMessageItem:…newPartTranslation:…` (marker `editMessageItemTranslation`),
|
|
132
134
|
`editMessageItem:…`, `editMessage:…`, `retractMessagePart:`, `sendMessage:reason:`.
|
|
133
|
-
Add a probe there when adding a verb so the SDK can capability-gate it
|
|
135
|
+
Add a probe there when adding a verb so the SDK can capability-gate it; the SDK's
|
|
136
|
+
`editMessage` capability is any one of the three edit markers.
|
|
134
137
|
|
|
135
138
|
## macOS 27 operation callsites — MAP-175
|
|
136
139
|
|
|
@@ -139,12 +142,12 @@ operation callsites were found and verified by **Neo Shangguan** for MAP-175.
|
|
|
139
142
|
|
|
140
143
|
| Operation | Current bridge selector | macOS 27 entrypoint found | Downstream operation dispatch | Integration status | Found by |
|
|
141
144
|
|---|---|---|---|---|---|
|
|
142
|
-
| `leave-chat` | `-[IMChat leaveChat]` — absent | `-[IMChat leave]` | `[provider _chat_leave:chat]` |
|
|
143
|
-
| `edit-message` | Four-argument `-[IMChat editMessageItem:…]` / `-[IMChat editMessage:…]` — absent | Five-argument `-[IMChat editMessageItem:…newPartTranslation:…]` | `[provider _chat:chat sendEditedMessageItem:editedItem previousMessageItem:originalItem partIndex:index editType:1 backwardCompatabilityText:text]` |
|
|
144
|
-
| `mentions` |
|
|
145
|
-
| `text-styling` / `text-effects` | No bridge operation | IM text-style/effect attributes on `NSAttributedString`; no dedicated send selector | Existing `IMMessage` construction → `IMChat` send path |
|
|
145
|
+
| `leave-chat` | `-[IMChat leaveChat]` — absent | `-[IMChat leave]` | `[provider _chat_leave:chat]` | Call site updated to `leave`; not yet tested on relay e2e | **Neo Shangguan** |
|
|
146
|
+
| `edit-message` | Four-argument `-[IMChat editMessageItem:…]` / `-[IMChat editMessage:…]` — absent | Five-argument `-[IMChat editMessageItem:…newPartTranslation:…]` | `[provider _chat:chat sendEditedMessageItem:editedItem previousMessageItem:originalItem partIndex:index editType:1 backwardCompatabilityText:text]` | Wired (imsg#3), native CLI/RPC E2E'd (imsg#11) and SDK verb host-verified 2026-09-03 on macOS 27 | **Neo Shangguan** |
|
|
147
|
+
| `mentions` | `send-rich` / `send.rich` `text_formatting[].mention` (imsg#13) | Confirmed-mention `NSAttributedString` attribute (`__kIMMentionConfirmedMention`) resolved at runtime; no dedicated send selector | Existing `IMMessage` construction → `IMChat` send path | Wired + native E2E'd 2026-09-03 (attribute keys match a genuine iPhone mention row; highlight + notification on the recipient); SDK verb host-verified 2026-09-03 incl. recipient highlight | **Neo Shangguan** |
|
|
148
|
+
| `text-styling` / `text-effects` | No bridge operation | IM text-style/effect attributes on `NSAttributedString`; no dedicated send selector | Existing `IMMessage` construction → `IMChat` send path | Wired + tested e2e relayside | **Neo Shangguan** |
|
|
146
149
|
| `contact-card` | No contact-card-specific bridge operation | `.vcf` attachment identified by `kUTTypeVCard` / `public.vcard` | Existing `IMFileTransfer` attachment send path | Attachment representation found; not live-smoked | **Neo Shangguan** |
|
|
147
|
-
| `chat-background` |
|
|
150
|
+
| `chat-background` | `chat.background.set` / `chat.background.remove` / `chat.background.status` (Mapier-Labs/imsg#12, merged) | `-[IMChat setTranscriptBackgroundAndSendToChat:transferID:]` | `[provider _chat:chat setTranscriptBackgroundAndSendToChat:background transferID:transferID]` | Native bridge merged after matched-build CLI/RPC E2E; SDK verbs `setChatBackground` / `removeChatBackground` / `chatBackgroundStatus` in this repo; relay command + recipient-side validation pending | **Neo Shangguan** |
|
|
148
151
|
| `location-request` | No bridge operation | `+[MSMessage findMyLocationRequestMessage]` | `+[CKComposition compositionWithMSMessage:appExtensionIdentifier:]` using `IMBalloonBundleIdentifierWaldo` → `-[CKChatController setComposition:]` | Factory/staging callsites found; payload/bridge route pending; not wired/live-smoked | **Neo Shangguan** |
|
|
149
152
|
|
|
150
153
|
The summary records stable selector names for implementation. Numeric callsite
|
|
@@ -191,9 +194,9 @@ replacement entrypoint; bridge code should call it rather than reaching into
|
|
|
191
194
|
group-leave operation, so it is not a `leaveChat` replacement.
|
|
192
195
|
|
|
193
196
|
The existing `leaveChat` selector can remain as the older-system path, with
|
|
194
|
-
`leave` as a runtime-gated fallback.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
`leave` as a runtime-gated fallback. **Update:** the bridge call site has since
|
|
198
|
+
been updated to probe `leave`; it has not yet been tested end-to-end through
|
|
199
|
+
relay (no relay e2e group-leave smoke performed).
|
|
197
200
|
|
|
198
201
|
### `edit-message` — found by Neo Shangguan
|
|
199
202
|
|
|
@@ -383,9 +386,11 @@ As with mentions, these mutation helpers live in the `/System/iOSSupport`
|
|
|
383
386
|
ChatKit image and are evidence for payload shape rather than bridge call
|
|
384
387
|
targets. A bridge implementation should resolve the native
|
|
385
388
|
`IMSharedUtilities` symbols by name, construct the attributed payload itself,
|
|
386
|
-
and reuse the existing `IMMessage`/`IMChat` send path.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
+
and reuse the existing `IMMessage`/`IMChat` send path. **Update:** this is now
|
|
390
|
+
wired and tested end-to-end through relay — the attributed payload is
|
|
391
|
+
constructed over the requested ranges and sent via the existing path, with
|
|
392
|
+
effects animating on device on the `mapidev` development profile. Inbound
|
|
393
|
+
styled-text parsing remains open if that direction is needed.
|
|
389
394
|
|
|
390
395
|
### `contact-card` — found by Neo Shangguan
|
|
391
396
|
|
|
@@ -494,21 +499,49 @@ else:
|
|
|
494
499
|
```
|
|
495
500
|
|
|
496
501
|
`-[CKCoreChatController updateTranscriptBackground:transferID:]` begins at
|
|
497
|
-
`0x1dbc7c538`. Its non-null
|
|
498
|
-
|
|
502
|
+
`0x1dbc7c538`. Its non-null branch stages the prepared poster and watch payloads
|
|
503
|
+
on disk, obtains the poster URL's `absoluteString`, and then converges with the
|
|
504
|
+
null removal branch on the same `IMChat` selector:
|
|
499
505
|
|
|
500
506
|
```text
|
|
501
|
-
set: [chat setTranscriptBackgroundAndSendToChat:
|
|
507
|
+
set: [chat setTranscriptBackgroundAndSendToChat:posterURL.absoluteString
|
|
502
508
|
transferID:transferID] callsite 0x1dbc7cc38
|
|
503
509
|
remove: [chat setTranscriptBackgroundAndSendToChat:nil
|
|
504
510
|
transferID:transferID] callsite 0x1dbc7c710
|
|
505
511
|
```
|
|
506
512
|
|
|
513
|
+
The `absoluteString` selector stub immediately before the non-null callsite is
|
|
514
|
+
at `0x1e0016430` and resolves to selector address `0x1f5907443`. A live smoke
|
|
515
|
+
that passed the `CKTranscriptBackground` object itself produced
|
|
516
|
+
`-[ChatKit.CKTranscriptBackground length]: unrecognized selector`, further
|
|
517
|
+
confirming that the daemon-facing first argument is the staged URL string, not
|
|
518
|
+
the ChatKit wrapper object.
|
|
519
|
+
|
|
507
520
|
This establishes that removal is represented by a null background sent through
|
|
508
|
-
the same operation, not by a separate remove selector.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
521
|
+
the same operation, not by a separate remove selector. The complete native
|
|
522
|
+
recipe archives the poster configuration, prepares its watch snapshot, stages
|
|
523
|
+
the resulting `posterData` and `watchData` at the poster URL and its
|
|
524
|
+
`im_associatedWatchBackgroundURL`, and passes the poster URL string to IMChat.
|
|
525
|
+
Recipient behavior outside the development profile still requires validation.
|
|
526
|
+
|
|
527
|
+
A read-only runtime probe on the macOS 27 development host (build `26A5421a`)
|
|
528
|
+
confirmed that `IMChat` responds to
|
|
529
|
+
`setTranscriptBackgroundAndSendToChat:transferID:`. Its live Objective-C method
|
|
530
|
+
signature returns `void`, has four total arguments (`self`, `_cmd`, and two
|
|
531
|
+
object arguments), and `+[NSString stringGUID]` is available for generating the
|
|
532
|
+
transfer identifier. A development-profile semantic smoke then generated and
|
|
533
|
+
staged a Gradient background, invoked IMChat, and matched the live background
|
|
534
|
+
GUID to the persisted `set` event and cache assets. Passing a null first
|
|
535
|
+
argument with a fresh transfer identifier subsequently cleared the same live
|
|
536
|
+
GUID and persisted a matching `clear` event. This verified the direct native
|
|
537
|
+
set/remove operations. The production bridge (Gradient builder, null-background
|
|
538
|
+
removal handler, capability probes, standalone smoke scripts, and
|
|
539
|
+
persistence-confirming CLI/RPC surfaces) then passed its matched CLI/dylib
|
|
540
|
+
E2E on the development profile and merged as Mapier-Labs/imsg#12 into
|
|
541
|
+
`mapier/deploy`. This repo's `ImsgGateway` exposes it as `setChatBackground`,
|
|
542
|
+
`removeChatBackground`, and `chatBackgroundStatus` (see
|
|
543
|
+
`docs/gateway-contract.md` §2 "Chat backgrounds"). Relay command wiring and
|
|
544
|
+
recipient-side end-to-end coverage remain pending.
|
|
512
545
|
|
|
513
546
|
### `location-request` — found by Neo Shangguan
|
|
514
547
|
|
|
@@ -558,13 +591,13 @@ for the MAP-175 findings above:
|
|
|
558
591
|
|
|
559
592
|
| Operation | Readiness | What is resolved | Remaining implementation work |
|
|
560
593
|
|---|---|---|---|
|
|
561
|
-
| `leave-chat` | **
|
|
562
|
-
| `edit-message` | **
|
|
563
|
-
| `mentions` | **
|
|
564
|
-
| `text-styling` / `text-effects` | **
|
|
594
|
+
| `leave-chat` | **Call site updated** | macOS 27 `-[IMChat leave]` entrypoint and `_chat_leave:` dispatch; call site re-pointed to `leave` with the `leaveChat` fallback retained | Test end-to-end through relay on `mapidev` (bridge call site updated, relay e2e not yet run) |
|
|
595
|
+
| `edit-message` | **Native merged (imsg#3/#11); SDK verb host-verified 2026-09-03** | Five-argument selector, argument order, edited-item builder, daemon dispatch, runtime probe/fallbacks, native CLI/RPC E2E | Relay `message.moderate edit` mapping of `effectStarted`; recipient-side rendering check |
|
|
596
|
+
| `mentions` | **Native merged (imsg#13, `7c31772`); SDK verb host-verified 2026-09-03** | Attribute key + handle value confirmed against a genuine row, recipient highlight + notification, membership fail-closed | Relay `message.send` payload for mention ranges |
|
|
597
|
+
| `text-styling` / `text-effects` | **Done — tested e2e relayside** | Transport attributes, style bit mapping, effect type/name mapping, existing send boundary; attributed payload constructed and sent through the relay path, animating on device | — (inbound styled-text parsing still open if needed) |
|
|
565
598
|
| `contact-card` | **Prototype-ready** | vCard UTI/content representation and reuse of the existing attachment-transfer path | Choose/validate MIME and transfer metadata, accept or serialize `.vcf` input, verify recipient rendering and inbound parsing |
|
|
566
|
-
| `chat-background` remove | **
|
|
567
|
-
| `chat-background` set | **
|
|
599
|
+
| `chat-background` remove | **Native merged (imsg#12); SDK verb in this repo** | Null-background semantics, transfer-ID generation, selector ABI, live clear, persisted clear event, race-guard contract, matched-build CLI/RPC E2E | Host-Mac `tier2-smoke.ts --chat-background`; relay `chat.background.remove` command; validate recipient state |
|
|
600
|
+
| `chat-background` set | **Native merged (imsg#12); SDK verb in this repo** | Gradient configuration build, archive, poster/watch snapshot, staging contract, URL-string IMChat argument, live set, persisted event, cache/upload metadata, matched-build CLI/RPC E2E | Host-Mac `tier2-smoke.ts --chat-background`; relay `chat.background.set` command; validate recipient rendering and retry behavior |
|
|
568
601
|
| `location-request` | **Callsite-only** | Waldo feature gate, `MSMessage` factory selector, `CKComposition` wrapper, and app-extension identifier | Recover the factory's serialized Waldo payload and a safe bridge route into the existing send path without depending on a `CKChatController` UI instance |
|
|
569
602
|
|
|
570
603
|
Every new or replaced bridge path still needs defensive runtime capability
|
|
@@ -592,9 +625,10 @@ macOS version:
|
|
|
592
625
|
the matched CLI+dylib, inject (SIP off), and smoke.
|
|
593
626
|
|
|
594
627
|
For `leave-chat`: `handleLeaveChat` and everything above it (relay → RPC → file
|
|
595
|
-
IPC → dispatch) is intact. The only dead link
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
628
|
+
IPC → dispatch) is intact. The only dead link was `-[IMChat leaveChat]`;
|
|
629
|
+
`-[IMChat leave]` is its macOS 27 replacement and the call site has since been
|
|
630
|
+
re-pointed to it (with `leaveChat` retained as the older-host path). The
|
|
631
|
+
remaining work is to test it end-to-end through relay on the development
|
|
632
|
+
profile.
|
|
599
633
|
|
|
600
634
|
[#5]: https://github.com/Mapier-Labs/imsg-sdk/issues/5
|
package/package.json
CHANGED