Skip to content

fix(discord): mirror Components v2 outbound messages to session transcript#53607

Closed
lupuletic wants to merge 3 commits into
openclaw:mainfrom
lupuletic:fix-discord-components-v2-transcript-mirror-21649-21649
Closed

fix(discord): mirror Components v2 outbound messages to session transcript#53607
lupuletic wants to merge 3 commits into
openclaw:mainfrom
lupuletic:fix-discord-components-v2-transcript-mirror-21649-21649

Conversation

@lupuletic

@lupuletic lupuletic commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

sendDiscordComponentMessage sends components via Discord REST API and registers component entries but never appends the outbound message to the session transcript, so the agent loses context when a user clicks a button.

Closes #21649

Changes:

  • Export appendAssistantMessageToSessionTranscript through src/plugin-sdk/outbound-runtime.ts
  • In extensions/discord/src/send.components.ts, import and call appendAssistantMessageToSessionTranscript after registerBuiltDiscordComponentMessage when sessionKey is present
  • Build transcript text from spec.text + component labels (buttons, selects, section accessory buttons, modal trigger buttons) for full agent context
  • Discord outbound adapter attaches meta.transcriptText to component delivery results so the core deliver pipeline mirrors richer component context
  • Core deliver pipeline checks delivery results for meta.transcriptText and prefers it over plain text for transcript mirroring
  • Add unit tests verifying transcript mirroring is called with correct params
  • Add e2e mirror test through deliverOutboundPayloads for component payloads
  • Add regression test for no double-write in outbound-send-service

Testing:

  • pnpm build && pnpm check && pnpm test (all passed)
  • Unit tests mock appendAssistantMessageToSessionTranscript verifying it is called with correct params
  • Integration test for no double-write in outbound-send-service
  • E2e test validates full pipeline integration of meta.transcriptText

AI-assisted (Claude + Codex committee consensus, fully tested).

AI-Assisted PR Checklist

  • Marked as AI-assisted
  • Testing degree: fully tested (pnpm build + check + test gates passed)
  • Code reviewed by LLM committee (Claude Opus + Codex dual-model review with consensus gate)
  • I understand what the code does
  • Bot review conversations addressed and resolved

@aisle-research-bot

aisle-research-bot Bot commented Mar 24, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded transcript aggregation via meta.transcriptText can cause resource exhaustion
2 🟡 Medium Prompt/transcript injection via untrusted meta.transcriptText mirrored into session transcript
Vulnerabilities

1. 🟡 Unbounded transcript aggregation via meta.transcriptText can cause resource exhaustion

Property Value
Severity Medium
CWE CWE-400
Location src/infra/outbound/deliver.ts:785-803

Description

The new Discord components transcript summarization and mirroring logic can generate and persist arbitrarily large transcript entries.

  • buildComponentTranscriptText() builds a transcript string by concatenating all component message text plus every block text, button label, and select option label.
  • deliverOutboundPayloadsCore() then concatenates meta.transcriptText across all outbound results and stores it into the session transcript via appendAssistantMessageToSessionTranscript().
  • No maximum size/character limits, truncation, or bounding of blocks/options/labels is applied before persisting.

If any part of a component spec (e.g., labels/options/text) is influenced by untrusted input (directly or indirectly through tools/plugins or user-controlled content), an attacker could cause very large transcript writes leading to:

  • Disk/DB bloat (session transcript files grow without bound)
  • Increased CPU/memory usage and latency while joining strings and writing files
  • Increased downstream LLM context/token usage/cost when transcripts are used as context

Vulnerable code (aggregation and join):

// extensions/discord/src/send.components.ts
for (const block of spec.blocks ?? []) {
  ...
  if (block.buttons?.length) {
    parts.push(block.buttons.map((b) => `[${b.label}]`).join(" "));
  }
  if (block.select) {
    const opts = block.select.options;
    if (opts?.length) {
      parts.push(opts.map((o) => `[${o.label}]`).join(" "));
    }
  }
}
return parts.join("\n");

and mirroring persistence:

