Commit Graph

2250 Commits

Author SHA1 Message Date
Peter Steinberger 9a15d4cbf9 fix(discord): keep forum attachments and receipts in their first thread (#128765)
* fix(discord): keep structured forum attachments in one thread

* test(discord): type forum delivery fixture sender
2026-08-24 13:17:06 -07:00
ClawSweeper 29f39affc2 fix(agents): resolve terminal from admitted gateway (#128348)
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
2026-08-24 12:46:14 -04:00
Peter Steinberger a9fdb68496 fix(discord): preserve distinct super reaction events (#128649) 2026-08-24 03:17:10 -07:00
Peter Steinberger 234df15a6d chore: refresh dependencies after seven-day cooldown (#128414)
* build(deps): refresh dependencies after cooldown

Apply dependency, toolchain, action, image, and exact tool updates released by the inclusive 2026-08-16 seven-day cutoff. Adapt owner boundaries for the resulting CUA, logging, Teams, Markdown, native, and test-harness contract changes while retaining versions blocked by upstream compatibility constraints.

* fix(ui): align markdown renderer env typing

* fix(deps): align postcss and mistral peer contracts

* fix(deps): repair refreshed dependency contracts

* fix(deps): retain tslog startup budget

* fix(ci): verify Android tools with SHA-256

* fix(ci): fence Android SDK cache version
2026-08-24 03:01:54 -07:00
Peter Steinberger 23df5799fd fix(discord): classify announcement threads consistently (#128568)
* refactor(discord): deduplicate thread channel classification

* fix(discord): classify announcement threads during channel resolution
2026-08-24 01:02:16 -07:00
Peter Steinberger 65bcdf2f26 fix(discord): report actual delivered message kinds (#128575) 2026-08-24 00:05:07 -07:00
Peter Steinberger b136df2307 feat(channels): matrix custom emote discovery, discord guild-emoji cache (#128518)
Matrix gains emoji-list backed by MSC2545 image packs: room state
im.ponies.room_emotes (any state key) plus the personal
im.ponies.user_emotes account-data pack, sticker-only entries excluded,
identifiers are literal shortcodes (Matrix reactions send the annotation
key as-is; the mxc URL is exposed alongside). Discord emoji-list results
now ride the lifecycle-owned bounded entity cache, invalidated by
GuildEmojisUpdate; the non-privileged GuildExpressions intent is added so
that dispatch actually arrives. Google Chat custom-emoji listing is
documented as unavailable: customEmojis.list requires user auth while the
plugin is a chat.bot service-account channel.
2026-08-23 23:00:22 -07:00
Peter Steinberger 2aa5eee34e feat(channels): custom emoji discovery via emoji-list across Discord, Slack, Telegram (#128435)
* feat(channels): custom emoji discovery via emoji-list across Discord, Slack, Telegram

Make custom emojis discoverable by the agent. The message tool's emoji
param now documents custom-emoji syntax per channel (gate-aware, only
naming emoji-list when the action is actually advertised). Discord
emoji-list defaults guildId from the current conversation and returns
reaction-ready { name, identifier, animated? } entries; Slack returns
normalized shortcodes with aliasOf. Telegram gains emoji-list backed by
one canonical allowed-reactions owner (getChat available_reactions,
custom_emoji entries preserved), numeric custom-emoji reactions, and
replaces the dead 'reaction disallow list' error advice with a bounded
sample of the chat's allowed reactions.

* test(channels): expect telegram emoji-list provider-owned read gate in plugin shape contract

* test(telegram): prove emoji-list authority chain via mock-gateway e2e

Ephemeral gateway + mock Bot API + mock OpenAI provider: current-chat
emoji-list returns normalized standard and custom_emoji identifiers with
exactly one getChat call; a delegated cross-chat request is rejected with
the conversation-binding error and zero Bot API requests reference the
foreign chat.
2026-08-23 20:39:06 -07:00
Peter Steinberger cd10b65257 fix(discord): revalidate delivery authority per send (#128357)
* fix(discord): fence media caption follow-ups

* fix(discord): revalidate delivery custody per post

* test(discord): prove delivery fence over HTTP

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

* fix(discord): fence each retry attempt

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

* fix(discord): fence remaining retry paths

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-23 20:05:29 -07:00
Peter Steinberger 267f1ad917 perf(discord): reuse prepared outbound account (#128462)
Co-authored-by: Amp <amp@ampcode.com>
2026-08-23 19:52:19 -07:00
Peter Steinberger 3aab15c077 fix(discord): honor explicit component attachment filenames (#128401) 2026-08-23 17:56:50 -07:00
Peter Steinberger 7864980d9e fix(discord): expire stale thread starter cache entries (#128404) 2026-08-23 16:42:25 -07:00
Peter Steinberger 4d00dc4fca fix(outbound): retain attachments when plural media URLs are blank (#128372) 2026-08-23 14:35:39 -07:00
Peter Steinberger 663a9d2f15 fix(discord): recover question feedback after failed acknowledgements (#128293) 2026-08-23 10:51:12 -07:00
Peter Steinberger 3d75c27103 perf(discord): avoid duplicate inbound binding lookup (#128264)
Amp-Thread-ID: https://ampcode.com/threads/T-01a021f5-984a-7628-a30c-491c166ff247

Co-authored-by: Amp <amp@ampcode.com>
2026-08-23 09:13:04 -07:00
xingzhou 056d6a1da6 fix(discord): surface failed voice notes after text fallback (#127876)
* fix(discord): surface failed voice notes after text fallback

* fix(discord): preserve voice failure after attachment errors
2026-08-23 05:10:43 -07:00
Peter Steinberger 1475b67111 fix(discord): deliver embed-only and component-only messages (#128152) 2026-08-23 05:01:17 -07:00
sunlit-deng 1e9d493be9 fix(discord): preserve MIME extension for component uploads (#119671) 2026-08-23 04:55:20 -07:00
Masato Hoshino 6c37eb01d9 fix(discord): malformed gateway HELLO frame kills the bot's socket listener (#121468)
* fix(discord): survive malformed gateway HELLO frames

The HELLO branch dereferenced payload.d through a bare cast and replaced
only null/undefined with the 45s default, so a { op: 10, d: null } frame
threw a TypeError out of the socket message listener (no try/catch), and
a zero, negative, sub-second, or out-of-range interval reached setTimeout
unchanged, scheduling the first heartbeat immediately.

Normalize the HELLO body with the canonical record guard and accept the
interval only inside the timer-safe range, matching the QQ gateway. The
cast is removed rather than guarded.

* fix(discord): honor any heartbeat interval the gateway can schedule

Review of the first revision pointed out that the 1000 ms lower bound was
an interoperability break: a compatible gateway negotiating, say, 500 ms
would have had its interval replaced by the 45s default and then been
disconnected as unresponsive. Discord documents no lower bound.

Drop the bound to a positive timer-safe integer, so shape validation and
the timer ceiling stay while every interval this client can actually
schedule is honored. Regression rows pin 500 ms and 1 ms.
2026-08-23 04:14:15 -07:00
Sebastian B Otaegui b9b6ca46c3 fix(discord): upload-file with a base64 buffer fails without pointing at send (#123748)
* fix(discord): reject upload-file buffer attachments with send guidance

The flat message tool schema advertises buffer for every action, so
callers reasonably infer upload-file + buffer is valid on Discord. The
Discord handler only reads filePath/path/media for upload-file and
failed with an error that never mentioned the supported path.

Scope the buffer schema description to action="send" and make Discord
upload-file reject a buffer-only call with guidance to use send instead
of duplicating the send buffer pipeline under a second action.

* review: keep the shared buffer description channel-neutral

iMessage's upload-file deliberately consumes a buffer, so scoping the
shared schema text to send would teach models away from a supported
path. Keep the guidance owner-local: Discord's rejection carries it.
2026-08-23 02:32:11 -07:00
Yiğit ERDOĞAN 60136ec1a2 fix(discord): upload-file silently drops a caption (#124255)
* fix(discord,msteams): upload-file silently drops a caption

Both channels accept `media` as an alias for the uploaded file, so a
send-shaped call reaches `upload-file` carrying its text in `caption`.
Discord read only `message`/`content` and MS Teams read only
`text`/`content`/`message`, so that text was dropped and the file landed
with no message at all.

Accept `caption` as the lowest-precedence alias on both channels, matching
the WhatsApp and Slack upload paths. An explicitly empty higher-precedence
value still wins, so existing callers keep their behavior. The MS Teams
reader is scoped to `upload-file` because the other actions carry their own
text field.

* refactor: narrow this PR to the Discord channel

Microsoft Teams keeps the same defect and the same fix shape, but a Teams
after-fix delivery or activity trace is not reachable without a live tenant,
so that half is deferred to its own change rather than blocking the Discord
repair that already has a production-boundary trace.
2026-08-23 00:01:04 -07:00
Vito Cappello 5ddf381eed fix(channels): preserve gateway context for inbound turns (#127962)
* fix(channels): preserve gateway context for inbound turns

* fix(plugin-sdk): type bound channel reply dispatcher

* fix(channels): carry bound reply dispatchers

* fix(channels): keep reply carrier internal

* fix(auto-reply): consolidate dispatcher type imports

* fix(channels): keep reply dispatch typing internal

* fix(channels): derive inbound reply dispatcher types from turn plan

Keep ChannelRuntimeSurface identical to main: adapters read the bound
dispatchReplyFromConfig through the existing PluginRuntime["channel"]
wiring type and derive its type from the public ChannelInboundTurnPlan
contract, so the compatibility surface no longer grows a Gateway-bound
reply member.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(channels): hoist bound channel runtime casts for assertion ratchet

The assertion-safety ratchet collects SAFETY comments with a plain
ts.Scanner pass, which desyncs at the first template literal with a
substitution — comments after that point in a file are invisible, so
deep casts in monitor files cannot be SAFETY-covered. Hoist one shared
cast per call-site scope (absorbing the pre-existing buildContext
casts) and retype Discord's internal channelRuntime chain as
PluginRuntime["channel"] so dispatch reads need no assertion.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(channels): verify gateway dispatcher ownership

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(codex): assert sequenced node process notifications

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* docs(changelog): preserve release-owned root changelog

Channel Gateway tools preserve the owning dispatcher for Telegram, Discord, iMessage, Signal, and WhatsApp so terminal tools remain available. Thanks @VACInc.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(codex): avoid shadowing process notification bindings

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-22 13:44:47 -07:00
Peter Lee 97fe326660 fix(discord): treat audio transcripts as untrusted input (#123098)
Centralize untrusted audio transcript framing across channel plugins and keep Discord voice transcripts out of typed command provenance.

Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-22 14:28:18 +05:30
Peter Steinberger 3376c29800 perf(discord): avoid redundant payload cleaning (#127422)
Co-authored-by: Amp <amp@ampcode.com>
2026-08-21 12:42:14 -07:00
Josh Avant 0b85966072 fix gateway conversation route ownership (#126424) 2026-08-21 12:20:21 -07:00
Peter Steinberger 782a7d7aed refactor(discord): remove obsolete action runtime wrapper (#127123) 2026-08-21 02:07:48 -07:00
Peter Steinberger fd3a919063 fix(media): clean failed temporary staging (#127092) 2026-08-21 01:02:27 -07:00
Peter Steinberger 07c8b42a71 fix(channels): preserve delivery after preview cleanup (#126922) 2026-08-20 18:18:25 -07:00
Peter Steinberger 319df0e7a1 test: trim plugin test duplicates (#126917) 2026-08-20 18:07:27 -07:00
Peter Steinberger a4178c7eb1 fix(discord): demote expected command truncation logs (#126824) 2026-08-20 13:26:44 -07:00
Peter Steinberger 231405930a fix(discord): record activity after successful poll and sticker sends (#126791)
* fix(discord): record activity for structured outbound messages

* chore(discord): shrink structured-send assertion baseline
2026-08-20 11:56:07 -07:00
Peter Steinberger aafa61be65 fix(discord): preserve message text from every embed (#126752)
* fix(discord): preserve text from every message embed

* chore(discord): shrink assertion baseline after embed cleanup
2026-08-20 10:28:20 -07:00
Peter Steinberger 0961247d94 fix(discord): route polls through canonical delivery (#126250)
* fix(discord): route polls through canonical delivery

* test(discord): align canonical poll action coverage

* test(gateway): isolate canonical poll fallback coverage

* fix(gateway): align canonical poll capability
2026-08-19 18:07:21 -07:00
Peter Steinberger c6ecfd26e5 fix(outbound): preserve first-reply behavior through durable delivery (#126205)
* fix(outbound): preserve reply facts through durable delivery

* fix(protocol): regenerate message action models

* fix(outbound): complete reply fact custody
2026-08-19 17:52:12 -07:00
Peter Steinberger 2fe6c0206d fix(discord): retain attachment-only reply context (#126204) 2026-08-19 15:55:55 -07:00
Steven ff37627804 fix(discord): resolve realtime voice API key references (#125443)
* fix(discord): resolve realtime voice SecretRefs

* fix(discord): isolate realtime voice secret owners

* fix(discord): contain realtime secret owner gate

* fix(build): align plugin SDK boundary paths

* fix(discord): normalize realtime secret owner accounts

* fix(discord): gate canonical realtime secret owners

* style(discord): format realtime voice tests

* test(codex): assign run-attempt tools shard

* fix(discord): skip unavailable realtime providers

* fix(discord): preserve realtime provider availability errors

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-19 15:23:14 -07:00
Peter Steinberger 97557ec3f5 fix(widgets): route show_widget through Discord Activities (#126294)
* refactor(widgets): unify Discord presentation

* fix(discord): keep incomplete Activity routes private

* fix(discord): require usable Activity accounts

* docs(discord): clarify hidden Activity routes
2026-08-19 05:41:37 -07:00
Michael Appel 55f6700fe1 fix(discord): preserve realtime speaker context (#123243) 2026-08-19 04:25:24 -07:00
Peter Steinberger 554dfbe0a2 feat(discord): auto-join occupied voice rooms (#125974)
* feat(discord): auto-join occupied voice rooms

Add opt-in voice.autoJoin[].whenOccupied residency so Discord voice bots join for the first human and leave when the room becomes empty while preserving existing always-on, manual, transcript, and follow-user behavior.\n\nCloses #125973

* test(discord): isolate process runtime mocks

Use stable hoisted runtime-env mocks so isolate=false Discord test ordering cannot turn sleepWithAbort back into an unmocked function.

* fix(discord): defer unknown voice occupancy

Treat memberless voice states as unresolved instead of human so bot-only rooms cannot trigger occupancy-managed auto-join. Add cache-to-listener and manager regressions.\n\nCloses #125973

* test: isolate shared module mocks

Replace ineffective non-isolated module spies with stable hoisted mocks and a child-process SQLite connection-reuse probe so gateway and Discord shards are order-independent.

* test(gateway): inline connection reuse probe

Keep the child-process SQLite ownership probe in its owning Vitest file so Knip sees the full test surface without weakening process isolation or the original order regression.
2026-08-19 00:08:54 -07:00
Peter Steinberger baefd067bb fix(deps): keep package runtime dependencies single-owned (#126119)
* fix(deps): consolidate shared runtime helpers

* test(concurrency): support current test lib target

* fix(time): preserve year-scale plugin durations

* fix(agents): preserve empty subagent completions
2026-08-18 22:12:45 -07:00
Peter Steinberger cc273e6eea fix(channels): stop idle ingress retention writes (#126073)
* fix(channels): prune ingress retention on admission

* style(channels): keep ingress monitor within limit

* fix(channels): skip pruning ignored ingress
2026-08-18 19:05:30 -07:00
Peter Steinberger 2bed8caf9e feat(ui): channel conversation avatars in the sidebar (Discord + Slack) (#125668)
* feat(gateway): proxy channel conversation avatars

* feat(discord): capture conversation avatars

* feat(slack): capture DM sender avatars

* test(discord): bind guild avatar mock

* feat(ui): render channel conversation avatars

* fix(ui): align sidebar owner fixtures

* fix(gateway): version channel-avatar routes by media revision

A stable per-session URL let AuthenticatedAvatarRouteLoader's blob and
sticky-404 caches pin a mounted row to a stale or blank avatar after the
backing media changed. Append an opaque digest of the media reference so
replacement and 404-recovery change the route identity.

* test(ui): align sidebar owner facet

* fix(ui): keep owner chip until channel avatar loads

A session with a channelAvatarUrl suppressed its owner chip even while the
blob was loading, auth was not ready, or the route 404ed, leaving an empty
lead slot. The chip now rides as fallback content inside the avatar element
and yields only to a usable image. Covers 404 and auth-pending states;
avatar rows keep renderedOwnerId unset so an owner-viewer stays visible in
the facepile.

* perf(ui): keep channel avatar fallback within budget

* perf(ui): lazy-load the channel avatar element

The avatar element and its authenticated blob loader rode the startup
bundle through session-leading-indicator, pushing startup JS 51 B over the
CI gzip budget. Channel avatars are not startup-critical: register the
element on the first avatar row; the owner-chip fallback covers the
one-time upgrade window. Startup JS returns ~1 KiB under the ceiling.

* build(ui): raise startup baseline for channel avatars

CI-measured startup JS is 344379 B against a 343289 B baseline (+1090 B).
The avatar element and blob loader are code-split out of startup (previous
commit); the residual is the sidebar lead-slot render branch and row
plumbing, which cannot be deferred. Baseline updated via
check-control-ui-performance --update-baseline with CI bytes per the
script's contract; well inside the 4096 B ratchet step and 358400 B
ceiling.
2026-08-18 09:55:50 -07:00
Peter Steinberger 2d5334fb42 fix(tts): normalize streamed speech and report fallbacks (#125686)
* fix(tts): normalize streamed speech and report fallbacks

Streamed Discord voice now strips Markdown and raw link destinations like buffered synthesis.

Record the first failed streaming provider and reason code before successful file fallback.

* test(ui): stabilize reconnect retry timing

Drive the retry deterministically by advancing the exact 100 ms fake timer while preserving the ordering and outbox proof. Repairs CI run 32110130614 job 95627654678.

* fix(discord): ignore expected TTS stream skips

Address the ClawSweeper finding by retaining warning metadata only for actual failed attempts. Expected unsupported-for-streaming skips stay silent while real streaming failures still warn after successful file fallback.

* test(ui): settle interaction state before assertions

Repairs CI run 32117859632 by invoking the markdown table handler directly with a targeted event and waiting for durable IndexedDB composer state before navigation or page close.

* test: remove incidental UI and process identities

CI run 32120793258 exposed two owner-specific test assumptions.

Preserve the child-session hover visibility and pointer assertions, then focus and activate its menu with Enter so an unrelated delayed hovercard cannot intercept the pointer.

Assert the exact owned descendant PID is dead when QA cleanup completes instead of checking a globally reusable PGID.

* test(ui): stabilize connected send retry timing

CI run 32125741781 exposed the wall-clock race in the connected retry test.
2026-08-18 03:46:22 -07:00
Peter Steinberger 460b6b1e96 refactor(realtime): deduplicate voice audio handling (#125728)
Reuse the canonical realtime audio duration and OpenAI-compatible wire-format helpers while removing redundant state transitions and truncate payload construction. The production diff is net-negative.

Provider interruption policy and lifecycle ownership stay local to each provider; this does not introduce shared mutable assistant tracking or change behavior.
2026-08-18 02:00:12 -07:00
Peter Steinberger 0cc4d42f3f fix(discord): accept shipped nested DM access config (#125359)
Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

Co-authored-by: Amp <amp@ampcode.com>
2026-08-17 11:49:26 -07:00
Peter Steinberger eb38d5e486 fix(reply): report settled delivery outcomes once (#124773)
* refactor(reply): publish settled dispatch receipts

* refactor(reply): isolate dispatch outcome accounting

* refactor(discord): infer settled dispatch result

* test(reply): model settled dispatch receipts

* test(reply): type settled receipt fixtures

* test(telegram): declare settled final receipt

* test(telegram): adapt legacy dispatch fixtures

* fix(reply): complete settled receipt compatibility

* test(channels): keep delivery suite within lint budget

* test(channels): reuse settled count fixture

* test(channels): consolidate receipt assertions

* test(channels): extract delivery receipt fixtures

* fix(qa): recover stopped clients after config restart

* fix(reply): settle deferred delivery receipts

* test(channels): share delivery turn fixtures

* fix(channels): preserve non-visible reconciliation

* fix(reply): preserve settled receipt compatibility

* refactor(reply): make settled receipts canonical

* refactor(reply): absorb settlement at dispatcher boundary

* test(matrix): prove settled receipt precedence

* refactor(sessions): extract prompt snapshot types

* fix(reply): restore queued admission counts
2026-08-17 06:38:16 -07:00
Peter Steinberger 185b1ab726 fix(discord): retain progress drafts after error finals (#125140)
* fix(discord): retain error progress drafts

* test(discord): parse REST probe request URLs

* test(qa): force Discord error-final path

* test(qa): match Discord overload final
2026-08-17 02:22:02 -07:00
Peter Steinberger e3d553b734 fix(channels): surface Discord voice failures and preserve Telegram controls (#125152)
* fix(channels): surface degraded Discord and Telegram sends

* fix(telegram): preserve legacy slash callbacks

* fix(ci): correct env-var count after version define removal
2026-08-17 01:56:38 -07:00
Peter Steinberger 5ebfbbf8d7 fix(plugins): honor per-agent runtime config (#124978)
* fix(plugins): resolve per-agent config through SDK

* test(codex): preserve agent runtime exports

* test(telegram): make default owner explicit

* refactor(plugins): use lightweight agent scope runtime

* fix(codex): preserve multi-agent execution ownership

* chore(plugin-sdk): record approved agent scope exports

* fix(codex): keep scoped sandbox ownership authoritative

* fix(codex): preserve agent scope in native side actions

* fix(ci): avoid counting node check as environment variable
2026-08-17 01:53:08 -07:00
Peter Steinberger fcb499a4ce refactor(channels): move read-gate policy to channel plugins (#125136)
* refactor(channels): move read-gate policy to plugins

* test(channels): declare bundled Discord read gates
2026-08-17 01:38:30 -07:00