feat(slack): dispatch message_changed / message_deleted to lifecycle handlers (#211) - #288
Conversation
…handlers (#211) Ports the Slack half of vercel/chat 4ac04551 (#788, chat@4.37.0) and the handleMessageChanged hunk of 864d9222 (#846, chat@4.39.0) on top of the #196 core handlers: message_deleted is no longer ignored, message_changed dispatches real edits (with a lazily parsed previous_message) after the unfurl-cache side step, and one _thread_id_for_message_event helper maps a message, its edit and its delete to the same thread. Adds _parse_slack_timestamp and the SlackEvent edit/delete fields. Python-specific: the bot's own message_changed returns before parsing (performance only; core would drop it after the parse).
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 43 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…211 tests and CHANGELOG - _with_inherited mirrors upstream {...m, k: m.k ?? fallback}: keys that resolve to None are left out of Message.raw instead of stored as explicit None. - New tests pin the outer channel_type inheritance for top-level DM edits, the hidden-edit edited.ts/text branches, deleted_ts / event_ts precedence, hidden === true, and team/team_id/type inheritance. - CHANGELOG: correct the no-handler consumer-impact claim (edits are still parsed and dispatched through core, as upstream does).
|
Merge gate: CI green (test 3.12, test 3.13, Lint & Type Check, CodeQL/Analyze python+actions, CodeRabbit) on d1cb081. Local Codex review (gpt-6-astra, xhigh, --base origin/main) on d1cb081 (merge of main incl. #286 Slack inbound normalization and #287 Teams): "No actionable regressions found against the specified merge base. All 999 Slack and message-lifecycle tests passed, and lint passed for the changed Python files." 1 astra round after the main merge (the prior convergence on 05b6e2e was also clean). Main merged cleanly with no conflicts; local validation green (7076 passed, strict fidelity 733/733, pyrefly 0 errors, fidelity target delta +0). CodeRabbit was rate-limited; no outstanding bot review findings. Merging with --admin (Protect Main requires a code-owner approval). |
Summary
The Slack adapter now passes message edits and deletes to the
on_message_updated/on_message_deletedhandlers that #196 added to core. Before this change it droppedmessage_deleted, and it usedmessage_changedonly to cache link unfurls.Upstream commits mapped
4ac04551feat(chat): add message update and delete lifecycle callbacks (feat(chat): add message update and delete lifecycle callbacks vercel/chat#788, chat@4.37.0). This PR ports the Slack half:message_deletedis no longer an ignored subtype and goes to the newhandleMessageDeleted.handleMessageChangednow dispatches edits.threadIdForMessageEventandparseSlackTimestamp.864d9222fix(slack): keep alert attachment content (fix(slack): keep alert attachment content on normalized messages vercel/chat#846, chat@4.39.0). Only thehandleMessageChangedhunk is ported:previousMessageis a lazy async factory that falls back to the sync parse. The attachment-content part belongs to [4.41/SL5] Slack inbound: pasted tables and alert attachments in message content #210.What changed (
src/chat_sdk/adapters/slack/adapter.py,types.py)_thread_id_for_message_event(event): new messages, edits and deletes all use this one helper.thread_ts or "". Anything else:thread_ts or ts or "". Upstream uses||here.message_deletedis routed to_handle_message_deleted.deleted_tsis the first non-Noneofdeleted_ts,message.tsandprevious_message.ts.previous_messageis normalized: it takeschannel/channel_type/team/team_idfrom the outer event when it lacks them (upstream??).previous.thread_ts/previous.ts.process_message_deleted(MessageDeletedEvent(...)).deleted_atis UTC fromevent_ts ?? ts,previous_messageis a sync parse andrawis the payload.message_changed, in this order:normalizedinner message.tombstone._cache_unfurls_from_message_changed, and its early returns are gone.process_message_updated(self, thread_id, factory, previous_message=<async factory or None>, options=options). The previous-message factory tries_parse_slack_messageand falls back to_parse_slack_message_syncwith a warning onException.CancelledErrorstill propagates._parse_slack_timestamp(ts): returns a tz-aware UTCdatetime, orNonefor missing or non-numeric input.SlackEventgainsdeleted_ts,event_ts,hidden,messageandprevious_message.Tests
All new tests are in
tests/test_slack_webhook.py. Upstreamadapter-slack/src/index.test.tsis not fidelity-mapped.Ported from upstream, using the
message subtype handlingandlink unfurl enrichmentdescribes at chat@4.41.1:Python-specific (
TestMessageLifecyclePythonSpecificand one test inTestUnfurlMetadata):CancelledErrorpropagates._parse_slack_timestampreturnsNoneforNone,"","abc","nan"and"inf", and a UTC datetime for"1700000000.123".message_changedmakes nousers_infocall and noprocess_message_updatedcall. A companion test checks that another user's edit still resolves its author.previous_message.tswith the DM thread rule; with no previous message thedeleted_tspath is used anddeleted_atisNonefor a non-numericevent_ts; a delete with no ts at all is dropped.channel_typeinherits it from the outer event (slack:D_DM:); hidden edits dispatch when onlyedited.tsor only the text changes;hidden: "true"(non-boolean) does not suppress;deleted_tsbeatsprevious_message.tsandevent_tsbeatsts;team/team_id/channel/typeare inherited on the edit and delete payloads (the pre-edit snapshot only gets channel / channel_type / type, as upstream); keys absent from both events are not added toraw.Chat: the handlers receive(thread, "after", "before")and a delete event withplatform="slack", andon_messagenever fires.Updated tests that encoded the old behavior:
TestIgnoredSubtypesdropsmessage_changed/message_deletedand now also asserts that neither lifecycle processor is called.TestMessageSubtypestests (test_message_changed_does_not_route_to_process_message,test_ignores_message_deleted) became the upstream "dispatches …" ports.test_message_changed_with_no_unfurls_does_not_write_stateis unchanged and still passes.Fidelity:
Delta vs committed report (HEAD): missing 151 -> 151 (+0). Slack adapter tests are not inMAPPING/TARGET_MAPPING, soscripts/fidelity_target.jsondid not change.Divergences (1)
docs/UPSTREAM_SYNC.md; breadcrumb in the code; pinned bytest_bot_own_edit_skips_user_lookup_and_update_dispatch)._handle_message_changedreturns when_is_message_from_self(normalized)is true, before it builds the parse factory.author.is_me, which comes from that same function, so handlers see exactly the same calls.users.infolookup, a participant state write, and up to 2 s of unfurl polling for every streamed delta (post+edit and native streaming each emit onemessage_changed).previous_messageis passed as an async factory, as upstream does, because core (#196) accepts factories. There is no sync-only divergence.Consumer impact
on_message_updated/on_message_deleted.handler(thread, message, previous_message).previous_messageisNonewhen Slack omits the snapshot.MessageDeletedEventwithchannel_id,message_id(the deleted ts),thread_id,deleted_at,previous_messageandraw.on_mention/on_subscribed_message/on_message).processMessageUpdatedresolves the factories without checking for handlers): a cachedusers.infolookup, a thread-participant state write, up to 2 s of unfurl polling when the text has links, and subscription/identity state reads. Previously amessage_changedwithout unfurls did no I/O. Each delete builds a sync-parsedMessageDeletedEvent(no lookup).channel_type,team,team_id) are only added toMessage.rawwhen the inner or outer event carries them, matching upstream??->undefined(no explicitNonekeys).Validation
ruff check / format,
audit_test_quality.py(0 hard failures),--check-docs, strict fidelity at chat@4.31.0 (all TS tests have Python equivalents),pytest tests/: 6945 passed, 24 skipped (after merging origin/main).pyrefly check: 0 errors.Review (gpt-6-astra)
team/team_id, so in multi-workspace mode an attachment on a serialized and rehydratedprevious_messageloses itsteamId.adapter-slack/src/index.ts:3670-3675builds the snapshot with onlychannel,channel_typeandtype.rehydrateAttachmentalso needsfetchMetadata.teamId.Merge gate
Final HEAD:
05b6e2e, which includes a cleangit merge origin/main.Findings from the independent reviewers (5):
Fixed: the CHANGELOG and this PR body said "with no handler registered, behavior is unchanged". Two reviewers reported that this is false. The text now says that with no handler registered, edits are still parsed and dispatched through core, which is what upstream does. No short-circuit was added, because it would be a new divergence.
Fixed:
normalized, the pre-edit snapshot and the deletepreviousstored an explicitNoneforchannel_type/team/team_idwhen neither event had them. This is the TS hazard where??producesundefined. The new_with_inheritedhelper adds a key only when its resolved value is notNone.test_inherited_keys_absent_from_both_events_are_not_added_to_rawfails without the fix.Fixed: no test covered a top-level DM edit whose
channel_typeis only on the outer event. Addedtest_top_level_dm_edit_inherits_channel_type_from_the_outer_event.Fixed: several mutants survived:
hidden is TrueAdded targeted tests. All 12 mutants the reviewer listed (M5, M5b, M7, M8, M9, M9b, M10, M11, M11b, M12, M19, M22) are now killed.
gpt-6-astra: 1 round on
05b6e2e. PASS, no actionable findings. The rounds on the HEAD before this pass are recorded above.Bots: CodeRabbit posted only rate-limit and draft-skip notices. No bot comments needed action.
CI: all green on
05b6e2e: Tests 3.12 and 3.13, Lint & Type Check (including strict fidelity and pyrefly), and CodeQL.Local validation: all green.
--check-docsCloses #211
Part of #184