// src/infra/outbound/deliver.ts
const adapterParts = results
  .map((r) => (typeof r.meta?.transcriptText === "string" ? r.meta.transcriptText : ""))
  .filter(Boolean);
...
const mirrorText = resolveMirroredTranscriptText({ text: resolvedText, mediaUrls: ... });
await appendAssistantMessageToSessionTranscript({ ..., text: mirrorText, ... });

Recommendation

Add hard limits and truncation for mirrored transcript text before persisting.

Suggested approach:

  • Cap total mirrored transcript characters (and optionally per-result adapter text).
  • Cap the number of blocks/buttons/options included in buildComponentTranscriptText().
  • Consider stripping/normalizing control characters and limiting line count.

Example (centralized truncation before persisting):

const MAX_TRANSCRIPT_CHARS = 4000;

function truncateTranscript(text: string): string {
  const trimmed = text.trim();
  if (trimmed.length <= MAX_TRANSCRIPT_CHARS) return trimmed;
  return trimmed.slice(0, MAX_TRANSCRIPT_CHARS - 1) + "…";
}

const resolvedText = /* existing logic */;
const mirrorText = resolveMirroredTranscriptText({ text: truncateTranscript(resolvedText), mediaUrls });

And (optional) bound component summaries:

const MAX_OPTIONS = 25;
const MAX_BLOCKS = 50;
for (const block of (spec.blocks ?? []).slice(0, MAX_BLOCKS)) {
  ...
  const opts = (block.select.options ?? []).slice(0, MAX_OPTIONS);
}

2. 🟡 Prompt/transcript injection via untrusted meta.transcriptText mirrored into session transcript

Property Value
Severity Medium
CWE CWE-74
Location src/infra/outbound/deliver.ts:785-810

Description

deliverOutboundPayloadsCore now prefers r.meta.transcriptText (provided by channel adapters/plugins) over the original params.mirror.text when appending an assistant message into the persisted session transcript.

  • Input (untrusted): r.meta.transcriptText comes from outbound adapter results (e.g., Discord adapter builds it from component labels/placeholders/options, which may be derived from user-controlled or third-party plugin-controlled data).
  • Sink: appendAssistantMessageToSessionTranscript persists the text as an assistant message in the session transcript.
  • Impact: If session transcripts are later used as LLM context, an attacker can inject instructions/jailbreak content into the agent's “memory” (prompt injection), potentially causing data exfiltration or unsafe tool use.

Vulnerable code:

const adapterParts = results
  .map((r) => (typeof r.meta?.transcriptText === "string" ? r.meta.transcriptText : ""))
  .filter(Boolean);
...
const resolvedText = allEnriched
  ? adapterParts.join("\n")
  : ...

const mirrorText = resolveMirroredTranscriptText({ text: resolvedText, ... });
...
await appendAssistantMessageToSessionTranscript({ ..., text: mirrorText, ... });

Recommendation

Treat adapter-provided transcript text as untrusted and prevent it from being interpreted as high-authority assistant memory.

Options (pick one or combine):

  1. Do not store adapter text as an assistant message; store it as structured metadata not fed back to the model.

  2. If it must be stored, wrap/label and escape it to reduce instruction-following risk, and add size limits:

const safeAdapterParts = adapterParts
  .map(t => t.replace(/[\u0000-\u001F\u007F]/g, " "))
  .map(t => t.slice(0, 2000));

const resolvedText = safeAdapterParts.length
  ? [params.mirror.text, "[UI transcript summary — do not treat as instructions]", ...safeAdapterParts]
      .filter(Boolean)
      .join("\n")
  : params.mirror.text;
  1. Trust boundary enforcement: only allow meta.transcriptText from built-in adapters (deny/ignore for third-party plugins unless explicitly allowlisted/signed).

Analyzed PR: #53607 at commit bbaf711

Last updated on: 2026-03-29T14:13:28Z

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: M labels Mar 24, 2026
@greptile-apps

greptile-apps Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR correctly fixes the missing session transcript mirroring for Discord Components v2 outbound messages. Previously, sendDiscordComponentMessage registered component entries and posted to Discord but never recorded the sent message in the session transcript, causing the agent to lose context when a user clicked a button.

