You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Add a same-thread concurrent _handle_message_activity test that exercises
the realistic _active_streams race (two near-simultaneous DM webhooks for
the same thread). Pins upstream's plain-Map clobber semantics: the second
registration overwrites the first, both in-flight handlers observe the
later session, and the registry ends empty after both finish. The original
distinct-threads test is renamed to make clear it covers session
ISOLATION, not the registry race.
- Empty-id final-send fallback: when Teams accepts streaming chunks but
returns id="" on the first activity, _close_stream_session now ships the
final message anyway (omitting streamId from channelData) instead of
skipping and leaving the streaming UI spinning until Teams times the
session out client-side. Mirrors upstream's looser check (text non-empty
→ ship the final). Adds a regression test and a non-parity row in
docs/UPSTREAM_SYNC.md.
- _chained_wait_until: resolve our internal processing_done gate BEFORE
invoking the caller-supplied waitUntil, so the deadlock-immunity argument
is trivially obvious (a misbehaving upstream callback can't starve the
await on processing_done).
- _TeamsStreamSession: add a public read-only `text` property so external
callers (now _close_stream_session) read through it instead of the
underscore-prefixed _text attribute. _stream_via_emit retains the direct
_text write as the canonical mutator.
Copy file name to clipboardExpand all lines: docs/UPSTREAM_SYNC.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -489,6 +489,7 @@ stay explicit instead of being rediscovered in code review.
489
489
| Fallback streaming final SentMessage content (non-Teams adapters) | SentMessage + final edit carry `final_content` (remend'd — inline markers auto-closed) | SentMessage + final edit carry raw `accumulated`| Narrow UX refinement. If a stream ends with an unclosed `*`/`~~`/etc., upstream ships the unclosed marker; we run `_remend` so the user sees a clean final message. Not observable in the common case where streams close their own markers. Teams native streaming and the Teams accumulate-and-post path both ship raw `accumulated`, matching upstream after #416; this divergence applies only to the remaining adapters that still route through `_fallback_stream`. |
490
490
| Teams group-chat / channel streaming via accumulate-and-post |`TeamsAdapter.stream` accumulates the full text and issues a single `post_message` instead of post+edit, even for group chats and channel threads | Same after vercel/chat#416 (`if (activeStream && !activeStream.canceled) … else { accumulate; postMessage }`) — no divergence at the adapter level | Documented for clarity: the Python port matches upstream's post-#416 behavior of avoiding the post+edit flicker where Teams doesn't support native streaming. The adapter no longer touches `_teams_update` from the streaming path. |
491
491
| Teams native streaming soft-cancel on send failure (DMs) | A `_teams_send` error mid-stream (e.g. 429, 5xx) cancels the streaming session, suppresses the exception, and returns the `RawMessage` with whatever text was already accepted | Upstream's `IStreamer.emit` is fire-and-forget under the SDK; no public hook to fail-soft on per-chunk send errors | Bot Framework streaming activities are advisory — once cancelled, Teams times the streaming UI out client-side. Soft-cancel preserves the chat-handler's ability to continue work after a transient backend hiccup, instead of bubbling the rate-limit through the stream iterator. The `RawMessage` carries the user-visible text so SentMessage history stays consistent. |
492
+
| Teams native streaming final-send when first chunk's `id` was empty (DMs) |`_close_stream_session` sends the final `message` activity whenever `text` is non-empty, even if `stream_id` is `None` (Bot Framework REST response returned `{"id": ""}` on the first chunk). The final activity omits `streamId` from `channelData` rather than serializing `None`. | Upstream's `streamViaEmit` awaits the `chunk` event for the first activity's `id`; if Teams returns an empty id, `messageId` becomes `""` and the SDK's auto-close emits the final activity through `IStreamer` regardless | Without the final `message` activity, the Teams client's streaming UI keeps spinning until the platform times the session out — a stuck-loading-state UX failure with no user workaround. We mirror upstream's looser check (text non-empty → ship the final) so the streaming indicator clears even when the Bot Framework REST response surface returns an empty `id`. |
492
493
| Teams divider rendering |`card_to_adaptive_card` hoists `separator: True` onto the next sibling (or emits a non-empty Container for a trailing divider) |`convertDividerToElement` emits an empty `Container` with `separator: True`| Upstream shares the same bug: Microsoft Teams renders an empty Container at zero height, so the separator line is effectively invisible. Python port fixes locally (issue #45) rather than blocking on upstream. |
493
494
|`SlackAdapter.current_token` / `current_client`| Public `@property` accessors that return the request-context-bound token and a preconfigured `AsyncWebClient`| Not exposed (`getToken()` is private on the TS `SlackAdapter`) | Python-only addition (issue #47). Downstream code that calls Slack Web APIs from inside a handler — email resolution, user profile fetches, reaction bookkeeping — otherwise depends on underscore-prefixed helpers. |
494
495
|`ConcurrencyConfig.max_concurrent`| Enforced via `asyncio.Semaphore` in the `"concurrent"` strategy path; rejects non-integer or `<= 0` values, and rejects any non-`None``max_concurrent` paired with a non-`"concurrent"` strategy | Accepted into the config type with docstring "Default: Infinity" but never read (3 writes, 0 reads) | Silent correctness bug upstream — consumers setting `max_concurrent=N` with `strategy="concurrent"` reasonably expect an N-way bound on in-flight handlers. We honor the documented contract via a semaphore and fail-fast on misconfiguration so it's never silent. `max_concurrent=None` stays compatible with every strategy (unbounded default). |
0 commit comments