Repository navigation
Conversation
arkheioncorp
left a comment
There was a problem hiding this comment.
Review: APPROVE
Verdict: APPROVE
Summary: Moves quoted, forwarded, and shared text out of the sender's text field and into channel_context / reply_to_text, so the gateway treats only the sender's own words as the request and expands @file:/@url: references only in it.
Findings:
- Security positive: This is a meaningful XSS/injection-surface reduction. Before this change, a forwarded message containing
see @file:planted.txtwould be expanded as if the sender wrote it. Now it stays literal inchannel_context. neutralize_untrusted_inline_textis correctly used for author names inattributed_context().- The
channel_context/reply_to_textsplit is clean: replies go toreply_to_text(withreply_to_message_id), forwards/shared content goes tochannel_context(withattributed_context()labeling). - All adapters updated: telegram, discord, wecom, qqbot, weixin, yuanbao, slack, feishu — each consistently puts other-people text in the right field.
base.pymerge logic (absorb_channel_context) preserves context across batched/merged messages.- Tests cover forwarded documents, forwarded messages, replied-to attachments, merged forwards, shared links, QQ quotes, Weixin quotes, WeCom quotes — comprehensive.
- One minor: the
reply_to_message_id=f"quote:{msg_id}"synthetic ID for quotes without a real message ID is a reasonable convention; just ensure downstream code that matches onreply_to_message_idhandles non-numeric prefixes (looks like it does, since it was already used in some form).
No blocking issues. Well-scoped, well-tested, and addresses a real security concern.
Matrix permalinks change with each triggering event. Rendering one in the pinned session prompt made the prompt stale or changed its bytes every turn, which breaks conversation prompt caching. Put the link in the current model-facing user message and remove it from the durable transcript, including queued turns. Respect effective PII redaction when adding the note. Percent-encode room IDs, event IDs and the via server so delimiters cannot change the matrix.to URL structure. Preserve the Matrix and Discord source-note handling when integrating the upstream prefix removal change. Use removeprefix after selecting the platform-specific note, so each transport removes only its own generated prefix.
The live Matrix exchange now checks the model request for a permalink to the event sent by the independent client. It also verifies that the link is absent from the system prompt, which must remain stable across turns. Record the exchange duration separately from container setup time.
Room version 12 IDs have no server component. A permalink built only from the room ID therefore omitted the server hint, which can prevent a client outside the room from locating it. Use the joined bot account's server when the room ID has no domain. The builder still leaves via absent when neither server is known.
A room ID can identify a server that no longer participates in the room. Its server may also include a port or IPv6 address, which the last-colon parser truncated to an invalid via hint. Use the joined bot account's server for inbound source links. When only a room ID is available, parse its full server after the first colon. The same parse also corrects the room's session scope.
In a thread, every turn's source link pointed at the thread root. The root was chosen as a stable anchor while the link lived in the pinned session prompt, which is per thread. The link now goes on each user turn, so the reason no longer applies, and the model never received the ID of the message that triggered the turn. A request to cite "this message" produced a link to the root, which may be another user's message. Build the per-turn link from the triggering event in all cases. A matrix.to link to an event inside a thread opens that thread in current clients, and the session prompt already gives the thread root.
A room-ID permalink needs via servers so that a reader outside the room can join through one of them. The link carried only the bot's server. When that server was unreachable from the reader's server, the link failed. Choose up to three servers as the Matrix specification recommends for room-ID permalinks. The first is the server of the highest-power joined member at power level 50 or above; room version 12 creators count as the highest power. The rest are the servers with the most joined members, and IP literals are never chosen. The adapter reads members, power levels and the create event from the mautrix state store, which sync keeps current, so building a link makes no request. When the store has no joined members for the room, use the bot's server and then the room ID's server. Server ACLs are not applied, because the state store does not keep m.room.server_acl. Move the builder and the selection into a sibling module, and emit one via parameter per server.
The session prompt stopped rendering the Matrix source link when the link moved to the current user turn. Two prompt tests still asserted that the prompt omitted the link when it was unset or redacted. No code path produces that output any more, so neither test could fail because of this feature. The prompt-stability test covers the remaining contract, and the per-turn tests cover the note and its PII suppression. Restore the comment on the per-turn notes. strip_inbound_source_note removes a note only when it is the first thing in the message, so a later prefix added outside the notes would leave the note in the persisted transcript.
Choosing source-link via servers made every inbound message await get_power_levels and get_create on the client's state store. The project-context isolation and voice tests gave the adapter a MagicMock store with only get_members awaitable, so all six inbound tests in those files failed with "'MagicMock' object can't be awaited". Production always uses mautrix's MemoryStateStore. Give both fakes a real MemoryStateStore in which the bot and the sender have joined the test rooms. The real store implements every state query, so these tests keep working when the adapter starts reading other room state. The stores are filled with MemoryStateStore.deserialize because the helpers are synchronous and some are called inside a running event loop.
The effective-state parent added a description to the matrix_read limit argument: "Maximum number of events to read". In this layer the same argument also bounds the members kind, which returns members rather than events, and the pins kind. Say that the limit applies to events or members so the model can tell that it bounds member lists too.
The merged matrix_read description said that events are listed oldest first and that `errors` lists events that could not be decrypted. Both sentences came from the history reads. A pins read returns events in the order of the room's m.room.pinned_events list, and its errors also cover pins that could not be fetched, are not visible messages, came back as another event, or whose edit was redacted. A model that read the description could tell a user that a deleted pin was an encrypted message. Limit the ordering sentence to room and thread reads, say that pins follow the room's pinned order, and say that `errors` lists events that could not be read or decrypted, with the reason. History reads already returned non-decryption errors such as "event content changed", so the wider wording is also accurate for them.
Matrix batches quick text messages into one turn, and the merged event keeps the first chunk's message_id. When thread backfill read the thread's relations, it excluded only that ID. If a user sent two quick messages into a thread without a session, the second message appeared under "[Earlier messages in this thread]" and again in the current turn. Record the IDs of the events merged into a pending event, in the text batch and in both busy-session merge branches, and exclude all of them from the backfill.
Reply resolution and thread backfill decrypt m.room.encrypted events when the client has the megolm session keys, but no test reached either branch. Thread backfill deserialises the raw event with mautrix, and the earlier test invocation could not import mautrix, so a test of that path would have skipped the event without failing. Drive both paths with real mautrix events and a crypto fake that decrypts only when the keys are available. A decrypted event supplies the quoted text or the thread entry, and a missing session leaves it out. Replacing either decryption call with a no-op fails all four cases.
The gateway core fetched earlier thread messages for a new Matrix thread
session itself. `_prepare_inbound_message_text` checked
`source.platform == Platform.MATRIX` and `hasattr(adapter,
"fetch_thread_context")`, which put Matrix-specific code in the runner
beside the generic `prepare_turn_context` hook that the room-context
layer added for room-state notes.
The two paths also disagreed about the `[New message]` marker. The
thread history went in front of the message text, and the room note
with its marker was prepended afterwards. When a room note was pending
and a message started a new thread session, the model received
<room note>
[New message]
<reply pointer>
[Earlier messages in this thread]
...
<message>
so the marker labelled the thread history as part of the new message.
Move the thread fetch into `MatrixAdapter.prepare_turn_context`. The
hook gains a `first_turn` argument, which the core sets when the
session transcript is empty, so an adapter can add history that only a
new session needs. The Matrix adapter returns the room note followed by
the thread history as one note, and the core puts the single
`[New message]` marker after all of it, before the reply pointer and
the message. The marker also follows thread history when no room note
is pending, as it already follows the Discord history backfill.
`TurnContextUpdate.channel_state` becomes optional. The adapter reads
room state only for text messages, and a failed read gives no
snapshot, but thread history is still reported in both cases; a `None`
snapshot leaves the saved state unchanged.
The core no longer checks the platform here, and any adapter can
report first-turn context through the same hook.
After the merge of NousResearch#128621, the adapter had two rules for one question: whether a leading "> " block is a legacy reply fallback or the user's own quote. NousResearch#128621 added _has_reply_fallback, which requires an <mx-reply> element at the start of the HTML body, or a plain quote block that starts with the quoted sender's pill and ends with a blank line. This branch's _strip_thread_reply_fallback used its own copy of the pill regex and checked the pill alone. The explicit-reply path in _extract_reply_context still treated any leading "> " as a fallback, because this branch's signature passed only formatted_body, and _has_reply_fallback needs the whole content. Use _has_reply_fallback for thread messages and for explicit replies, and remove _strip_thread_reply_fallback and its duplicate regex. _extract_reply_context now takes source_content as a positional argument before relates_to, as in NousResearch#128621, and reads formatted_body from it. The sender and chat_type keywords remain. Two test expectations change: - In NousResearch#128621's test_reply_strips_only_a_reply_fallback, the thread-message-own-quote case now expects no reply_to_message_id. This branch treats m.in_reply_to under is_falling_back as the thread's fallback target, not an explicit reply, so the event is not a reply. The message text, which is what that case checks, is unchanged. - test_thread_message_strips_only_the_reply_fallback gains a case for a thread message whose quote starts with a pill but has no blank line after it. The pill-only rule stripped that quote; the shared rule keeps it as the user's text. The shared rule also strips a thread message's plain quote when the HTML body starts with <mx-reply>, even without a pill, as it already does for explicit replies.
Two helpers check whether a reply's HTML body starts with an <mx-reply> fallback. _has_reply_fallback decides whether the plain-text quote is stripped from the message, and extract_mx_reply_quote returns the quote as reply_to_text. Their checks disagreed. _has_reply_fallback required format "org.matrix.custom.html" and a bare "<mx-reply>" tag. extract_mx_reply_quote ignored format and accepted any body that started with "<mx-reply". When the tag had an attribute, as in <mx-reply data-x="1">, or the event had no format field, and the plain-text quote had no pill, the message kept the quote and reply_to_text repeated it. The model then saw the quote twice. Add starts_with_mx_reply(content) to reply_context.py and call it from both helpers. It requires the HTML format and matches an <mx-reply> start tag with or without attributes, because the spec defines the fallback as a formatted_body that "begins with an <mx-reply> start tag". The match ignores case because the quote extractor's HTML parser lower-cases tag names, so the check accepts every tag that the extractor reads as <mx-reply>. extract_mx_reply_quote now takes the event content instead of the formatted body because it needs the format field.
Busy-text debounce updates the reply anchor to the latest inbound message but did not record the earlier message IDs. A new Matrix thread session therefore fetched an earlier trigger as thread history and included the same text again in the current user message. Record each previous anchor and incoming batch's merged IDs before the anchor changes. Thread-history preparation can then exclude every event already represented in the current turn. Input without a new anchor keeps its existing anchor and still contributes its merged IDs. Extend the real model-preparation invariant to both ingress and busy batching. The busy case repeated the first trigger before this change and now excludes it. Independent full-event expectations also reproduce six missing-ID cases and pass after the fix. The pending-merge contract checks unknown, unauthorized and authorized quoted authors together with media and merged IDs.
The room-context prerequisite adds channel_state as the final positional MessageEvent constructor argument. Threading inserted two fields before that argument, so an existing positional room-state dictionary became the reply-author authorization value and the event lost its room state. Append the new threading fields after channel_state. Existing positional construction keeps its original meaning, and the new authorization field defaults to None. A direct constructor probe reproduces the old misassignment and passes after reordering; all 330 tests in the seven adjacent event, thread and gateway files pass.
The reaction follow-up layer now includes the complete action and effective-event prerequisites. Text intake and event construction pass reply_parent explicitly, while reaction tools require joined room state and an authorised requester. The old callbacks rejected the keyword, and the queued identity test failed before exercising context restoration. Accept the reply context in those callbacks and configure the queued fixture with joined membership and both intended requesters. Remove the duplicated identity argument introduced when replaying the metadata test. The admission, cursor, FIFO and A/B/A identity assertions remain intact. The adjacent background-task control also created an unawaited coroutine because its AsyncMock treated toolsets_for_source as asynchronous. Give that synchronous method an explicit MagicMock, like the existing media extractors. The warning reproduces on the original published branch under strict coroutine checks; the corrected control and the composed fixture checks pass all 103 cases.
Move reaction watch registration, delivery tracking and admission into a topical mixin before changing their interaction with effective reply contexts. The adapter exceeds the project file-size threshold; the mixin keeps the existing behaviour and gateway bindings together. The plugin loader imports each adapter under a profile-owned namespace. A class imported through the source path has a different identity, so check the shared Matrix mixin when registering a delivery. A real plugin loader invariant verifies watch registration across A/B/A profile homes.
A reaction to the last chunk of a split final reply loses the beginning of the logical answer when gateway preparation refreshes the concrete reply event. After an encrypted restart, the follow-up therefore quotes the reacted chunk instead of the saved final excerpt. Record the delivered prefix in delivery order, with digests of the final visible bodies. Persist those bounded dependencies with the watch and propagate their retained effective states through the existing turn snapshot. Reuse the exact saved excerpt while the visible prefix remains unchanged. Current edits replace the quotation, and redactions or content errors prevent stale text from entering the model input. The reacted target is checked separately even when it follows the quoted prefix. The regressions reproduce the restart loss and stale prefix quotation on the previous head. The strict seven-file gate passes 172 tests with the actual Matrix SDK, four workers and retries disabled.
A cancelled reaction handler deletes its single-use watch before the gateway admits the follow-up. Cancellation during the busy-session lookup leaves no queued input and no acknowledged sync event, so replaying the same reaction cannot resume the conversation. Reserve the watch for that reaction instead of deleting it immediately. Release the reservation when admission fails before the gateway consumes the event. Delete it after admission, including cancellation while a queued event's durable acknowledgement finishes. Persist the reaction ID so a restart can retry the same event without allowing a different reaction to consume the watch. Claim tokens make completion conditional on the reservation's current owner. The regression cancels the real busy admission path after its session lookup, then retries through the native sync dispatcher. It also checks reservation replacement after reopening the store and cancellation after FIFO admission while the durable write is pending. The strict eight-file gate passes 175 tests with the actual Matrix SDK and no retries. The reaction-admission tests created SessionStores but finished with their shared SQLite handles still open. All three cancellation cases passed, followed by six unclosed database warnings at interpreter exit. Close each test-owned store after cancellation and task gathering in the existing finally block. The cancellation and exactly-once assertions stay unchanged. The three complete child cases now exit without unclosed database warnings.
The reaction follow-up contract registered the gateway authorisation callback, then expected the adapter's direct user check to deny every caller. Matrix now delegates that check to the live callback so pairing revocations take effect immediately. Global, platform and pairing grants therefore fail the old assertion before the reaction path runs. Expect the direct check to match the configured grant. Keep the actual follow-up admission assertion and the denied case, which retains its reaction watch without dispatching a turn. Historical attribution for original source a4d3478 (audit record 314; historical approval position 315). Public PR NousResearch#126281 already includes this assertion correction at its captured head 1369cea. This empty commit records the missing original contribution credit. The source tree is unchanged.
Reaction sends retain every delivered event ID, and final edits update effective content after successful delivery. Those outcomes can still report success when the adapter exceeds Matrix's byte budget. Check the complete receipt result for multibyte chunks. Check that a final edit fits the content budget without changing its complete effective content, finalization flag or successful cache update. On the unchanged public source, these contracts reproduce a 16-byte chunk under a 12-byte limit and a 59,494-byte formatted edit under a 45,000-byte content limit. Both pass in the combined upstream and reaction implementation.
Restoring every accepted follow-up separately preserves FIFO input, but it also separates a parked photo from the compatible goal continuation that should consume it. The photo then completes with a separate turn while the continuation remains pending. At the recursion limit, merge compatible photo and text input through the existing recorded merger and attach its processing lifecycle to the consuming event. Conflicting replies, security contexts and execution owners still use accepted FIFO restoration. That path retains every previously admitted input without repeating capacity admission. All 93 original lifecycle routes now pass, including merged photos and orphan cancellation. The queued profile and reply contracts use the actual TurnContext model, so the fixture includes the receipt state without replacing its identity or profile assertions. Source-commit: a51649262e4290a32bdd2f64e29d704b7425747d Integration-note: A fresh processing task previously promoted its reservation to accepted queued input before the runner decided whether to merge it. Preserve the existing accepted bit when a queued reservation is reused, but create fresh processing reservations with accepted=False. This keeps fresh photos with compatible pending input and retains accepted FIFO restoration for previously queued records. The actual processing contract reproduces the fresh-input failure and retains the accepted-input control. The qualified 111-case gate excludes only the two independently diagnosed shutdown observer cases; the complete gate follows their owner correction.
The shared batching implementation is in the platform facade, which is already about 4,700 lines long. A new reply-boundary hook would add more behaviour to that facade. Move the existing batching methods to BaseTextBatchingMixin before changing their behaviour. Keep every method body unchanged and type the mixin receiver. Update the gateway instructions to specify the new implementation path. The fixed upstream annotation cleanup overlaps the extracted text-batch methods. Keep the complete methods in their topical module and apply the same unquoted event annotations there. Their bodies are unchanged.
Gate finding BR8 r1 2c [Medium]: the new reply-context conflict rule made the debounce's pending-slot merge reachable for one sender. one->X, two->Y, three->X left pending='one\nthree' and buffer='two', so the agent saw "three" before "two". Same-sender text now stays in the debounce buffer; only a different sender may merge into the pending slot.
The shared text batcher appends text and media without absorbing reply context. A plain message followed by a quoted message loses the quote, and two different reply targets become one turn with the first target. Use the existing boundary hook to dispatch incompatible reply contexts as separate batches. Absorb the complete reply context when one event has no quote, and pad attachment flags before extending media URLs. Keep the shared dispatch cancellation policy for completed batches. WeCom now promotes the current batch after enqueueing. Looking up the previous batch would change the attachment event that a reply boundary has already dispatched. Keep the upstream unquoted event annotations and format the extracted module consistently. Method bodies and assertions are unchanged.
The Telegram adapter contains the teardown of text, photo and album buffers in a large transport facade. Reply-boundary deliveries need the same teardown policy. Move the existing cancellation method into TelegramDelayedDeliveryMixin before extending it. Keep the method body and adapter method resolution unchanged, and type the mixin receiver.
A reply boundary starts a flush outside the ordinary delivery maps. Delivery teardown therefore misses a boundary that has already popped its event and is awaiting dispatch. Cancelling a boundary before its coroutine starts can also lose the event in its private temporary map. Keep boundary events and tasks in a dedicated buffer. Include that buffer in delivery cancellation, salvage and clearing. Cancellation requeues an in-flight event through the existing flush body; buffered boundaries are salvaged before the later ordinary text batch. The event-synchronised regression reproduces the missing earlier batch and unfinished task before the fix. It checks both complete events and that the boundary task has exited when teardown returns.
Inbound preparation combines profile scope, context references, media notes and reply pointers in a large intake module. Move that existing pipeline into a topical sibling before changing its expansion and quoting behaviour. GatewayInboundMixin inherits the methods, and the lazy intake-helper imports preserve the existing runtime patch seams. Compose the inbound extraction with the required upstream revision. Move all fifteen complete current context methods, including their current type annotations, while retaining the remaining upstream intake methods and the plugin injection base. Keep the existing local imports for the Discord routing note and media rehoming at their new defining scopes. Keep the extracted inbound methods in their topical module and apply the upstream unquoted annotations there. Method bodies are unchanged.
Image descriptions are generated context. A reference in a description must not trigger local file access. Document why image analysis follows authored caption and speech expansion so a later ordering change does not treat generated references as sender input.
Pending-event merges and busy-text debounce share reply and attachment semantics inside the large base adapter. Telegram's ingress overrides also modify the same batching boundary. Extract those existing topics before integrating the reviewed reply-context fixes. Repoint in-tree imports and patch targets to the defining merge module. The adapter classes inherit their existing methods, so timers, tracked redispatch and delayed-delivery ownership keep their current behaviour.
Four routing tests pass a recording transport to RelayAdapter, whose transport parameter requires the RelayTransport protocol. The fixture only implements its inbound setter and outbound send, so the four constructor calls fail static type checking even though their exercised routing paths work. Implement the complete structural protocol in the recording fixture. Unused operations raise an assertion error so an unexpected call fails the test. Keep the recorded action, platform and send result, and narrow the captured dictionaries before the existing routing assertions. The correction changes the inherited test fixture. It preserves the existing contributor records and all original routing assertions.
The encrypted-file fixture returned a constant serialized payload, so assigning its uploaded URL did not appear in later serialization. The Matrix SDK serializes the URL stored on the encrypted-file object. The adapter currently overwrites the outgoing URL, which concealed this fixture difference in the existing upload test. Read the fixture URL when serialization is called. Add an invariant that compares the complete payload before and after URL assignment, with only the URL overwritten. Keep the existing upload and routing assertions.
Matrix receives ordinary m.reaction events, but reads and catch-up only showed message events. The bundled m.relations summary cannot provide annotation reactions. Read bounded annotation children through the live Matrix client. Decrypt encrypted reactions to verify their type, but use the outer cleartext m.relates_to for the target and key even if the decrypted payload specifies another relation. Discard redacted events and deduplicate by sender and key. Present the actor and target in read and catch-up context without starting a turn. Mark clipped keys and report incomplete reads when keys, API failures or bounded pages prevent a complete snapshot. A five-second batch deadline keeps a slow homeserver from delaying a turn for every earlier message. Completed lookups remain visible when others time out. A separate Matrix client confirms that a reaction appears in the next mentioned turn. Focused tests cover duplicate and redacted reactions, encrypted events, long keys, lookup errors, and the deadline. Bind the real room-policy predicate in the read fixtures. Read admission rechecks room policy after awaiting room identity, so the fixture must provide the same adapter protocol as the production path.
Source-permalink and reaction-context branches each test their own prompt contract. Their combined adapter adds the trigger permalink to catch-up messages, so the reaction fixtures need the SDK's asynchronous state-store methods and complete expected source notes. Merge both feature histories without changing their existing commits. Preserve reaction author trust, room and thread context, transcript roles, cache privacy and the source-permalink encoding and routing rules. Use the real MemoryStateStore in the affected fixtures and retrieve SessionSource from the final context tuple item. Check each complete catch-up prompt against its explicit room, trigger event and via server, including the first event of a batched turn. The four composed gateway files cover 343 cases. The affected two files pass all 264 cases after the fixture adaptation; the unchanged session and project-isolation files passed their 79 cases. Scoped Ruff reports no lint diagnostics. Existing formatter differences remain qualified separately. Live Docker room and thread backfill validation is still outstanding. This dependent layer preserves both parent histories. Its two live backfill fixture adaptations do not claim complete original 858 delivery. Historical composition qualification The preceding message records the original two-parent composition. This successor reapplies its fixture integration after the individually preserved prerequisite, SDK and reaction contributions on captured main 648a309. Its history has one parent. The original composition message remains complete for provenance; this commit does not create a merge or establish live Matrix acceptance.
The original feature tests predate the canonical approval lifecycle, permalink source fields, room policy owner argument and SDK send error classification. Their doubles omitted asynchronous state-store reads, exact approval request IDs and terminal card edits. Several complete expected events also omitted the newly composed source and mention metadata, so the full feature gate failed before checking its intended behaviour. Update those fixture bindings and complete expected values for the current defining owners. Keep every original parameter row, delivery attempt, payload, cancellation and authorization assertion. Thread reconnect expectations retain confirmed delivery and cache identity. Catch-up expectations include the permalink note before the new message. The complete nine-file feature boundary passes all 808 cases with the actual installed Matrix SDK, four workers and no file retries. The full Matrix file finishes in 30.7 seconds, including the event-loop lookup contract that timed out on the earlier incomplete source composition. Original858 public source qualification The preceding 808-case result records the original composition. It does not describe a new run against this public tree. The published backfill composition has 343 scoped cases, using 264 final affected cases and 79 unchanged earlier successes. Its inherited formatter and type findings remain qualified, and live Docker validation remains pending. The other feature contracts are represented by their existing public owners. Their fixture expectations follow the actual approval, notice, location, send-classification, read-policy and delivered-event APIs. Expectations from the original later composition do not establish bugs in standalone owners that use an earlier API. No combined six-module endpoint or repeated native gate is required for this source credit. This source-neutral contribution preserves the complete original message and both original contributor dates. It records the distributed source qualification without changing the published source tree. Current linear successor qualification The 343-case result above describes the earlier a76019d composition. This contribution follows the verified single-parent successor a83881c and keeps its source tree unchanged. The accepted seven-file gateway gate for that successor passes 362 cases. Live Matrix validation, private integration and whole-campaign completion remain separate.
The Matrix SDK retries rate-limit responses and connection timeouts indefinitely by default. A failed request during account or room setup can therefore stop the whole native module until the outer runner kills it, without reporting the setup error. Disable those retries and bound ordinary fixture requests to fifteen seconds. Apply the same configuration to registration, restored accounts and the encrypted observer. The fixtures now expose the SDK error so the failing operation can be diagnosed directly. A real loopback HTTP server reproduces the repeated rate-limit requests through registration and restored accounts. Both paths now make one request and return the expected error.
Matrix turns could read message history, but the agent could not inspect the current room state, joined members, power levels or pinned messages. Those details are available from the receiving bot and can affect what the agent should say or attempt. Extend the Matrix-only read tool with bounded inspection kinds. Fetch state and members through the owning adapter, apply its existing room and requester policy, and resolve pinned messages with the same decryption path as event reads. Report missing pin content per event so the other pins remain visible. Include the event type and creator override when presenting permission thresholds. A separate Matrix client verifies that the model receives the room details and pinned message from a disposable Synapse instance. Use the effective-state parent's client-events decoder and gateway scheduling path throughout inspection and its tests. Recheck admission after identity resolution, derive permission defaults from raw state, and preserve cancellation while refreshing pinned content. The feature and these interface migrations form one change so the initial commit uses the current prerequisite APIs.
The dependency-rebinding fixtures installed a fake importer but left the package-availability check unchanged. With a complete SDK installed, ensure_matrix_deps skips the importer, so these fixtures failed without exercising their intended success and refusal paths. Report a missing Matrix package in the fixture alongside the existing installation seam. The real rebind and failed-import assertions then exercise the intended fresh-install boundary.
The Matrix adapter ignores emotes and does not register native sticker events. Emotes describe the sender's action, while stickers use a separate event type with a description and an image. The existing text handler would interpret a slash-like action body as a gateway command, so native actions need attributed text before command classification. Accept both through the existing room, sender and mention gates. Prefix emotes with their actor, preserve sticker descriptions, and download sticker pixels through the bounded media path. Use the same labels in effective reads, replies and thread history. Queued stickers can be edited or redacted while media preparation is pending. Add an optional media-event snapshot capability and retain the source paths of image descriptions, so final model preparation removes both withdrawn pixels and their derived text. Existing platform snapshots remain compatible and previous conversation context remains unchanged. The two parameterised invariants reproduce six failures on the committed parents and now cover native SDK events, gates, unavailable media and withdrawal during asynchronous preparation. Matrix and adjacent gateway checks pass against the pinned SDK source.
iainlane
force-pushed
the
fix/gateway-quoted-text-out-of-body
branch
from
October 8, 2026 16:05
f9b241b to
0560602
Compare
Discord ingress can create a thread from a text-channel message. The source then identifies the thread, so its topic must come from that same route. Looking up the original text channel instead adds its instructions to a thread that does not inherit text-channel topics. Exercise the real Discord SDK objects through inbound dispatch for direct channels, existing threads and newly created threads. The new-thread row fails when the topic lookup uses the original message channel; the corrected effective-channel lookup passes all three rows.
The fake HTTP API discarded the client_session supplied by the Matrix adapter, so disconnect closed a mock while the real aiohttp session was left open. A reconnect test also replaced disconnect with a stub and left its new connection open after checking the guard. Strict resource warnings expose both leaks. Preserve the supplied session in the fake API. Use a wrapping disconnect spy in the reconnect test and disconnect the replacement client after the existing call assertion. The fake API and reconnect fixture predate this branch, so this focused fixture maintenance is separate from the corrections folded into their campaign contributors. Production code and all original assertions are unchanged.
Pending-event merges and busy-text debounce share reply and attachment semantics inside the large base adapter. Telegram's ingress overrides also modify the same batching boundary. Extract those existing topics before integrating the reviewed reply-context fixes. Repoint in-tree imports and patch targets to the defining merge module. The adapter classes inherit their existing methods, so timers, tracked redispatch and delayed-delivery ownership keep their current behaviour. The fixed upstream annotation cleanup overlaps the extracted debounce state. Keep the state in its existing topical module and apply the same unquoted task annotation there. The complete state and timer behaviour remain unchanged.
iainlane
force-pushed
the
fix/gateway-quoted-text-out-of-body
branch
2 times, most recently
from
October 9, 2026 21:29
5923b54 to
5185097
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack
#128679 and #129160 must merge before this PR. Their order relative to each other is independent. #128655 follows this PR. The nesting records the logical dependencies, including both prerequisites of this PR.
Quoted and forwarded content must remain distinct from the sender's own words. Otherwise the gateway can expand a file or URL reference that another person wrote, or lose the quoted context when several messages are batched.
Represent quoted and forwarded text as attributed event context across Discord, Telegram, Slack, Feishu, WeCom, Weixin, QQ and Yuanbao. Preserve that context through adapter and gateway batching. Extract the existing receiver methods into topical modules before changing their behaviour.
The branch composes the current published contributions from speech and reference handling and gateway batching. Reference expansion receives the sender's actual caption and transcript before generated notes, quoted text or other people's context are added. The composition preserves the current Matrix context snapshot, media metadata, retention and rendering paths. Native Discord thread dispatch also retains the channel topic.
Review and history
The direct published batching prerequisite is
627fb2227c402f619e699eb2afd9214dd6e9180d. Above it are eight original source contributions from the current published #128679, followed by this PR's 24 original records. Those 24 include eight intentional contributor-credit records and 16 source or test contributions. All current original author and committer identities, dates, timezones, complete message prefixes, source references, order and empty credit records are preserved. Three bounded source joins preserve the existing extractions and apply the captured upstream annotation, UTC and timeout-alias cleanup inside their actual defining modules. The WeCom reply-context simplification retains the current UTC timestamp alias.The own comparison is the composed context prerequisite through this PR. All 32 records are linear and signed. The published #128679 itself remains unchanged.
Validation
I ran all 48 affected complete test files with the installed SDKs, four workers, no retries and strict runtime, resource, unraisable-exception and thread warnings. The run recorded 955 passes and seven host skips. Two unchanged files retain two failures and three errors, so this is not a fully green scope.
These checks exercise local dispatch, batching and real SDK imports with transport doubles. They do not establish live service exchange or hosted CI approval.