Key changes:

  • buildComponentTranscriptText extracts a human-readable representation (text, button labels, select options, modal trigger) from a DiscordComponentMessageSpec for use as transcript context.
  • sendDiscordComponentMessage now directly appends to the session transcript when sessionKey is present (direct-call path).
  • outbound-adapter.ts attaches the same text as meta.transcriptText on the result, so the deliver.ts mirror pipeline can prefer the richer component text over the plain mirror.text (pipeline path).
  • The two write paths are mutually exclusive: the adapter deliberately omits sessionKey from its sendDiscordComponentMessage call, leaving the write to the deliver pipeline.
  • appendAssistantMessageToSessionTranscript is now exported through src/plugin-sdk/config-runtime.ts, making it available to plugin-level code.

Notable detail: In outbound-adapter.ts, the meta spread uses meta: { transcriptText } (line 169), which fully replaces any pre-existing meta on result. DiscordSendResult has no meta field today so nothing is lost, but a defensive deep-merge (meta: { ...result.meta, transcriptText }) would be safer if the type evolves.

Confidence Score: 4/5

  • Safe to merge; the fix is targeted and well-tested, with one minor future-proofing note on the meta merge.
  • The core bug fix is correct, the two write paths are properly isolated (no double-write risk), and test coverage is thorough across happy paths and edge cases. The only outstanding item is a shallow meta merge in outbound-adapter.ts that poses no current risk but could silently drop fields if DiscordSendResult gains additional meta in the future.
  • extensions/discord/src/outbound-adapter.ts — shallow meta merge on line 169 (minor, no current impact).
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/discord/src/outbound-adapter.ts
Line: 167-170

Comment:
**Shallow meta merge drops existing fields**

`meta: { transcriptText }` fully replaces any `meta` already present on `result`. `DiscordSendResult` currently has no `meta`, so nothing is lost today, but if the return type ever gains `meta` fields (e.g., a rate-limit hint, a message-reference id), this spread would silently discard them.

A defensive merge costs nothing and future-proofs the code:

