Skip to content

Commit 65e9ad1

Browse files
committed
fix(imessage): preserve direct progress without typing rpc
1 parent a279c5c commit 65e9ad1

2 files changed

Lines changed: 90 additions & 8 deletions

File tree

extensions/imessage/src/monitor.last-route.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,84 @@ describe("iMessage monitor last-route updates", () => {
256256
});
257257
});
258258

259+
it("keeps direct progress options when imsg lacks native typing support", async () => {
260+
setCachedIMessagePrivateApiStatus("imsg", {
261+
available: true,
262+
v2Ready: true,
263+
selectors: {},
264+
rpcMethods: ["watch.subscribe", "send", "read"],
265+
});
266+
dispatchInboundMessageMock.mockImplementationOnce(async (params) => {
267+
expect(params.replyOptions?.suppressDefaultToolProgressMessages).toBe(true);
268+
expect(params.replyOptions?.allowProgressCallbacksWhenSourceDeliverySuppressed).toBe(true);
269+
expect(params.replyOptions?.onToolStart).toBeUndefined();
270+
return { queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } } as const;
271+
});
272+
273+
let onNotification: ((message: { method: string; params: unknown }) => void) | undefined;
274+
const client = {
275+
request: vi.fn(async (method: string) => {
276+
if (method === "watch.subscribe") {
277+
return { subscription: 1 };
278+
}
279+
if (method === "typing") {
280+
throw new Error("typing should not start without native typing support");
281+
}
282+
throw new Error(`unexpected imsg method ${method}`);
283+
}),
284+
waitForClose: vi.fn(async () => {
285+
onNotification?.({
286+
method: "message",
287+
params: {
288+
message: {
289+
id: 13,
290+
chat_id: 123,
291+
sender: "+15550001111",
292+
is_from_me: false,
293+
text: "run a long script without native typing",
294+
is_group: false,
295+
created_at: new Date().toISOString(),
296+
},
297+
},
298+
});
299+
await Promise.resolve();
300+
await Promise.resolve();
301+
}),
302+
stop: vi.fn(async () => {}),
303+
};
304+
createIMessageRpcClientMock.mockImplementation(async (params) => {
305+
if (!params?.onNotification) {
306+
throw new Error("expected iMessage notification handler");
307+
}
308+
onNotification = params.onNotification;
309+
return client as never;
310+
});
311+
312+
await monitorIMessageProvider({
313+
config: {
314+
channels: {
315+
imessage: {
316+
dmPolicy: "allowlist",
317+
allowFrom: ["+15550001111"],
318+
sendReadReceipts: false,
319+
},
320+
},
321+
messages: { inbound: { debounceMs: 0 } },
322+
session: { mainKey: "main" },
323+
} as never,
324+
runtime: { error: vi.fn(), exit: vi.fn(), log: vi.fn() },
325+
});
326+
327+
await vi.waitFor(() => {
328+
expect(dispatchInboundMessageMock).toHaveBeenCalledTimes(1);
329+
});
330+
expect(client.request).not.toHaveBeenCalledWith(
331+
"typing",
332+
expect.objectContaining({ typing: true }),
333+
expect.anything(),
334+
);
335+
});
336+
259337
it("starts direct typing before dispatching the inbound turn", async () => {
260338
setCachedIMessagePrivateApiStatus("imsg", {
261339
available: true,

extensions/imessage/src/monitor/monitor-provider.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,11 +1060,11 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
10601060
channel: "imessage",
10611061
chatType: decision.isGroup ? "group" : "direct",
10621062
});
1063-
const shouldStartDirectTyping =
1064-
supportsTyping &&
1063+
const shouldUseDirectToolTypingOptions =
10651064
!decision.isGroup &&
10661065
sendPolicy !== "deny" &&
10671066
(configuredTypingMode === undefined || configuredTypingMode === "instant");
1067+
const shouldStartDirectTyping = supportsTyping && shouldUseDirectToolTypingOptions;
10681068
const earlyDirectTypingTarget = shouldStartDirectTyping
10691069
? buildDirectIMessageReplyTarget({
10701070
cfg,
@@ -1305,23 +1305,27 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
13051305
},
13061306
});
13071307
let directTypingController: IMessageTypingController | undefined;
1308-
const directToolTypingOptions = shouldStartDirectTyping
1308+
const directToolTypingOptions = shouldUseDirectToolTypingOptions
13091309
? ({
13101310
// iMessage's native typing bubble is channel-owned UI, not a
13111311
// visible tool-progress message. The suppress flag is what lets
13121312
// dispatch forward this callback even when verbose progress is off;
13131313
// allowProgress covers message_tool_only source delivery. Keep this on
1314-
// the direct instant/default path so configured typingMode values still
1315-
// decide when typing can begin.
1314+
// the direct instant/default path even when older imsg builds do not
1315+
// report native typing support.
13161316
suppressDefaultToolProgressMessages: true,
13171317
allowProgressCallbacksWhenSourceDeliverySuppressed: true,
13181318
onTypingController: (typing: IMessageTypingController) => {
13191319
directTypingController = typing;
13201320
typingReplyOptions.onTypingController?.(typing);
13211321
},
1322-
onToolStart: async () => {
1323-
await directTypingController?.startTypingLoop();
1324-
},
1322+
...(supportsTyping
1323+
? {
1324+
onToolStart: async () => {
1325+
await directTypingController?.startTypingLoop();
1326+
},
1327+
}
1328+
: {}),
13251329
} as const)
13261330
: {};
13271331
const configuredBlockStreaming = resolveChannelStreamingBlockEnabled(accountInfo.config);

0 commit comments

Comments
 (0)