Skip to content

v0.4.26.1 — Python-only follow-up on 4.26

Choose a tag to compare

@patrick-chinchill patrick-chinchill released this 23 Apr 23:03
· 88 commits to main since this release
Immutable release. Only release title and notes can be modified.
d23b6d9

0.4.26.1 (2026-04-23)

Python-only follow-up on 0.4.26. Still alpha — APIs may change.

Fixes

  • Slack native streaming: SlackAdapter.stream() no longer calls
    AsyncWebClient.chat_stream(...) without await. The unawaited coroutine
    returned a truthy object, and the first streamer.append(...) raised
    AttributeError, breaking native Slack streaming for any consumer using
    the default adapter. Issue #44.
  • Teams divider renders at non-zero height: empty Container with
    separator: True rendered as zero-height in the Teams UI. Dividers
    between siblings now hoist separator: True onto the following element;
    a trailing divider emits a minimal non-empty Container. Issue #45.
  • ConcurrencyConfig.max_concurrent is now enforced: consumers setting
    concurrency=ConcurrencyConfig(strategy="concurrent", max_concurrent=N)
    now actually get an asyncio.Semaphore(N) cap on in-flight handlers.
    Previously the field was accepted and ignored (upstream TS has the same
    gap). None / unset keeps the unbounded default. Issue #51.

Python-specific (divergence from upstream 4.26)

  • Fallback streaming runtime robustness (cluster of fixes): framework-
    agnostic request.text() handling now tolerates sync Flask-style
    requests (was raising TypeError: object is not awaitable). Handlers
    typed Callable[..., Awaitable[None] | None] may return sync (None) —
    the dispatcher now awaits only when inspect.isawaitable() confirms,
    preventing runtime crashes on sync handlers.
  • max_concurrent enforcement (see above) — upstream accepts the
    config field but never enforces it; we do.

New public APIs

  • Chat.thread(thread_id, *, current_message=None): new worker-
    reconstruction factory mirroring TS chat.thread(threadId). Adapter is
    inferred from the thread-ID prefix; state and message history come from
    the Chat instance. current_message is preserved so Slack native
    streaming still works post-reconstruction. Issue #46.
  • SlackAdapter.current_token / current_client: public @property
    accessors for the request-context-bound bot token and a preconfigured
    AsyncWebClient. Replaces underscore access from consumer code making
    direct Slack Web API calls inside a handler (email resolution, user
    profile fetches, etc.). Issue #47.

Internals

  • Pyrefly: 213 → 0 type errors; baseline file removed. CI now enforces
    zero errors. Root causes fixed: 8-adapter lock_scope: LockScope | None
    protocol conformance; _ChatSingleton as Protocol; submodule-aware
    replace-imports-with-any; NoReturn on error re-raisers;
    inspect.isawaitable guards for duck-typed request handling and
    sync-or-async handler dispatch. No Any widening, no new # type: ignore lines beyond 10 at adapter event-construction sites where
    thread=None/channel=None get re-wrapped by Chat before handler
    dispatch (matches upstream TS's Omit<> partial-event pattern).
  • Test count: 3545 passed, 2 skipped.

Known gaps (not fixed in this release)

  • onOptionsLoad handler for dynamic select dropdowns — issue #50
  • Thread.getParticipants() method — issue #54
  • rehydrate_attachment adapter hook for queue/debounce + attachments —
    issue #52
  • 40 upstream tests without Python equivalents (Options Load, Plan variants,
    StreamingPlan options, getParticipants) — issue #53
  • Discord native Gateway WebSocket (HTTP-only today) — issue #57
  • Teams certificate-based mTLS auth — issue #58
  • Google Chat file uploads (TODO upstream too) — issue #59
  • Global handler-dispatch bound across reactions/actions/slash/modals — issue #61