```suggestion
    return attachChannelToResult("discord", {
      ...result,
      ...(transcriptText ? { meta: { ...result.meta, transcriptText } } : {}),
    });
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(discord): address review feedback — ..." | Re-trigger Greptile

Comment thread extensions/discord/src/outbound-adapter.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0174349b9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/send.components.ts Outdated
Comment thread src/infra/outbound/deliver.ts
@lupuletic lupuletic force-pushed the fix-discord-components-v2-transcript-mirror-21649-21649 branch from d017434 to 10ad0d7 Compare March 24, 2026 10:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10ad0d72ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/send.components.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 145cfae599

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/send.components.ts Outdated
@lupuletic lupuletic force-pushed the fix-discord-components-v2-transcript-mirror-21649-21649 branch from 145cfae to 78bc0d7 Compare March 28, 2026 07:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78bc0d779e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +83 to +85
if (spec.modal?.triggerLabel?.trim()) {
parts.push(`[${spec.modal.triggerLabel.trim()}]`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include default modal trigger text in transcript mirror

When a component message defines modal without triggerLabel, buildDiscordComponentMessage still renders a button labeled Open form, but this transcript helper skips adding any modal label because it only reads spec.modal.triggerLabel. In those common default-modal cases, the mirrored assistant transcript omits the actionable control the user sees, so follow-up agent reasoning loses context about which interaction was offered.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — buildComponentTranscriptText now falls back to "Open form" when spec.modal exists without triggerLabel, matching the button label rendered by buildDiscordComponentMessage.

@lupuletic lupuletic force-pushed the fix-discord-components-v2-transcript-mirror-21649-21649 branch from 78bc0d7 to bbaf711 Compare March 29, 2026 14:10

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bbaf7113ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 800 to 803
const mirrorText = resolveMirroredTranscriptText({
text: params.mirror.text,
text: resolvedText,
mediaUrls: params.mirror.mediaUrls,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve adapter transcript text when media URLs are present

When an adapter provides meta.transcriptText (for example Discord component labels) and the outbound payload also has mirror.mediaUrls, this call path still passes mediaUrls into resolveMirroredTranscriptText, which always prefers media filenames over text. In that common "components + attachment" case, the transcript entry becomes only the filename and drops the interactive context that this change is trying to preserve, so later turns can no longer see which buttons/selects were offered.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — when adapter provides transcriptText (component labels), it now takes priority over media filename resolution. Media URLs are appended as supplementary context, not as replacements for the interactive component text.

@lupuletic lupuletic force-pushed the fix-discord-components-v2-transcript-mirror-21649-21649 branch from bbaf711 to bd220ff Compare March 30, 2026 06:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd220ff220

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config/sessions/transcript.ts Outdated

// When adapter transcript text is present (e.g. component labels), use it as
// the primary text and append media filenames as supplementary context.
if (trimmed && mediaUrls.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict media-text transcript merge to enriched adapter paths

This new if (trimmed && mediaUrls.length > 0) branch runs for every captioned media send, not just adapter-enriched component sends, so ordinary media deliveries now mirror caption\nfilename instead of the prior filename-only text. That broad behavior change increases transcript noise/token usage and can duplicate assistant content in session context for all channels that send media with captions. The merge should be gated by an explicit “adapter-enriched text” signal (or equivalent caller-side branching) so non-enriched media mirrors keep existing semantics.

Useful? React with 👍 / 👎.

const resolvedText = allEnriched
? adapterParts.join("\n")
: adapterParts.length > 0
? [params.mirror.text, ...adapterParts].filter(Boolean).join("\n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid duplicating base text in mixed enriched mirror batches

In mixed result batches, prepending params.mirror.text to adapterParts duplicates content when enriched adapter transcript text already includes the same base message (for example, component transcript Pick a color\n[Red]... plus mirror text Pick a color). This produces repeated lines in a single mirrored assistant entry and can skew downstream reasoning over session context; dedupe/merge should avoid re-adding a prefix already present in adapter text.

Useful? React with 👍 / 👎.

…cript

Add buildComponentTranscriptText() to produce a plain-text summary of
Discord component messages (buttons, selects, sections, modals) for the
session transcript. The adapter attaches it as meta.transcriptText so the
existing mirror infrastructure in deliver.ts writes a single enriched
transcript entry instead of the plain fallback text.

Key design decisions:
- No parallel transcript write path: component text flows through
  meta.transcriptText on the adapter result, picked up by the existing
  deliverOutboundPayloadsCore mirror block. This avoids the duplicate
  transcript write that would occur if sendDiscordComponentMessage wrote
  independently of executeSendAction's onHandled callback.
- appendAssistantMessageToSessionTranscript is not exported to the plugin
  SDK to keep the transcript API as an internal surface.
- Mixed batch support: when some delivery results carry enriched adapter
  text and others do not, both the mirror text and adapter text are
  concatenated so neither portion is lost.

Closes openclaw#21649
@lupuletic lupuletic force-pushed the fix-discord-components-v2-transcript-mirror-21649-21649 branch from bd220ff to 36bce87 Compare April 5, 2026 13:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36bce87236

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +44 to +49
if (trimmed && mediaUrls.length > 0) {
const names = mediaUrls
.map((url) => extractFileNameFromMediaUrl(url))
.filter((name): name is string => Boolean(name && name.trim()));
const mediaSuffix = names.length > 0 ? names.join(", ") : "media";
return `${trimmed}\n${mediaSuffix}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate text+media transcript merge to enriched adapter sends

The new if (trimmed && mediaUrls.length > 0) branch changes transcript behavior for every captioned media send, not just component-enriched sends: any normal outbound with both caption text and attachments now mirrors as caption\nfilename instead of the previous filename-only entry. That broad regression increases transcript noise/token usage and can duplicate assistant content in session history for channels that never provide meta.transcriptText; this merge should be conditioned on an explicit enriched-text signal from the caller rather than raw text presence.

Useful? React with 👍 / 👎.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 29, 2026
@clawsweeper

