Commit Graph

1495 Commits

Author SHA1 Message Date
Vincent Koc 8bc499efa4 test(slack): isolate plugin command dispatch proof (#127805) 2026-08-22 02:00:05 -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 1d5d5cf462 perf(slack): reuse prepared payload without identity (#127678)
Amp-Thread-ID: https://ampcode.com/threads/T-01a021f5-984a-7628-a30c-491c166ff247

Co-authored-by: Amp <amp@ampcode.com>
2026-08-21 16:30:00 -07:00
Josh Avant 0b85966072 fix gateway conversation route ownership (#126424) 2026-08-21 12:20:21 -07:00
Peter Steinberger f7cbe9e5da fix(slack): preserve partial reply delivery (#127130) 2026-08-21 02:32:05 -07:00
Peter Steinberger 07c8b42a71 fix(channels): preserve delivery after preview cleanup (#126922) 2026-08-20 18:18:25 -07:00
Sarah Fortune 67bcea131e feat(slack): add compact commentary progress (#126480)
* feat(slack): add compact progress style

* chore(slack): protect compact progress contract

* chore(slack): clarify compact progress comment

* chore(slack): document compact task card behavior

* chore(slack): document compact draft card behavior

* chore(slack): document compact progress config contract

* fix(slack): enforce compact commentary-only progress

* chore(slack): strengthen compact style guard

* fix(slack): type compact progress config

* fix(slack): scope compact plan suppression

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-20 15:02:33 -07:00
Peter Steinberger 8efbf7ce00 fix(ui): session hovercards stop replaying unchanged PR status (#126726)
* perf(ui): avoid replaying unchanged session PR subscriptions

* test: ratchet assertion safety baseline

* test(ui): assert service worker update outcomes

* test(slack): avoid wall-clock rate-limit assertion
2026-08-20 11:11:40 -07:00
Peter Steinberger 3378e07d50 refactor(plugin-sdk): promote shared runtime primitives (#126193)
* refactor(plugin-sdk): promote shared runtime primitives

* test(codex): keep one attempt tools owner
2026-08-19 01:10:18 -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 3eecab1bee refactor(plugins): adopt shared retry runtime (#126065) 2026-08-18 17:46:38 -07:00
Peter Steinberger 6f8e5d9078 test(slack): remove cache reset seams (#125971) 2026-08-18 12:47:14 -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 d95d591d98 fix(slack): require message timestamps for delivery success (#125762) 2026-08-18 03:16:17 -07:00
pash-openai 31a240c2b7 fix(slack): remove interrupted previews when turns finish silently (#125494) 2026-08-18 01:36:21 +00: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 9329e4d76a refactor(approvals): consolidate channel approval kind (#125215) 2026-08-17 04:08:10 -07:00
Ayaan Zaidi f2754e4318 fix(slack): stream native task rows as deltas (#125168)
Slack appends task_update details/output per update for the same id (only title/status replace); the native progress stream re-sent every row each snapshot, so bash rows accumulated "completedcompleted…". Reconcile now emits only changed rows with append-only field deltas, keeps status words out of details, and puts the once-emitted result (file delta or failing exit) in output.
2026-08-17 14:39:20 +05:30
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
Peter Steinberger c26eda0620 feat(approvals): add request kind discriminant (#125084)
* feat(approvals): add request kind discriminant

* test(approvals): expect normalized legacy request
2026-08-16 23:30:39 -07:00
Peter Steinberger f9b1ccc4fa refactor(plugin-sdk): discriminate delivery and exec results (#124979)
* refactor(plugin-sdk): discriminate delivery and exec results

* test(plugin-sdk): update delivery target expectations

* fix(outbound): preserve canonical delivery targets

* fix(irc): normalize message delivery target
2026-08-16 22:55:41 -07:00
Sarah Fortune fc677bcc3c fix(slack): honor bot policy for Enterprise Grid messages (#125009)
* fix(slack): honor bot policy for Enterprise messages

* fix(slack): route Enterprise message subtypes

* test(slack): fix Enterprise system event labels

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-16 22:13:51 -07:00
Peter Steinberger 04fd260b4c refactor(channels): rename progress work counter (#125011) 2026-08-16 22:11:21 -07:00
Peter Steinberger 7349177ce3 feat: main-session agent-wide visibility + session.groupScope routing (#124965)
* feat: add main session group routing

* docs: explain main session routing scopes

* fix: align memory session visibility

* test(qa): cover main-scoped group bindings

* fix(sessions): preserve binding-scoped outbound routes

* fix(routing): preserve explicit outbound owners

* fix(sessions): recognize global main visibility

* chore(ci): prune assertion safety baseline
2026-08-16 19:57:18 -07:00
Peter Steinberger 2012af16f2 refactor(channels): delete the per-turn progress receipt line (#124972)
The finished-turn digest ('🧠 N thoughts · 💬 N notes · 🛠️ N tool calls ·
⏱️ Ns') was synthesized onto three surfaces: Slack's Block Kit card footer
(removed in #123851), Discord's final-answer subtext, and a duplicate Telegram
implementation added to match Discord.

Nothing gated it — no config key for the receipt has ever existed on any
channel — so PR #122976, which deleted it from Slack's native progress surface,
could only ever be a partial removal. Finish it: live progress surfaces keep
their in-flight indicators, and a finished turn leaves the answer with nothing
synthesized appended.

Telegram's window collapsed *into* the digest, so it now retires like every
other surface; Discord already discarded its draft after the final landed. The
core tracker keeps only the live counters Slack's working footer reads.
2026-08-16 19:14:42 -07:00
Peter Steinberger 2d887acba3 fix(slack): drop the tool-call receipt from finished progress cards (#123851)
The Block Kit session card terminalized into a '🛠️ N tool calls · ⏱️ Ns'
footer, leaving a per-turn receipt in the Slack transcript on the default
progress path. PR #122976 deleted the same receipt from the native progress
surface but left this sibling rendering it.

Finished cards now keep only the durable diff stat and the session link;
tool-call and elapsed counters stay live working state. Also corrects the
documented channels.slack.streaming.mode default, stale since #122552.
2026-08-16 14:58:23 -07:00
Peter Steinberger eab2b8fdca refactor(slack): mark approval headers with typed block ids (#124841) 2026-08-16 14:53:26 -07:00
Peter Steinberger 568b920b21 feat(lint): enforce import ordering and deduplication (#124730)
* refactor(imports): dedupe and hoist imports

* feat(lint): enforce import/no-duplicates and import/first
2026-08-16 11:44:52 -07:00
Peter Steinberger 63401b730b fix: keep doctor security conditions as single findings (#124666)
* fix(doctor): record security finding severity

* refactor(security): keep audit severity internal

* fix(security): preserve channel finding severity
2026-08-16 10:24:40 -07:00
Peter Steinberger a118bfbf30 fix(slack): stop sniffing the NO_REPLY product token in the transport (#124561)
sendMessageSlack special-cased core's silent-reply token before any API
call, returning a fabricated 'suppressed' messageId. Silent-reply
stripping is owned by core auto-reply normalization before payloads
reach outbound — no sibling channel transport has this check, so a
literal NO_REPLY sent through the message tool delivered everywhere
except Slack. The check predates the extension extraction (it moved
verbatim in 8746362f5e) and is duplicate policy.

Delete the check, its sentinel mint (the only one in the repo), and the
receipt filter for it; tests now pin sibling-parity delivery instead of
the suppression.
2026-08-16 05:56:41 -07:00
Peter Steinberger f43544f752 refactor: remove vestigial runtime indirection (#124554)
* refactor: remove vestigial indirection

* test: update Slack runtime API guard
2026-08-16 05:31:36 -07:00
Peter Steinberger 78ca5fcfb6 perf(test): fast-forward Slack probe retry window (#124428) 2026-08-15 22:18:20 -07:00
Peter Steinberger e4aaef4660 perf(test): shorten Slack startup auth regression (#124404) 2026-08-15 21:16:31 -07:00
Sarah Fortune dbc2356abc feat(slack): make presence event guidance configurable (#123875)
* feat(slack): make presence event guidance configurable

* chore(config): regenerate channel metadata

* fix(slack): keep presence prompt SDK compatible

* chore(config): refresh config baseline

* fix(slack): cap presence event prompts

* chore(config): refresh Slack prompt metadata

* fix(slack): bound queued presence wake prompts

* fix(slack): inherit presence prompt for named accounts

* fix(slack): keep prompt limit at config boundary

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-15 12:02:19 -07:00
Peter Steinberger c0824d284e test(extensions): remove type-only probe contracts (#124190) 2026-08-15 08:02:07 -07:00
Ilya Kuprov f21d34d71d fix(slack): upload-file silently drops a caption (#121047)
The Slack `upload-file` action accepts `media` as an alias for `filePath`, so
a caller can reach it with the same vocabulary used for a media `send`. That
vocabulary carries its accompanying text in `caption`, but the upload path
read only `initialComment` and `message`, so the text was dropped and the file
arrived in the channel with no comment at all.

Accept `caption` as the lowest-precedence alias. Explicit `initialComment`
still wins, `message` still comes next, and an explicitly empty
higher-precedence value stays empty.
2026-08-15 10:58:55 +03:00
Sarah Fortune f2e5d2054d feat(slack): include observed away duration in presence events (#123805)
* feat(slack): include observed away duration in presence events

* style(slack): format presence observation type

* test(slack): type presence enqueue mock

* fix(slack): preserve presence event prompting

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-14 22:46:00 +00:00
Peter Steinberger 2bc1699a37 fix(slack): preserve structured payload dispatch custody (#123710) 2026-08-14 09:01:05 -07:00
Dallin Romney 83d53044a4 fix(channels): route commentary through one progress owner (#121009)
* fix(slack): keep commentary progress on draft lane

* fix(channels): route commentary through one progress owner

Keep non-verbose commentary in Slack and Discord draft lanes while preserving one durable commentary payload when verbose progress makes those drafts yield. Freeze that owner decision for the turn so session changes apply on the next turn.

Co-authored-by: Dallin Romney <dallinromney@gmail.com>

Punchcard-Session: amber-workshop-workshop-36

* fix(channels): refresh queued commentary owner

Recompute the frozen draft-versus-durable commentary owner for every queued follow-up turn and carry that decision into final payload projection.

Punchcard-Session: amber-workshop-workshop-36

* fix(channels): clarify commentary owner opt-in

* fix(channels): preserve queued draft preambles

* fix(channels): require explicit queued progress ownership

* refactor(channels): record commentary progress owner

* fix(telegram): tolerate unscoped queued updates

* test(discord): use canonical draft fixture after rebase

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-14 22:15:53 +08:00
Josh Avant 97a53a9b35 feat: audit admitted channel participant identity (#122863)
* feat: audit admitted channel participant identity

* fix: preserve Telegram identity through thread recovery

* fix: signal held gateway process groups

* fix: keep audit evidence passive in collect routing

* fix: validate copied channel participant evidence

* fix: bind channel participant evidence to host ingress

* fix: honor Telegram proof credential roles

* fix: restart held Telegram proof through gateway

* fix: repair channel identity CI regressions

* test(matrix): bind thread routing owner

* fix: preserve direct DM SDK compatibility

* fix: bind channel provenance at host runtime

* test(feishu): provide channel context builder

* fix: defer record-bound channel runtime resolution

* fix: keep channel admission evidence core-private

* fix(audit): bind channel admission to plugin lifecycle

* fix(audit): bind ingress provenance to final context

* refactor(audit): split admission scope keys

* test(queue): cover combined metadata carriers

* refactor(audit): keep lifecycle helpers private

* fix(queue): preserve combined turn authority

* test(channels): provide ingress context builders

* test(channels): align integrated CI fixtures

* test(clickclack): resolve model-loop ingress

* docs: preserve channel participant evidence invariant
2026-08-14 08:57:01 -05:00
Kimi Yu d3ff5583d3 fix(slack): keep cached monitor identity and allowlists live (#123403)
* fix(slack): keep cached monitor context state live

* test(slack): type cached monitor context mock
2026-08-13 19:15:38 -07:00
Kimi Yu 358eecf8cb fix(slack): apply updated global settings to new messages (#123373) 2026-08-13 17:55:26 -07:00
Peter Steinberger 902eddf16b fix: keep global channel events agent-scoped (#123316)
Slack and Discord routed system events now retain route ownership when they enter literal-global queues, so sibling agents cannot consume them. Genuinely unbound routing remains an explicit error instead of falling back to a default agent.
2026-08-13 14:45:19 -07:00
Peter Steinberger faa6202412 fix(slack): keep thread freshness on routed agent (#123202) 2026-08-13 13:17:36 -07:00
Peter Steinberger 11b3bf374c feat(slack): unify the native progress turn into one streamed message (#122976)
* feat(slack): unify the native progress turn into one streamed message

Native progress mode now streams the whole turn into a single Slack message:
narration flows as markdown_text chunks interleaved with plan/task chunks,
task rows carry file-path details and +N/-N output, the terminal task links
the session via url_source, and the final answer lands through stopStream in
the same message. Media/oversized/error finals keep their normal-delivery
fallbacks.

Deletes the finished-card receipt collapse (the '\u{1F6E0} N tool calls · ⏱ Ns' edit)
outright: the card now stays as its finished self.

Live-verified on a real workspace: exactly one bot message per turn.

* fix(slack): serialize native stream updates and keep append-only rendered text monotonic

Overlapping progress updates (compositor render, narration payload, final)
computed their narration delta before awaiting the network and committed
state only afterwards, so concurrent updates re-appended identical
narration into the streamed message (each status line landed 3x live).
A single ordering chain now makes each update's compute -> append -> commit
atomic.

applyAppendOnlyStreamUpdate also replaced the accumulated rendered text
with the incoming cumulative partial once an appended chunk had diverged
rendered from source, dropping content the sink already displayed; rendered
now only ever extends.

* fix(slack): stop refreshing thread status once a turn has visible output

Slack clears the assistant thread status as soon as the app puts anything
in the thread, and renders its own rotating agent-working row ("Generating
response...", "Finding answers...") for every status write after that -- it
ignores the app-supplied string. The typing keepalive re-set the status
every 3s for up to 60s, so each turn painted a duplicate status row under
the streamed card or progress message.

The status write is now gated on the turn having visible output, which the
dispatcher already tracks (delivered reply, committed preview, or posted
draft message). The first status still fires before any output, so slow
turns keep their indicator, and the typing reaction is tracked separately
so a suppressed status write still cleans up its reaction.

* fix(slack): let the plan card own the status line instead of echoing it

The status headline and plan explanation fed both the streamed narration
markdown and the plan card title, so every headline rendered twice: once as
static text and once in the card that keeps updating it in place. Narration
now carries only authored commentary and reasoning, and a preamble payload
whose text the card title already shows is not streamed again.

* feat(slack): make the native agent card the default progress surface

Slack's native plan/task card was opt-in behind streaming.progress
.nativeTaskCards while the Block Kit session card shipped as the default.
The native surface is the better product on every axis we can measure --
one streamed message instead of three artifacts, live task rows with file
paths and diff counts, and Slack's own agent chrome -- so it becomes the
default and an explicit false selects the Block Kit card instead.

The session link is now emitted only when it can actually work: the
operator set gateway.publicOrigin and left the Control UI enabled.
Installations with no externally reachable Gateway get no link rather than
a dead one.

The progress card still only appears for turns that do real work; the
existing compositor start gate keeps plain question-and-answer turns
card-free.

* fix(slack): finish the final inside a buffered native stream

A short narration leaves the SDK session un-flushed, so `delivered` stays
false until `stop` makes its first network call. Requiring delivery before
finishing in-stream sent the final through normal delivery and then
finalized the stream anyway, producing exactly the second message this path
exists to prevent. Stop-time rejection already falls back via
SlackStreamNotDeliveredError, so a live session is enough.

Addresses the ClawSweeper P1/P2 finding on this PR.

* refactor(slack): collapse duplicate streaming surfaces and drop dead code

Cleanup pass over the progress/streaming neighborhood, all verified unused
by exhaustive reference search:

- Deleted buildSlackProgressStreamStartChunks/UpdateChunks: byte-identical
  pass-throughs to the same builder, plus the render-module branch that
  chose between them. One exported builder now.
- Collapsed slackStreaming.draftMode, a lossless restatement of the mode it
  was derived from, and its outbound mapper; nine comparisons now read the
  mode directly. Inbound legacy parsing stays for doctor migration.
- Dropped stopSlackStream's text parameter, the draft stream's stop() member
  and onMessageSent hook, a redundant nativeStreaming argument, four dead
  members on the progress runtime, and two single-expression wrappers.
- Deduped the native card title, which was computed twice per render.

Production LOC for the whole PR drops from +216 to +114.

* chore(config): regenerate bundled channel metadata for the Slack card default

The generated metadata still carried the old opt-in help text and
default-false description for streaming.progress.nativeTaskCards, so
config UI and diagnostics would publish stale guidance.

* fix(slack): un-export the now-internal legacy draft-mode type

Collapsing draftMode removed the type's only external consumer, so knip
flagged it as an unused export. Doctor migration still parses these legacy
values inbound, so the type stays module-local.
2026-08-13 11:19:32 -07:00
Sarah Fortune 3f731db90c fix(slack): accept org-wide user IDs in Enterprise Grid policies (#122934)
* test(slack): accept org-wide enterprise users

* fix(slack): allow org-wide enterprise users

* style(slack): format enterprise user policy

* test(slack): preserve enterprise user identity scope

* fix(slack): preserve enterprise user identity scope

* fix(slack): import workspace identity normalizer

* fix(slack): normalize enterprise owner ids

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-12 21:01:05 -07:00
Peter Steinberger 4d54c3f1a1 refactor(config): retire dead streaming.progress.render key (#122927)
* refactor(config): retire dead streaming.progress.render key

The key had zero runtime consumers after #122552. Core doctor now strips it via stripRetiredTuningKnobs, and production LOC is net -13.

* fix(tooling): pin plugin SDK surface counts to the reduced export set

The retired progress-draft render reader counted twice via channel-outbound and channel-message's wildcard re-export.
2026-08-12 20:36:40 -07:00
Peter Steinberger 08f9c3a5cb test(extensions): remove duplicate runtime replays (#122949) 2026-08-12 19:58:04 -07:00
Vincent Koc dabf55727b fix(ci): prevent channel add command test timeout (#122879)
* test(channels): isolate add env setup contracts

* test(channels): cover adapter env setup
2026-08-12 19:24:31 -07:00
Calin Laurentiu Ilie 95bbd117ef fix(slack): prevent duplicate Socket Mode connections after reconnect errors (#122624)
* fix(codex): read canonical transcript session targets (#1)

* test(slack): reproduce reconnect timer surviving shutdown

* fix(slack): keep reconnects within one socket lifecycle

* test(slack): exercise native reconnect over loopback

* test(slack): satisfy reconnect integration checks
2026-08-12 18:57:04 -07:00