Skip to content

Commit 7c36c48

Browse files
committed
msteams: add typingIndicator config and avoid duplicate DM typing
1 parent 381a865 commit 7c36c48

File tree

4 files changed

+28
-1
lines changed

4 files changed

+28
-1
lines changed

extensions/msteams/src/reply-dispatcher.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,28 @@ describe("createMSTeamsReplyDispatcher", () => {
138138

139139
expect(streamInstances).toHaveLength(1);
140140
expect(streamInstances[0]?.sendInformativeUpdate).toHaveBeenCalledTimes(1);
141+
expect(typingCallbacks.onReplyStart).not.toHaveBeenCalled();
142+
});
143+
144+
it("sends native typing indicator for channel conversations by default", async () => {
145+
createDispatcher("channel");
146+
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
147+
148+
await options.onReplyStart?.();
149+
150+
expect(streamInstances).toHaveLength(0);
141151
expect(typingCallbacks.onReplyStart).toHaveBeenCalledTimes(1);
142152
});
143153

154+
it("skips native typing indicator when typingIndicator=false", async () => {
155+
createDispatcher("channel", { typingIndicator: false });
156+
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
157+
158+
await options.onReplyStart?.();
159+
160+
expect(typingCallbacks.onReplyStart).not.toHaveBeenCalled();
161+
});
162+
144163
it("only sends the informative status update once", async () => {
145164
createDispatcher("personal");
146165
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];

extensions/msteams/src/reply-dispatcher.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ export function createMSTeamsReplyDispatcher(params: {
109109

110110
const blockStreamingEnabled =
111111
typeof msteamsCfg?.blockStreaming === "boolean" ? msteamsCfg.blockStreaming : false;
112+
const typingIndicatorEnabled =
113+
typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
112114

113115
const pendingMessages: MSTeamsRenderedMessage[] = [];
114116

@@ -176,7 +178,10 @@ export function createMSTeamsReplyDispatcher(params: {
176178
humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
177179
onReplyStart: async () => {
178180
await streamController.onReplyStart();
179-
await typingCallbacks?.onReplyStart?.();
181+
// Avoid duplicate typing UX in DMs: stream status already shows progress.
182+
if (typingIndicatorEnabled && !streamController.hasStream()) {
183+
await typingCallbacks?.onReplyStart?.();
184+
}
180185
},
181186
typingCallbacks,
182187
deliver: async (payload) => {

src/config/types.msteams.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export type MSTeamsConfig = {
9090
textChunkLimit?: number;
9191
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
9292
chunkMode?: "length" | "newline";
93+
/** Send native Teams typing indicator before replies. Default: true for groups/channels; DMs use informative stream status. */
94+
typingIndicator?: boolean;
9395
/** Enable progressive block-by-block message delivery instead of a single reply. */
9496
blockStreaming?: boolean;
9597
/** Merge streamed block replies before sending. */

src/config/zod-schema.providers-core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ export const DiscordAccountSchema = z
495495
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
496496
textChunkLimit: z.number().int().positive().optional(),
497497
chunkMode: z.enum(["length", "newline"]).optional(),
498+
typingIndicator: z.boolean().optional(),
498499
blockStreaming: z.boolean().optional(),
499500
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
500501
streaming: z.enum(["off", "partial", "block", "progress"]).optional(),

0 commit comments

Comments
 (0)