clawsweeper Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep open: current main still lacks the Discord Components v2 enriched transcript mirror, but this PR is not merge-ready because the branch conflicts, lacks real Discord proof, and makes generic transcript-metadata and media-mirroring changes that need a safer contract.

Canonical path: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

So I’m closing this here because the remaining work is already tracked in the canonical issue.

Review details

Best possible solution:

Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level. Current main sends and registers Discord components while the shared mirror path persists only the plain mirror text/media names, and the linked bug gives a concrete send-components-then-click reproduction; I did not run live Discord in this read-only review.

Is this the best way to solve the issue?

No. The PR targets the right bug, but the maintainable fix should avoid generic adapter metadata as assistant memory and should not change ordinary media mirror semantics across channels.

Security review:

Security review needs attention: The diff needs security attention because adapter result metadata would become persisted assistant transcript text without a clear trust, size, or labeling contract.

  • [medium] Adapter metadata becomes assistant memory — src/infra/outbound/deliver.ts:807
    meta.transcriptText may contain adapter/plugin-derived text but is persisted as an assistant transcript entry that can later influence model context, so the trust boundary and labeling need to be explicit.
    Confidence: 0.86
  • [medium] Component transcript aggregation is unbounded — extensions/discord/src/send.components.ts:141
    The helper concatenates component text, labels, and option labels without a clear limit before persistence, which can inflate transcript size and downstream context cost.
    Confidence: 0.78

