msteams: add typingIndicator config and prevent duplicate DM typing indicator#60771
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c36c483d5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| dms: z.record(z.string(), DmConfigSchema.optional()).optional(), | ||
| textChunkLimit: z.number().int().positive().optional(), | ||
| chunkMode: z.enum(["length", "newline"]).optional(), | ||
| typingIndicator: z.boolean().optional(), |
There was a problem hiding this comment.
Move typingIndicator field to MSTeams config schema
The added typingIndicator property is currently wired into DiscordAccountSchema, but the new behavior reads channels.msteams.typingIndicator and MSTeamsConfigSchema is strict. That means users who set channels.msteams.typingIndicator will get a validation error for an unknown key, so the new Teams config knob cannot actually be used, while an unrelated Discord account key is now accepted but ignored.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR adds a However, there is a critical bug in the Zod schema placement:
Confidence Score: 1/5Not safe to merge — the Zod validation schema was added to the wrong channel schema, breaking the config knob entirely for MS Teams users. The core logic change and TypeScript type are correct, but the Zod schema for
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/config/zod-schema.providers-core.ts
Line: 498
Comment:
**`typingIndicator` added to the wrong Zod schema**
This property was added to `DiscordAccountSchema` (line 479), but the feature is implemented in the MS Teams extension and should be in `MSTeamsConfigSchema` (around line 1536).
Because `MSTeamsConfigSchema` uses `.strict()` (line 1560), any user who sets `channels.msteams.typingIndicator: false` in their config will receive a Zod validation error like `"Unrecognized key(s) in object: 'typingIndicator'"` — completely preventing the config knob from working.
The fix is to remove the field from `DiscordAccountSchema` and add it to `MSTeamsConfigSchema` instead, adjacent to the existing `blockStreaming` field:
```
// In MSTeamsConfigSchema (~line 1536):
chunkMode: z.enum(["length", "newline"]).optional(),
typingIndicator: z.boolean().optional(),
blockStreaming: z.boolean().optional(),
```
And remove it from `DiscordAccountSchema`:
```
// In DiscordAccountSchema (~line 497):
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
```
Note: `pnpm config:docs:gen` should also be re-run to keep the baseline artifact in sync (per repo CLAUDE.md guidelines).
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "msteams: add typingIndicator config and ..." | Re-trigger Greptile |
| dms: z.record(z.string(), DmConfigSchema.optional()).optional(), | ||
| textChunkLimit: z.number().int().positive().optional(), | ||
| chunkMode: z.enum(["length", "newline"]).optional(), | ||
| typingIndicator: z.boolean().optional(), |
There was a problem hiding this comment.
typingIndicator added to the wrong Zod schema
This property was added to DiscordAccountSchema (line 479), but the feature is implemented in the MS Teams extension and should be in MSTeamsConfigSchema (around line 1536).
Because MSTeamsConfigSchema uses .strict() (line 1560), any user who sets channels.msteams.typingIndicator: false in their config will receive a Zod validation error like "Unrecognized key(s) in object: 'typingIndicator'" — completely preventing the config knob from working.
The fix is to remove the field from DiscordAccountSchema and add it to MSTeamsConfigSchema instead, adjacent to the existing blockStreaming field:
// In MSTeamsConfigSchema (~line 1536):
chunkMode: z.enum(["length", "newline"]).optional(),
typingIndicator: z.boolean().optional(),
blockStreaming: z.boolean().optional(),
And remove it from DiscordAccountSchema:
// In DiscordAccountSchema (~line 497):
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
Note: pnpm config:docs:gen should also be re-run to keep the baseline artifact in sync (per repo CLAUDE.md guidelines).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/zod-schema.providers-core.ts
Line: 498
Comment:
**`typingIndicator` added to the wrong Zod schema**
This property was added to `DiscordAccountSchema` (line 479), but the feature is implemented in the MS Teams extension and should be in `MSTeamsConfigSchema` (around line 1536).
Because `MSTeamsConfigSchema` uses `.strict()` (line 1560), any user who sets `channels.msteams.typingIndicator: false` in their config will receive a Zod validation error like `"Unrecognized key(s) in object: 'typingIndicator'"` — completely preventing the config knob from working.
The fix is to remove the field from `DiscordAccountSchema` and add it to `MSTeamsConfigSchema` instead, adjacent to the existing `blockStreaming` field:
```
// In MSTeamsConfigSchema (~line 1536):
chunkMode: z.enum(["length", "newline"]).optional(),
typingIndicator: z.boolean().optional(),
blockStreaming: z.boolean().optional(),
```
And remove it from `DiscordAccountSchema`:
```
// In DiscordAccountSchema (~line 497):
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
```
Note: `pnpm config:docs:gen` should also be re-run to keep the baseline artifact in sync (per repo CLAUDE.md guidelines).
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
Adds an MS Teams configuration switch for native typing activities and updates the Teams reply dispatcher to avoid showing duplicate typing UX in DMs (stream status + native typing).
Changes:
- Introduce
channels.msteams.typingIndicatorconfig flag (intended default: enabled). - Skip native typing callback in DMs when the Teams reply stream is active to prevent double indicators.
- Add/adjust unit tests covering default DM behavior and opt-out behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/config/zod-schema.providers-core.ts | Adds a typingIndicator field, but currently in the wrong schema block (needs to be in MSTeamsConfigSchema, not Discord). |
| src/config/types.msteams.ts | Adds typingIndicator?: boolean to the MS Teams config type (docstring needs a small accuracy tweak). |
| extensions/msteams/src/reply-dispatcher.ts | Gates typingCallbacks.onReplyStart based on typingIndicator and stream presence to prevent duplicate DM typing indicators. |
| extensions/msteams/src/reply-dispatcher.test.ts | Adds tests asserting DM suppresses native typing, and that typing can be disabled via config (test scenario could better reflect typing-supported conversation types). |
| dms: z.record(z.string(), DmConfigSchema.optional()).optional(), | ||
| textChunkLimit: z.number().int().positive().optional(), | ||
| chunkMode: z.enum(["length", "newline"]).optional(), | ||
| typingIndicator: z.boolean().optional(), |
There was a problem hiding this comment.
typingIndicator appears to be an MS Teams config flag, but it’s being added to DiscordAccountSchema here. Since MSTeamsConfigSchema is .strict() and currently does not include typingIndicator, setting channels.msteams.typingIndicator would still fail config validation (and Discord would gain an unused field). Move this field into MSTeamsConfigSchema (and remove it from Discord), then regenerate any derived config metadata/schema artifacts as needed.
| typingIndicator: z.boolean().optional(), |
| textChunkLimit?: number; | ||
| /** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */ | ||
| chunkMode?: "length" | "newline"; | ||
| /** Send native Teams typing indicator before replies. Default: true for groups/channels; DMs use informative stream status. */ |
There was a problem hiding this comment.
The docstring says “Default: true for groups/channels; DMs use informative stream status”, but in the runtime implementation native typing is only supported for personal and groupchat conversation types (not channel). Consider rewording to “group chats” (and clarify that 1:1 chats suppress native typing when the informative status stream is used).
| /** Send native Teams typing indicator before replies. Default: true for groups/channels; DMs use informative stream status. */ | |
| /** Send native Teams typing indicator before replies. Default: true for group chats; 1:1 chats use informative stream status instead of native typing. */ |
| it("sends native typing indicator for channel conversations by default", async () => { | ||
| createDispatcher("channel"); | ||
| const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0]; | ||
|
|
||
| await options.onReplyStart?.(); | ||
|
|
||
| expect(streamInstances).toHaveLength(0); | ||
| expect(typingCallbacks.onReplyStart).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it("skips native typing indicator when typingIndicator=false", async () => { | ||
| createDispatcher("channel", { typingIndicator: false }); | ||
| const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0]; | ||
|
|
||
| await options.onReplyStart?.(); | ||
|
|
||
| expect(typingCallbacks.onReplyStart).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
This test asserts native typing behavior for conversationType="channel", but the dispatcher only supports typing activities for personal and groupchat conversations (channel uses a no-op typing sender). Using groupchat here (and in the opt-out test) would better match real typing-supported scenarios and reduce the risk of the test passing even if typing is never actually emitted for supported conversation types.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b90bcb5980
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (typingIndicatorEnabled && !streamController.hasStream()) { | ||
| await typingCallbacks?.onReplyStart?.(); |
There was a problem hiding this comment.
Fallback to native typing when informative update fails
onReplyStart now suppresses typingCallbacks.onReplyStart() for every personal chat based only on hasStream(), but sendInformativeUpdate() swallows send failures internally (onError + continue). In DMs where the streaminfo typing activity is rejected or fails transiently, this branch skips both informative status and native typing, which is a regression from previous behavior where native typing still ran. Please gate the skip on a successful informative send (or otherwise keep native typing as a fallback) so DM users still get typing feedback when stream initialization fails.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c32216eb8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (attachment.contentType !== "text/html") { | ||
| continue; |
There was a problem hiding this comment.
Match HTML attachments by prefix instead of exact MIME
extractTextFromHtmlAttachments only accepts attachment.contentType === "text/html", so attachments reported as text/html; charset=utf-8 are skipped and their message text is never extracted. In those cases rawText often stays empty and the handler falls back to attachment placeholders, which can make normal text messages look like file-only messages and bypass expected text processing.
Useful? React with 👍 / 👎.
| : typeof attachment.content?.text === "string" | ||
| ? attachment.content.text | ||
| : typeof attachment.content?.body === "string" | ||
| ? attachment.content.body |
There was a problem hiding this comment.
Parse the
content.content HTML payload variant
The new fallback extractor only checks attachment.content.text and attachment.content.body, but Teams attachments in this codebase also support attachment.content.content (see the shared HTML extraction utility). When that variant arrives, this branch returns an empty string and the inbound message text is lost even though valid HTML content is present.
Useful? React with 👍 / 👎.
…ndicator (openclaw#60771) * msteams: add typingIndicator config and avoid duplicate DM typing * fix(msteams): validate typingIndicator config * fix(msteams): stop streaming before Teams timeout * fix(msteams): classify expired streams correctly * fix(msteams): handle link text from html attachments --------- Co-authored-by: Brad Groux <[email protected]>
Summary
channels.msteams.typingIndicatorconfig flag (defaulttrue)typingIndicatorand active stream statetypingIndicatorvalidates underMSTeamsConfigSchemaWhy
Testing
pnpm config:docs:genpnpm vitest run extensions/msteams/src/reply-dispatcher.test.ts --config vitest.extensions.config.tsFixes #56380
Fixes #60746