Skip to content

Commit 6618073

Browse files
committed
fix: harden discord agent cid parsing (#29013) (thanks @Jacky1n7)
1 parent f7188e8 commit 6618073

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Docs: https://docs.openclaw.ai
111111

112112
### Fixes
113113

114+
- Discord/Agent component interactions: accept Components v2 `cid` payloads alongside legacy `componentId`, and safely decode percent-encoded IDs without throwing on malformed `%` sequences. Landed from contributor PR #29013 by @Jacky1n7. Thanks @Jacky1n7.
114115
- Discord/Inbound media fallback: preserve attachment and sticker metadata when Discord CDN fetch/save fails by keeping URL-based media entries in context, with regression coverage for save failures and mixed success/failure ordering. Landed from contributor PR #28906 by @Sid-Qin. Thanks @Sid-Qin.
115116
- Docs/Docker images: clarify the official GHCR image source and tag guidance (`main`, `latest`, `<version>`), and document that `OPENCLAW_IMAGE` skips local image builds but still uses the repo-local compose/setup flow. (#27214, #31180) Fixes #15655. Thanks @ipl31.
116117
- Agents/Model fallback: classify additional network transport errors (`ECONNREFUSED`, `ENETUNREACH`, `EHOSTUNREACH`, `ENETRESET`, `EAI_AGAIN`) as failover-worthy so fallback chains advance when primary providers are unreachable. Landed from contributor PR #19077 by @ayanesakura. Thanks @ayanesakura.

src/discord/monitor/agent-components.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -406,22 +406,21 @@ export function buildAgentSelectCustomId(componentId: string): string {
406406

407407
/**
408408
* Parse agent component data from Carbon's parsed ComponentData
409-
* Carbon parses "key:componentId=xxx" into { componentId: "xxx" }
409+
* Supports both legacy { componentId } and Components v2 { cid } payloads.
410410
*/
411-
function parseAgentComponentData(data: ComponentData): {
412-
componentId: string;
413-
} | null {
411+
function readParsedComponentId(data: ComponentData): unknown {
414412
if (!data || typeof data !== "object") {
415-
return null;
413+
return undefined;
416414
}
415+
return "cid" in data
416+
? (data as Record<string, unknown>).cid
417+
: (data as Record<string, unknown>).componentId;
418+
}
417419

418-
// Carbon parses "key:componentId=xxx" into { componentId: "xxx" }
419-
// Components v2 / other builders may use { cid: "xxx" } (e.g. occomp:cid=xxx).
420-
const raw =
421-
("cid" in data
422-
? (data as Record<string, unknown>).cid
423-
: (data as Record<string, unknown>).componentId) ??
424-
(data as Record<string, unknown>).componentId;
420+
function parseAgentComponentData(data: ComponentData): {
421+
componentId: string;
422+
} | null {
423+
const raw = readParsedComponentId(data);
425424

426425
const decodeSafe = (value: string): string => {
427426
// `cid` values may be raw (not URI-encoded). Guard against malformed % sequences.
@@ -601,10 +600,7 @@ function parseDiscordComponentData(
601600
if (!data || typeof data !== "object") {
602601
return null;
603602
}
604-
const rawComponentId =
605-
"cid" in data
606-
? (data as { cid?: unknown }).cid
607-
: (data as { componentId?: unknown }).componentId;
603+
const rawComponentId = readParsedComponentId(data);
608604
const rawModalId =
609605
"mid" in data ? (data as { mid?: unknown }).mid : (data as { modalId?: unknown }).modalId;
610606
let componentId = normalizeComponentId(rawComponentId);

src/discord/monitor/monitor.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,44 @@ describe("agent components", () => {
182182
expect(reply).toHaveBeenCalledWith({ content: "✓" });
183183
expect(enqueueSystemEventMock).toHaveBeenCalled();
184184
});
185+
186+
it("accepts cid payloads for agent button interactions", async () => {
187+
const button = createAgentComponentButton({
188+
cfg: createCfg(),
189+
accountId: "default",
190+
dmPolicy: "allowlist",
191+
allowFrom: ["123456789"],
192+
});
193+
const { interaction, defer, reply } = createDmButtonInteraction();
194+
195+
await button.run(interaction, { cid: "hello_cid" } as ComponentData);
196+
197+
expect(defer).toHaveBeenCalledWith({ ephemeral: true });
198+
expect(reply).toHaveBeenCalledWith({ content: "✓" });
199+
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
200+
expect.stringContaining("hello_cid"),
201+
expect.any(Object),
202+
);
203+
});
204+
205+
it("keeps malformed percent cid values without throwing", async () => {
206+
const button = createAgentComponentButton({
207+
cfg: createCfg(),
208+
accountId: "default",
209+
dmPolicy: "allowlist",
210+
allowFrom: ["123456789"],
211+
});
212+
const { interaction, defer, reply } = createDmButtonInteraction();
213+
214+
await button.run(interaction, { cid: "hello%2G" } as ComponentData);
215+
216+
expect(defer).toHaveBeenCalledWith({ ephemeral: true });
217+
expect(reply).toHaveBeenCalledWith({ content: "✓" });
218+
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
219+
expect.stringContaining("hello%2G"),
220+
expect.any(Object),
221+
);
222+
});
185223
});
186224

187225
describe("discord component interactions", () => {

0 commit comments

Comments
 (0)