What I checked:

  • stale F-rated PR: PR was opened 2026-03-24T10:06:09Z, is older than 60 days, and the latest review rated it F.
  • proof blocker: real behavior proof is missing and proof tier is F, so this branch is not merge-ready without contributor follow-up.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • steipete: GitHub path history shows repeated recent work across Discord component sending, outbound payload helpers, plugin SDK boundaries, and shared delivery/session paths relevant to this PR. (role: recent area contributor; confidence: high; commits: 41810a462e3c, 827b0de0ce74, 05eda57b3c72; files: extensions/discord/src/send.components.ts, extensions/discord/src/outbound-payload.ts, src/infra/outbound/deliver.ts)
  • shakkernerd: Path history identifies shakkernerd on the lazy outbound transcript mirroring helper that now governs assistant transcript text resolution. (role: adjacent transcript mirror contributor; confidence: medium; commits: 4a81771290a8; files: src/config/sessions/transcript-mirror.ts)
  • vincentkoc: Recent history includes outbound delivery lifecycle and routing metadata work near the delivery/session surface this PR changes. (role: adjacent outbound/session contributor; confidence: medium; commits: bd32b1a906f3, e593122465e7, 6d9b3887ea26; files: src/infra/outbound/deliver.ts, extensions/discord/src/send.components.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 6d9b3887ea26.

@clawsweeper

clawsweeper Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge.

What this changes:

The PR adds Discord component transcript summarization, returns that summary as meta.transcriptText from the Discord outbound adapter, changes shared transcript mirroring to prefer enriched adapter text, and adds Discord/outbound regression coverage.

Maintainer follow-up before merge:

The PR targets a real bug, but the remaining action is maintainer review plus author changes on an existing implementation PR: fix the shared media transcript regression, update the branch against current main, and resolve the transcript metadata trust/size boundary before merge.

Review findings:

  • [P2] Gate media transcript merging to enriched sends — src/config/sessions/transcript-mirror.ts:44
Review details

Best possible solution:

Land a narrow enriched-transcript implementation that mirrors Discord component text and interactive labels through one idempotent write path, gates text-plus-media transcript merging to explicit enriched adapter summaries, preserves ordinary captioned-media mirror semantics, and bounds or labels UI-derived transcript text before it becomes assistant session memory.

Full review comments:

  • [P2] Gate media transcript merging to enriched sends — src/config/sessions/transcript-mirror.ts:44
    The new text+media branch in resolveMirroredTranscriptText runs for every caller, not only Discord component results. Current main's delivery mirror coverage expects a captioned media send to persist the filename (report.pdf) rather than caption\nreport.pdf; this patch would change ordinary captioned media transcripts across channels and duplicate assistant text. Keep the old media-only behavior unless the caller explicitly passes enriched adapter transcript text.
    Confidence: 0.92

Overall correctness: patch is incorrect
Overall confidence: 0.88

Acceptance criteria:

  • pnpm test extensions/discord/src/send.components.test.ts extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/outbound-adapter.interactive-order.test.ts
  • pnpm test src/infra/outbound/deliver.test.ts src/infra/outbound/outbound-send-service.test.ts src/config/sessions/transcript.test.ts
  • pnpm check:changed in Testbox before merge because shared outbound/transcript behavior is touched

What I checked:

  • current_main_component_sender_registers_only: Current main sends the Discord component payload, registers built component entries, records outbound activity, and returns message/channel ids, but it does not append an assistant transcript entry or return enriched transcript metadata from this sender. (extensions/discord/src/send.components.ts:315, e46dccb35374)
  • current_main_component_payload_returns_plain_result: Current main's component payload adapter calls sendDiscordComponentMessageLazy and returns attachChannelToResult("discord", result) without meta.transcriptText or another component summary. (extensions/discord/src/outbound-payload.ts:84, e46dccb35374)
  • current_main_mirror_ignores_delivery_meta: Current shared delivery mirroring resolves transcript text only from params.mirror.text and params.mirror.mediaUrls; it does not inspect delivery result metadata. (src/infra/outbound/deliver.ts:1291, e46dccb35374)
  • current_media_mirror_contract_conflicts_with_pr: Current main has regression coverage expecting captioned media delivery to mirror only the media filename (report.pdf). The PR's new resolveMirroredTranscriptText branch would change that path to include the caption plus filename for ordinary media sends. (src/infra/outbound/deliver.test.ts:1732, e46dccb35374)
  • docs_confirm_user_surface: Discord docs say Components v2 interaction results route back as normal inbound messages, which matches the reported context gap when the original outbound component prompt is absent from the target session transcript. Public docs: docs/channels/discord.md. (docs/channels/discord.md:309, e46dccb35374)
  • pr_diff_media_helper_regression: The provided PR diff adds a trimmed && mediaUrls.length > 0 branch in resolveMirroredTranscriptText without an enriched-adapter signal, so the changed helper applies to every caller with both text and media URLs. (src/config/sessions/transcript-mirror.ts:44, 36bce8723693)

Likely related people:

  • Peter Steinberger: Local blame and recent current-main history put Peter on the Discord component sender, outbound payload adapter, shared delivery mirror, transcript mirror helper, Discord docs, and recent Discord component refactors. (role: recent maintainer and current-main line owner; confidence: high; commits: 34d11d57579d, 587b537b4745, e8b82d1cf92f; files: extensions/discord/src/send.components.ts, extensions/discord/src/outbound-payload.ts, src/infra/outbound/deliver.ts)
  • vincentkoc: The active changelog credits vincentkoc for tracked Discord component-message helper compatibility and many adjacent plugin SDK/channel fixes, making this a likely routing candidate for SDK and outbound contract review. (role: adjacent owner for plugin/channel transcript surfaces; confidence: medium; commits: be8c24633aaa; files: CHANGELOG.md, src/plugin-sdk/discord.ts, extensions/discord/src/components.ts)
  • thewilloftheshadow: The changelog credits thewilloftheshadow for the Discord Components v2 feature surface and related component controls, and the PR timeline shows this handle was explicitly mentioned/subscribed for routing. (role: introduced behavior area and Discord component domain reviewer; confidence: medium; commits: be8c24633aaa; files: CHANGELOG.md, docs/channels/discord.md, extensions/discord/src/components.ts)

Remaining risk / open question:

  • The PR is marked unmergeable in the provided context and current main now has captioned-media transcript coverage that conflicts with the PR's shared helper change, so the branch needs an update before validation.
  • The intended trust contract for adapter-provided meta.transcriptText is unresolved: it crosses a generic channel metadata bag into assistant transcript memory and should be bounded/labeled or moved to an explicit internal contract before merge.

Codex review notes: model gpt-5.5, reasoning high; reviewed against e46dccb35374.

@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 19, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Discord Components v2: outbound component messages not mirrored to target session transcript

1 participant