@open-everywhere/provider-discord 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +47 -0
- package/agent/access/discord-grant-click.ts +237 -0
- package/agent/approvals/discord-admin.ts +227 -0
- package/agent/approvals/discord-card.ts +248 -0
- package/agent/approvals/discord-flow.ts +469 -0
- package/agent/approvals/discord-scope-label.ts +27 -0
- package/agent/channels/discord-approval-card.ts +373 -0
- package/agent/channels/discord-backscroll.ts +593 -0
- package/agent/channels/discord-bot-name.ts +113 -0
- package/agent/channels/discord-channel-info.ts +83 -0
- package/agent/channels/discord-directory.ts +179 -0
- package/agent/channels/discord-dispatch-target.ts +103 -0
- package/agent/channels/discord-events.ts +175 -0
- package/agent/channels/discord-front.ts +355 -0
- package/agent/channels/discord-gateway.ts +3256 -0
- package/agent/channels/discord-identity.ts +269 -0
- package/agent/channels/discord-interaction-defer.ts +123 -0
- package/agent/channels/discord-message-ops.ts +127 -0
- package/agent/channels/discord-origin-codec.ts +105 -0
- package/agent/channels/discord-post-arm.ts +230 -0
- package/agent/channels/discord-post.ts +463 -0
- package/agent/channels/discord-read.ts +140 -0
- package/agent/channels/discord-rest.ts +85 -0
- package/agent/channels/discord.ts +244 -0
- package/agent/commands/discord-command-sync.ts +285 -0
- package/agent/commands/discord-commands.ts +291 -0
- package/agent/contributions.ts +31 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/identity/discord-link-consent.ts +124 -0
- package/agent/ops.ts +14 -0
- package/agent/provider-events.ts +86 -0
- package/agent/register.ts +147 -0
- package/agent/surface-parity.ts +95 -0
- package/dist/agent/access/discord-grant-click.d.ts +25 -0
- package/dist/agent/access/discord-grant-click.d.ts.map +1 -0
- package/dist/agent/access/discord-grant-click.js +210 -0
- package/dist/agent/access/discord-grant-click.js.map +1 -0
- package/dist/agent/approvals/discord-admin.d.ts +63 -0
- package/dist/agent/approvals/discord-admin.d.ts.map +1 -0
- package/dist/agent/approvals/discord-admin.js +219 -0
- package/dist/agent/approvals/discord-admin.js.map +1 -0
- package/dist/agent/approvals/discord-card.d.ts +102 -0
- package/dist/agent/approvals/discord-card.d.ts.map +1 -0
- package/dist/agent/approvals/discord-card.js +217 -0
- package/dist/agent/approvals/discord-card.js.map +1 -0
- package/dist/agent/approvals/discord-flow.d.ts +109 -0
- package/dist/agent/approvals/discord-flow.d.ts.map +1 -0
- package/dist/agent/approvals/discord-flow.js +375 -0
- package/dist/agent/approvals/discord-flow.js.map +1 -0
- package/dist/agent/approvals/discord-scope-label.d.ts +3 -0
- package/dist/agent/approvals/discord-scope-label.d.ts.map +1 -0
- package/dist/agent/approvals/discord-scope-label.js +27 -0
- package/dist/agent/approvals/discord-scope-label.js.map +1 -0
- package/dist/agent/channels/discord-approval-card.d.ts +81 -0
- package/dist/agent/channels/discord-approval-card.d.ts.map +1 -0
- package/dist/agent/channels/discord-approval-card.js +303 -0
- package/dist/agent/channels/discord-approval-card.js.map +1 -0
- package/dist/agent/channels/discord-backscroll.d.ts +209 -0
- package/dist/agent/channels/discord-backscroll.d.ts.map +1 -0
- package/dist/agent/channels/discord-backscroll.js +505 -0
- package/dist/agent/channels/discord-backscroll.js.map +1 -0
- package/dist/agent/channels/discord-bot-name.d.ts +19 -0
- package/dist/agent/channels/discord-bot-name.d.ts.map +1 -0
- package/dist/agent/channels/discord-bot-name.js +98 -0
- package/dist/agent/channels/discord-bot-name.js.map +1 -0
- package/dist/agent/channels/discord-channel-info.d.ts +22 -0
- package/dist/agent/channels/discord-channel-info.d.ts.map +1 -0
- package/dist/agent/channels/discord-channel-info.js +65 -0
- package/dist/agent/channels/discord-channel-info.js.map +1 -0
- package/dist/agent/channels/discord-directory.d.ts +11 -0
- package/dist/agent/channels/discord-directory.d.ts.map +1 -0
- package/dist/agent/channels/discord-directory.js +170 -0
- package/dist/agent/channels/discord-directory.js.map +1 -0
- package/dist/agent/channels/discord-dispatch-target.d.ts +15 -0
- package/dist/agent/channels/discord-dispatch-target.d.ts.map +1 -0
- package/dist/agent/channels/discord-dispatch-target.js +99 -0
- package/dist/agent/channels/discord-dispatch-target.js.map +1 -0
- package/dist/agent/channels/discord-events.d.ts +58 -0
- package/dist/agent/channels/discord-events.d.ts.map +1 -0
- package/dist/agent/channels/discord-events.js +150 -0
- package/dist/agent/channels/discord-events.js.map +1 -0
- package/dist/agent/channels/discord-front.d.ts +115 -0
- package/dist/agent/channels/discord-front.d.ts.map +1 -0
- package/dist/agent/channels/discord-front.js +279 -0
- package/dist/agent/channels/discord-front.js.map +1 -0
- package/dist/agent/channels/discord-gateway.d.ts +783 -0
- package/dist/agent/channels/discord-gateway.d.ts.map +1 -0
- package/dist/agent/channels/discord-gateway.js +2591 -0
- package/dist/agent/channels/discord-gateway.js.map +1 -0
- package/dist/agent/channels/discord-identity.d.ts +110 -0
- package/dist/agent/channels/discord-identity.d.ts.map +1 -0
- package/dist/agent/channels/discord-identity.js +201 -0
- package/dist/agent/channels/discord-identity.js.map +1 -0
- package/dist/agent/channels/discord-interaction-defer.d.ts +33 -0
- package/dist/agent/channels/discord-interaction-defer.d.ts.map +1 -0
- package/dist/agent/channels/discord-interaction-defer.js +102 -0
- package/dist/agent/channels/discord-interaction-defer.js.map +1 -0
- package/dist/agent/channels/discord-message-ops.d.ts +3 -0
- package/dist/agent/channels/discord-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/discord-message-ops.js +123 -0
- package/dist/agent/channels/discord-message-ops.js.map +1 -0
- package/dist/agent/channels/discord-origin-codec.d.ts +7 -0
- package/dist/agent/channels/discord-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/discord-origin-codec.js +98 -0
- package/dist/agent/channels/discord-origin-codec.js.map +1 -0
- package/dist/agent/channels/discord-post-arm.d.ts +3 -0
- package/dist/agent/channels/discord-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/discord-post-arm.js +195 -0
- package/dist/agent/channels/discord-post-arm.js.map +1 -0
- package/dist/agent/channels/discord-post.d.ts +120 -0
- package/dist/agent/channels/discord-post.d.ts.map +1 -0
- package/dist/agent/channels/discord-post.js +426 -0
- package/dist/agent/channels/discord-post.js.map +1 -0
- package/dist/agent/channels/discord-read.d.ts +40 -0
- package/dist/agent/channels/discord-read.d.ts.map +1 -0
- package/dist/agent/channels/discord-read.js +128 -0
- package/dist/agent/channels/discord-read.js.map +1 -0
- package/dist/agent/channels/discord-rest.d.ts +33 -0
- package/dist/agent/channels/discord-rest.d.ts.map +1 -0
- package/dist/agent/channels/discord-rest.js +75 -0
- package/dist/agent/channels/discord-rest.js.map +1 -0
- package/dist/agent/channels/discord.d.ts +3 -0
- package/dist/agent/channels/discord.d.ts.map +1 -0
- package/dist/agent/channels/discord.js +230 -0
- package/dist/agent/channels/discord.js.map +1 -0
- package/dist/agent/commands/discord-command-sync.d.ts +66 -0
- package/dist/agent/commands/discord-command-sync.d.ts.map +1 -0
- package/dist/agent/commands/discord-command-sync.js +224 -0
- package/dist/agent/commands/discord-command-sync.js.map +1 -0
- package/dist/agent/commands/discord-commands.d.ts +45 -0
- package/dist/agent/commands/discord-commands.d.ts.map +1 -0
- package/dist/agent/commands/discord-commands.js +266 -0
- package/dist/agent/commands/discord-commands.js.map +1 -0
- package/dist/agent/contributions.d.ts +17 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +31 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/identity/discord-link-consent.d.ts +13 -0
- package/dist/agent/identity/discord-link-consent.d.ts.map +1 -0
- package/dist/agent/identity/discord-link-consent.js +111 -0
- package/dist/agent/identity/discord-link-consent.js.map +1 -0
- package/dist/agent/ops.d.ts +2 -0
- package/dist/agent/ops.d.ts.map +1 -0
- package/dist/agent/ops.js +13 -0
- package/dist/agent/ops.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +78 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +5 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +125 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +90 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/package.json +185 -0
- package/tsconfig.build.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Discord WEBHOOK EVENTS β the app-level install/uninstall audit trail (OPE-118 Discord Β§9).
|
|
2
|
+
//
|
|
3
|
+
// Distinct from BOTH of Discord's other webhook-shaped things, which is the whole reason this file
|
|
4
|
+
// carries a header:
|
|
5
|
+
// β’ NOT an "incoming webhook" (Β§6) β that's a channel-scoped POST-only credential;
|
|
6
|
+
// β’ NOT the Interactions Endpoint (Β§5, discord-front.ts) β that's slash commands / button clicks.
|
|
7
|
+
// Webhook Events are configured on a SEPARATE "Webhooks" page of the app in the developer portal
|
|
8
|
+
// (its own URL), delivered app-level, and β critically β they do NOT carry ordinary chat events.
|
|
9
|
+
// Channel messages remain Gateway-only (Β§8, deploy/discord-relay); the ONLY thing OE wants here is
|
|
10
|
+
// `APPLICATION_AUTHORIZED` / `APPLICATION_DEAUTHORIZED`: the real-time signal that a guild admin
|
|
11
|
+
// installed or removed the app, which OE previously had no way to see short of noticing silence.
|
|
12
|
+
//
|
|
13
|
+
// OBSERVABILITY ONLY. This route gates nothing, drops nothing, and wakes nothing β it writes audit
|
|
14
|
+
// rows. A failure to audit is swallowed (the appendAudit layer is best-effort by contract) and the
|
|
15
|
+
// endpoint still acks, because a non-2xx makes Discord retry and eventually SUSPEND delivery.
|
|
16
|
+
//
|
|
17
|
+
// Payload shape verified against docs.discord.com/developers/events/webhook-events (2026-07):
|
|
18
|
+
// { version: 1, application_id, type, event: { type, timestamp, data } }
|
|
19
|
+
// type 0 = PING (endpoint verification), type 1 = Event.
|
|
20
|
+
// APPLICATION_AUTHORIZED data: { integration_type, user, scopes, guild? }
|
|
21
|
+
// β integration_type 0 = guild install (guild present), 1 = user install (no guild).
|
|
22
|
+
// APPLICATION_DEAUTHORIZED data: { user } (no guild, no scopes β Discord sends neither)
|
|
23
|
+
// Discord requires a 204 with an EMPTY body within 3s for both the PING and every event, and a 401
|
|
24
|
+
// when signature validation fails; repeated failures get the endpoint unregistered.
|
|
25
|
+
//
|
|
26
|
+
// Signature: the SAME Ed25519 app public key the Interactions Endpoint uses, so this reuses
|
|
27
|
+
// `verifyDiscordInteraction` (discord-front.ts) verbatim rather than standing up a second
|
|
28
|
+
// verification path β one key, one verifier.
|
|
29
|
+
import { POST } from "eve/channels";
|
|
30
|
+
import { verifyDiscordInteraction } from "./discord-front.js";
|
|
31
|
+
import { appendAudit, deploymentOrg } from "@open-everywhere/core/agent/adapter-api.js";
|
|
32
|
+
/** The public URL to paste into the app's developer-portal **Webhooks** page (NOT the
|
|
33
|
+
* Interactions Endpoint URL, which stays {@link PUBLIC_DISCORD_ROUTE}). */
|
|
34
|
+
export const PUBLIC_DISCORD_EVENTS_ROUTE = "/eve/v1/discord/events";
|
|
35
|
+
/** Discord's outer webhook `type`. */
|
|
36
|
+
const WEBHOOK_TYPE_PING = 0;
|
|
37
|
+
const WEBHOOK_TYPE_EVENT = 1;
|
|
38
|
+
function elog(msg) {
|
|
39
|
+
console.log(`[open-everywhere:discord-events] ${msg}`);
|
|
40
|
+
}
|
|
41
|
+
function str(v) {
|
|
42
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
43
|
+
}
|
|
44
|
+
function obj(v) {
|
|
45
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Classify one raw Webhook Events body. PURE β no I/O, so every branch is unit-tested.
|
|
49
|
+
*
|
|
50
|
+
* The audit partition mirrors what the rest of the Discord surface uses: the GUILD is the
|
|
51
|
+
* workspace (the org boundary β `bindDiscord` maps guildβworkspace), and the authorizing user is
|
|
52
|
+
* the principal. A USER install (integration_type 1) has no guild at all, so it partitions under
|
|
53
|
+
* the sentinel workspace `user-install` rather than being forced into a guild it doesn't have β
|
|
54
|
+
* a fabricated workspace id would be worse than an honest one.
|
|
55
|
+
*/
|
|
56
|
+
export function classifyDiscordEvent(rawBody) {
|
|
57
|
+
let json;
|
|
58
|
+
try {
|
|
59
|
+
json = JSON.parse(rawBody);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { kind: "ack", reason: "unparseable body" };
|
|
63
|
+
}
|
|
64
|
+
const p = obj(json);
|
|
65
|
+
if (!p)
|
|
66
|
+
return { kind: "ack", reason: "non-object body" };
|
|
67
|
+
if (p["type"] === WEBHOOK_TYPE_PING)
|
|
68
|
+
return { kind: "ack", reason: "PING β endpoint verification" };
|
|
69
|
+
if (p["type"] !== WEBHOOK_TYPE_EVENT)
|
|
70
|
+
return { kind: "ack", reason: `webhook type ${String(p["type"])} β not an event` };
|
|
71
|
+
const event = obj(p["event"]);
|
|
72
|
+
const eventType = str(event?.["type"]);
|
|
73
|
+
if (eventType !== "APPLICATION_AUTHORIZED" && eventType !== "APPLICATION_DEAUTHORIZED") {
|
|
74
|
+
// Entitlements, Social-SDK lobby/DM events, and anything Discord adds later: acked, unaudited.
|
|
75
|
+
return { kind: "ack", reason: `event ${eventType ?? "?"} β not an install/uninstall` };
|
|
76
|
+
}
|
|
77
|
+
const data = obj(event?.["data"]);
|
|
78
|
+
const guild = obj(data?.["guild"]);
|
|
79
|
+
const guildId = str(guild?.["id"]);
|
|
80
|
+
const userId = str(obj(data?.["user"])?.["id"]);
|
|
81
|
+
// integration_type is only sent on AUTHORIZED; 0 = guild install, 1 = user install.
|
|
82
|
+
const integrationType = typeof data?.["integration_type"] === "number" ? data["integration_type"] : undefined;
|
|
83
|
+
const scopes = Array.isArray(data?.["scopes"]) ? data["scopes"].filter((s) => typeof s === "string") : undefined;
|
|
84
|
+
return {
|
|
85
|
+
kind: "audit",
|
|
86
|
+
action: eventType === "APPLICATION_AUTHORIZED" ? "surface.app_installed" : "surface.app_uninstalled",
|
|
87
|
+
// A DEAUTHORIZED payload carries no guild (Discord sends only `user`), so an uninstall of a
|
|
88
|
+
// guild install partitions as unknown β honest, and the paired install row names the guild.
|
|
89
|
+
workspace: guildId ?? (integrationType === 1 ? "user-install" : "unknown"),
|
|
90
|
+
...(userId ? { principalId: userId } : {}),
|
|
91
|
+
detail: {
|
|
92
|
+
event: eventType,
|
|
93
|
+
...(integrationType !== undefined ? { integrationType } : {}),
|
|
94
|
+
...(guildId ? { guildId } : {}),
|
|
95
|
+
// Scope names only β the grant's shape, not any member data.
|
|
96
|
+
...(scopes && scopes.length > 0 ? { scopes } : {}),
|
|
97
|
+
...(str(event?.["timestamp"]) ? { eventTimestamp: str(event?.["timestamp"]) } : {}),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Discord requires an EMPTY 204 body for both the PING and every event, within 3 seconds. */
|
|
102
|
+
const ACK = () => new Response(null, { status: 204 });
|
|
103
|
+
/**
|
|
104
|
+
* Build the Webhook Events route handler: verify (fail-closed 401), classify, audit, ack.
|
|
105
|
+
*
|
|
106
|
+
* The audit write is deliberately AWAITED rather than backgrounded β a `surface.app_*` row is one
|
|
107
|
+
* cheap insert and Discord's budget is 3 seconds, so we'd rather the row land than shave a
|
|
108
|
+
* millisecond. It is also wrapped: an audit failure must never turn into a non-2xx, because
|
|
109
|
+
* Discord retries a failure and then suspends the endpoint β losing the very observability this
|
|
110
|
+
* route exists to provide.
|
|
111
|
+
*/
|
|
112
|
+
export function buildDiscordEventsHandler(opts = {}) {
|
|
113
|
+
const now = opts.now ?? (() => Date.now());
|
|
114
|
+
const publicKey = () => opts.publicKey ?? process.env.DISCORD_PUBLIC_KEY;
|
|
115
|
+
const audit = opts.audit ??
|
|
116
|
+
(async (row) => {
|
|
117
|
+
await appendAudit({ org: deploymentOrg(), platform: "discord", workspace: row.workspace, ...(row.principalId ? { principalId: row.principalId } : {}), action: row.action, detail: row.detail });
|
|
118
|
+
});
|
|
119
|
+
return async (req) => {
|
|
120
|
+
const rawBody = await req.text();
|
|
121
|
+
if (!verifyDiscordInteraction(rawBody, req.headers, publicKey(), now())) {
|
|
122
|
+
return new Response("unauthorized", { status: 401 });
|
|
123
|
+
}
|
|
124
|
+
const decision = classifyDiscordEvent(rawBody);
|
|
125
|
+
if (decision.kind === "ack") {
|
|
126
|
+
elog(`acked: ${decision.reason}`);
|
|
127
|
+
return ACK();
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await audit({ action: decision.action, workspace: decision.workspace, ...(decision.principalId ? { principalId: decision.principalId } : {}), detail: decision.detail });
|
|
131
|
+
elog(`${decision.detail["event"]} on ${decision.workspace} audited as ${decision.action}`);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// Observability-only: never let a failed row become a delivery failure (Discord would retry,
|
|
135
|
+
// then suspend the endpoint).
|
|
136
|
+
elog(`audit write failed (acked anyway): ${String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
return ACK();
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Mount the Webhook Events route onto the Discord channel. Additive: it only PREPENDS a route to
|
|
143
|
+
* the same channel object (one channel name β one session namespace, the frontDiscordChannel
|
|
144
|
+
* shape), touching neither the interactions front nor eve's own adapter/receive. With no URL
|
|
145
|
+
* configured in the developer portal this route is simply never hit.
|
|
146
|
+
*/
|
|
147
|
+
export function withDiscordEventsRoute(channel, opts = {}) {
|
|
148
|
+
return { ...channel, routes: [POST(PUBLIC_DISCORD_EVENTS_ROUTE, buildDiscordEventsHandler(opts)), ...channel.routes] };
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=discord-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-events.js","sourceRoot":"","sources":["../../../agent/channels/discord-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type Channel, type RouteHandlerArgs } from "eve/channels";
|
|
2
|
+
import { type DiscordInteractionResponse } from "../approvals/discord-flow.js";
|
|
3
|
+
import { type ApprovalRespondFn } from "./discord-approval-card.js";
|
|
4
|
+
import { type AttachSessionForCancel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
5
|
+
/** An HTTP route handler β `RouteHandler` isn't a public eve export, so we mirror its shape
|
|
6
|
+
* (same workaround as slack-front.ts). */
|
|
7
|
+
type RouteHandler = (req: Request, args: RouteHandlerArgs) => Promise<Response>;
|
|
8
|
+
/** The public URL Discord POSTs every interaction to (the app's Interactions Endpoint URL). */
|
|
9
|
+
export declare const PUBLIC_DISCORD_ROUTE = "/eve/v1/discord";
|
|
10
|
+
/** The internal path the wrapped `discordChannel` is mounted at (never hit by Discord directly). */
|
|
11
|
+
export declare const INTERNAL_DISCORD_ROUTE = "/eve/v1/discord/interaction";
|
|
12
|
+
/**
|
|
13
|
+
* Verify a Discord interaction over the RAW body (the exact bytes Discord signed):
|
|
14
|
+
* Ed25519 of `{timestamp}{body}` against the app public key, with a 5-minute skew window.
|
|
15
|
+
* Fail-closed: no key configured, a missing header, a malformed/stale timestamp, or a
|
|
16
|
+
* signature mismatch all return false β never "verified". `nowMs` is injectable so the
|
|
17
|
+
* skew window is tested; the crypto itself is eve's public `verifyDiscordSignature`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyDiscordInteraction(rawBody: string, headers: Headers, publicKey: string | undefined, nowMs?: number): boolean;
|
|
20
|
+
/** The decision the front route makes for one verified inbound interaction. The parsed
|
|
21
|
+
* body rides the approval-click branch so the handler needn't re-parse it. */
|
|
22
|
+
export type DiscordFrontDecision = {
|
|
23
|
+
readonly kind: "forward";
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "approval-click";
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
readonly interaction: unknown;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "link-consent-click";
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
readonly interaction: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** An `oe_grant:` credential-grant card button click (core access/credential-grant-card.ts) β ours:
|
|
35
|
+
* the OWNER answering a request to spend their credential (OPE-1260). */
|
|
36
|
+
| {
|
|
37
|
+
readonly kind: "grant-click";
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
readonly interaction: unknown;
|
|
40
|
+
}
|
|
41
|
+
/** An `oe_input:` BRIDGED-APPROVAL card button click (core channels/approval-card.ts) β ours: the
|
|
42
|
+
* room answering an eve `tool-approval` this deployment rendered onto a unified origin (OPE-1720).
|
|
43
|
+
* eve's own route cannot resolve it β the parked request lives on the UNIFIED window, not on the
|
|
44
|
+
* Discord channel's session β so forwarding it would drop the tap. */
|
|
45
|
+
| {
|
|
46
|
+
readonly kind: "input-click";
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
readonly interaction: unknown;
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: "oe-command";
|
|
51
|
+
readonly reason: string;
|
|
52
|
+
readonly interaction: unknown;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Decide how the front route handles a raw interaction body. FORWARD is the fail-safe
|
|
56
|
+
* default: anything discordChannel owns (PING/PONG, any OTHER slash command, eve's own HITL
|
|
57
|
+
* component clicks and modal submits) or anything unrecognized goes to it unchanged.
|
|
58
|
+
* A MESSAGE_COMPONENT (type 3) whose custom_id starts with our `oe-approval:` prefix, or an
|
|
59
|
+
* APPLICATION_COMMAND (type 2) named "oe", is ours. Pure: no I/O, so every branch is
|
|
60
|
+
* unit-tested.
|
|
61
|
+
*/
|
|
62
|
+
export declare function classifyDiscordInteraction(rawBody: string): DiscordFrontDecision;
|
|
63
|
+
/** Injectable seams so the front route is testable without live crypto env / the real store. */
|
|
64
|
+
export interface DiscordFrontOptions {
|
|
65
|
+
/** The app public key; defaults to DISCORD_PUBLIC_KEY (same env eve's channel reads). */
|
|
66
|
+
readonly publicKey?: string;
|
|
67
|
+
readonly now?: () => number;
|
|
68
|
+
/** Approval-click handler (defaults to the real approvals/discord-flow.ts). Injectable so
|
|
69
|
+
* tests can stub it without the store/executor machinery it drags in. */
|
|
70
|
+
readonly handleApprovalInteraction?: (raw: unknown, deps?: {
|
|
71
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
72
|
+
}) => Promise<DiscordInteractionResponse | null>;
|
|
73
|
+
/** Identity-consent click handler (defaults to the real identity/discord-link-consent.ts).
|
|
74
|
+
* Injectable for the same reason the approval one is β it reaches the link store. */
|
|
75
|
+
readonly handleLinkConsentInteraction?: (raw: unknown) => Promise<DiscordInteractionResponse | null>;
|
|
76
|
+
/** Credential-grant card click handler (defaults to the real access/discord-grant-click.ts).
|
|
77
|
+
* Injectable for the same reason the two above are β it reaches the grant store.
|
|
78
|
+
*
|
|
79
|
+
* π΄ IT TAKES THE ROUTE'S `waitUntil` (OPE-1282). That door answers with a DEFERRED response and
|
|
80
|
+
* resolves the click off the response path, so it needs somewhere for that work to live past the
|
|
81
|
+
* reply. A stub that ignores the second argument gets the old synchronous shape, which is exactly
|
|
82
|
+
* what a test wants. */
|
|
83
|
+
readonly handleGrantCardInteraction?: (raw: unknown, deps?: {
|
|
84
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
85
|
+
}) => Promise<DiscordInteractionResponse | null>;
|
|
86
|
+
/** Bridged-approval-card `oe_input:` tap handler (OPE-1720, defaults to
|
|
87
|
+
* `./discord-approval-card.ts`). Injectable so the classification and the branch are testable
|
|
88
|
+
* without a store, a bot token or eve's `respond` primitive. It takes the SETTLE delivery as its
|
|
89
|
+
* second argument because `args.from` is the only place eve hands that primitive out, so it is
|
|
90
|
+
* bound in the route and injected rather than reached for inside the handler. */
|
|
91
|
+
readonly handleApprovalCardInteraction?: (raw: unknown, respond: ApprovalRespondFn, deps?: {
|
|
92
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
93
|
+
}) => Promise<DiscordInteractionResponse | null>;
|
|
94
|
+
/** "/oe" zero-inference command handler (defaults to the real commands/discord-commands.ts).
|
|
95
|
+
* Injectable so tests can stub it without the spend/approval/memory machinery it may touch. */
|
|
96
|
+
readonly handleOeCommand?: (raw: unknown, attachSession?: AttachSessionForCancel) => Promise<DiscordInteractionResponse | null>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build the front route handler. Buffers the body once, verifies the Ed25519 signature,
|
|
100
|
+
* then answers our approval clicks or forwards per {@link classifyDiscordInteraction}.
|
|
101
|
+
* `innerHandler` is discordChannel's own POST handler, invoked with a FRESH Request
|
|
102
|
+
* (re-readable body) + the same {@link RouteHandlerArgs}, so discordChannel re-verifies
|
|
103
|
+
* and dispatches exactly as if it owned the URL.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildDiscordFrontHandler(innerHandler: RouteHandler, opts?: DiscordFrontOptions): RouteHandler;
|
|
106
|
+
/**
|
|
107
|
+
* Compose the approval-click front route with a wrapped `discordChannel`. `inner` must have
|
|
108
|
+
* been built with `route: INTERNAL_DISCORD_ROUTE`. Returns the SAME channel value
|
|
109
|
+
* (preserving eve's internal `__kind`/adapter/receive) with the public front route
|
|
110
|
+
* prepended β so Discord's Interactions Endpoint URL hits us, and both routes share one
|
|
111
|
+
* channel name β one session namespace. (Byte-for-byte the frontSlackChannel pattern.)
|
|
112
|
+
*/
|
|
113
|
+
export declare function frontDiscordChannel<C extends Channel<any, any, any>>(inner: C, opts?: DiscordFrontOptions): C;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=discord-front.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-front.d.ts","sourceRoot":"","sources":["../../../agent/channels/discord-front.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Approval-click front for the Discord interactions endpoint β the Discord sibling of
|
|
2
|
+
// slack-front.ts's option-(c) route-fronting, for the same structural reason: eve's
|
|
3
|
+
// `discordChannel` owns the interactions URL and handles MESSAGE_COMPONENT clicks itself
|
|
4
|
+
// (its own HITL decode β DEFERRED_UPDATE), exposing no onComponent hook β so an
|
|
5
|
+
// `oe-approval:` button click would vanish into eve's deferral. We FRONT the public URL
|
|
6
|
+
// with our own route, answer OUR clicks (approvals/discord-flow.ts), and forward everything
|
|
7
|
+
// else β PING, slash commands, eve's `eve_input:` HITL clicks, modal submits β to
|
|
8
|
+
// discordChannel's own handler unchanged.
|
|
9
|
+
//
|
|
10
|
+
// The design (mirrors frontSlackChannel exactly):
|
|
11
|
+
// β’ The wrapped `discordChannel` is pointed at an INTERNAL route (`/eve/v1/discord/interaction`).
|
|
12
|
+
// β’ OUR route sits at the PUBLIC `/eve/v1/discord` (the Interactions Endpoint URL in the
|
|
13
|
+
// Discord developer portal β unchanged, so no re-registration).
|
|
14
|
+
// β’ Both routes live on the SAME channel object (one channel name β one session namespace),
|
|
15
|
+
// and forwarding hands eve a FRESH Request over the same raw body, so it re-verifies and
|
|
16
|
+
// dispatches exactly as if it owned the URL β we only ADD a gate, never weaken one.
|
|
17
|
+
//
|
|
18
|
+
// The front route:
|
|
19
|
+
// 1. Buffers the RAW body ONCE (needed for BOTH signature verification AND forwarding).
|
|
20
|
+
// 2. Verifies Discord's Ed25519 signature over `timestamp + rawBody` (fail-closed 401).
|
|
21
|
+
// Unlike slack-front (which had to re-implement Slack's HMAC), eve publicly exports
|
|
22
|
+
// `verifyDiscordSignature`, so the actual crypto is eve's β we only add the header
|
|
23
|
+
// plumbing + skew window around it.
|
|
24
|
+
// 3. Classifies: a type-3 MESSAGE_COMPONENT whose custom_id carries our `oe-approval:`
|
|
25
|
+
// prefix (the approvals plane) or our `oe-link:` prefix (the identity-consent button,
|
|
26
|
+
// OPE-622) β OURS, answered synchronously with the interaction response the matching click
|
|
27
|
+
// handler returns; a type-2 APPLICATION_COMMAND named "oe" β OURS too (backlog item
|
|
28
|
+
// 23's zero-inference slash command β see commands/core.ts), answered synchronously and
|
|
29
|
+
// NEVER forwarded to discordChannel's `onCommand`, which dispatches a full agent turn for
|
|
30
|
+
// ANY registered command name (docs/deploy/surfaces/discord.md); anything else β forward.
|
|
31
|
+
import { POST } from "eve/channels";
|
|
32
|
+
import { verifyDiscordSignature } from "eve/channels/discord";
|
|
33
|
+
import { OE_DISCORD_APPROVAL_PREFIX } from "../approvals/discord-card.js";
|
|
34
|
+
import { handleDiscordApprovalInteraction } from "../approvals/discord-flow.js";
|
|
35
|
+
import { LINK_CONSENT_CLICK_PREFIX, OE_GRANT_ACTION_PREFIX, OE_INPUT_CLICK_PREFIX } from "@open-everywhere/core/agent/adapter-api.js";
|
|
36
|
+
import { handleDiscordLinkConsentInteraction } from "../identity/discord-link-consent.js";
|
|
37
|
+
import { handleDiscordGrantCardInteraction } from "../access/discord-grant-click.js";
|
|
38
|
+
import { handleDiscordApprovalCardInteraction } from "./discord-approval-card.js";
|
|
39
|
+
import { approvalCardRespondVia, OE_COMMAND_NAME } from "@open-everywhere/core/agent/adapter-api.js";
|
|
40
|
+
import { handleDiscordOeCommandInteraction } from "../commands/discord-commands.js";
|
|
41
|
+
/** The public URL Discord POSTs every interaction to (the app's Interactions Endpoint URL). */
|
|
42
|
+
export const PUBLIC_DISCORD_ROUTE = "/eve/v1/discord";
|
|
43
|
+
/** The internal path the wrapped `discordChannel` is mounted at (never hit by Discord directly). */
|
|
44
|
+
export const INTERNAL_DISCORD_ROUTE = "/eve/v1/discord/interaction";
|
|
45
|
+
// --- signature verification (Discord Ed25519) --------------------------------
|
|
46
|
+
//
|
|
47
|
+
// π΄ THIS SCHEME STAYS INLINE AND IS DELIBERATELY *NOT* ON THE WEBHOOK VERIFIER REGISTRY (OPE-197).
|
|
48
|
+
// Slack's `v0=` scheme was registered under that ticket; this one was assessed alongside it and
|
|
49
|
+
// refused. The reason is NOT that it fails to fit `WebhookVerifier.verify({rawBody, headers, keys,
|
|
50
|
+
// nowMs})` β it fits perfectly, and the registry's `keyKind: "public-key"` was designed against this
|
|
51
|
+
// exact scheme. The reason is that the entry would be UNREACHABLE, and a registry row nothing can
|
|
52
|
+
// reach makes the thing the registry is for WORSE:
|
|
53
|
+
//
|
|
54
|
+
// β’ `registeredWebhookVerifierSources()` is printed verbatim in the generic ingress's 401
|
|
55
|
+
// (webhooks/gate.ts) as the set of sources a caller may declare. Adding `discord` to it would
|
|
56
|
+
// advertise a hook an operator can create, point Discord at, and never receive one delivery on.
|
|
57
|
+
// β’ EVERY Ed25519-signed Discord egress is bound to a RESPONSE contract the generic ingress
|
|
58
|
+
// structurally does not satisfy, so Discord refuses the endpoint before any delivery exists:
|
|
59
|
+
// β the Interactions Endpoint URL is saved only if an immediate `PING` gets a signed `PONG`
|
|
60
|
+
// body back (docs/deploy/surfaces/discord.md Β§1.5 and its Gotchas);
|
|
61
|
+
// β the Webhook Events URL requires "a 204 with an EMPTY body within 3s for both the PING and
|
|
62
|
+
// every event", and "repeated failures get the endpoint unregistered" (the header of
|
|
63
|
+
// channels/discord-events.ts, verified against Discord's docs).
|
|
64
|
+
// The generic ingress answers `200 "ok"` (core channels/webhooks.ts). That is neither, on either
|
|
65
|
+
// arm β and both arms are all there is: Discord has no third signed outbound.
|
|
66
|
+
//
|
|
67
|
+
// This is the shape OPE-207 already deleted once on the Notion side: an arm that shipped and was
|
|
68
|
+
// removed because "nothing could put a hook into the status the arm required, so it was reachable by
|
|
69
|
+
// no route". Registering `discord` would re-create it, one registry row further out.
|
|
70
|
+
//
|
|
71
|
+
// WHAT WOULD FLIP THIS: the generic ingress growing a per-source response contract (a source able to
|
|
72
|
+
// declare the bytes it must answer a verification handshake with). Then the entry becomes reachable
|
|
73
|
+
// and should be added β the verifier body below is already the whole implementation. Until then the
|
|
74
|
+
// honest statement is that this deployment CAN verify Discord's signature, on the one route Discord
|
|
75
|
+
// can actually be pointed at, which is this one.
|
|
76
|
+
//
|
|
77
|
+
// (Note also that the crypto here is EVE'S `verifyDiscordSignature`, not ours β this file adds
|
|
78
|
+
// header plumbing and a skew window. On the "is the scheme ours and inline" axis it was always
|
|
79
|
+
// closer to Linear and Teams, which use eve's verifiers, than to Slack, which does not.)
|
|
80
|
+
const DISCORD_MAX_SKEW_SECONDS = 60 * 5; // same window eve's own verifyDiscordRequest defaults to
|
|
81
|
+
/**
|
|
82
|
+
* Verify a Discord interaction over the RAW body (the exact bytes Discord signed):
|
|
83
|
+
* Ed25519 of `{timestamp}{body}` against the app public key, with a 5-minute skew window.
|
|
84
|
+
* Fail-closed: no key configured, a missing header, a malformed/stale timestamp, or a
|
|
85
|
+
* signature mismatch all return false β never "verified". `nowMs` is injectable so the
|
|
86
|
+
* skew window is tested; the crypto itself is eve's public `verifyDiscordSignature`.
|
|
87
|
+
*/
|
|
88
|
+
export function verifyDiscordInteraction(rawBody, headers, publicKey, nowMs = Date.now()) {
|
|
89
|
+
const signature = headers.get("x-signature-ed25519");
|
|
90
|
+
const timestamp = headers.get("x-signature-timestamp");
|
|
91
|
+
if (!publicKey || !signature || !timestamp)
|
|
92
|
+
return false;
|
|
93
|
+
const ts = Number(timestamp);
|
|
94
|
+
if (!Number.isFinite(ts) || Math.abs(nowMs / 1000 - ts) > DISCORD_MAX_SKEW_SECONDS)
|
|
95
|
+
return false;
|
|
96
|
+
return verifyDiscordSignature({ body: rawBody, publicKey, signature, timestamp });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decide how the front route handles a raw interaction body. FORWARD is the fail-safe
|
|
100
|
+
* default: anything discordChannel owns (PING/PONG, any OTHER slash command, eve's own HITL
|
|
101
|
+
* component clicks and modal submits) or anything unrecognized goes to it unchanged.
|
|
102
|
+
* A MESSAGE_COMPONENT (type 3) whose custom_id starts with our `oe-approval:` prefix, or an
|
|
103
|
+
* APPLICATION_COMMAND (type 2) named "oe", is ours. Pure: no I/O, so every branch is
|
|
104
|
+
* unit-tested.
|
|
105
|
+
*/
|
|
106
|
+
export function classifyDiscordInteraction(rawBody) {
|
|
107
|
+
let json;
|
|
108
|
+
try {
|
|
109
|
+
json = JSON.parse(rawBody);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return { kind: "forward", reason: "unparseable body β let discordChannel answer it" };
|
|
113
|
+
}
|
|
114
|
+
if (typeof json !== "object" || json === null) {
|
|
115
|
+
return { kind: "forward", reason: "non-object body β let discordChannel answer it" };
|
|
116
|
+
}
|
|
117
|
+
const p = json;
|
|
118
|
+
if (p.type === 2) {
|
|
119
|
+
// APPLICATION_COMMAND (a slash command). "/oe" is OUR zero-inference command (backlog
|
|
120
|
+
// item 23) β answered here, NEVER forwarded to discordChannel's onCommand, which
|
|
121
|
+
// dispatches a full agent turn for ANY registered command name. Any OTHER command name
|
|
122
|
+
// (e.g. "/ask", docs/deploy/surfaces/discord.md) is a real user command and still
|
|
123
|
+
// forwards untouched β this is the ONLY command name the front route special-cases.
|
|
124
|
+
const data = typeof p.data === "object" && p.data !== null ? p.data : undefined;
|
|
125
|
+
const name = typeof data?.name === "string" ? data.name : undefined;
|
|
126
|
+
if (name === OE_COMMAND_NAME) {
|
|
127
|
+
return { kind: "oe-command", reason: `"/${OE_COMMAND_NAME}" command β ours, zero-inference, never forwarded`, interaction: json };
|
|
128
|
+
}
|
|
129
|
+
return { kind: "forward", reason: `application command "${name ?? "?"}" β not ours, discordChannel dispatches it` };
|
|
130
|
+
}
|
|
131
|
+
if (p.type !== 3) {
|
|
132
|
+
return { kind: "forward", reason: `interaction type ${String(p.type)} β not a component click` };
|
|
133
|
+
}
|
|
134
|
+
const data = typeof p.data === "object" && p.data !== null ? p.data : undefined;
|
|
135
|
+
const customId = typeof data?.custom_id === "string" ? data.custom_id : undefined;
|
|
136
|
+
// Four OE prefixes now, and they are deliberately different strings so a log line names one
|
|
137
|
+
// feature: `oe-approval:` is the approvals plane, `oe-link:` is the identity-consent button
|
|
138
|
+
// (OPE-622), `oe_grant:` is the credential-grant card (OPE-1260 β before it, that card's buttons
|
|
139
|
+
// rendered here and fell through to eve, so the owner's tap was lost), `oe_input:` is the bridged
|
|
140
|
+
// eve approval card on a unified origin (OPE-1720 β the same lost-tap shape, one plane along).
|
|
141
|
+
// Anything else β eve's own `eve_input:` HITL buttons included β forwards untouched, and the two
|
|
142
|
+
// spellings are one character apart on purpose: `oe_input:` is OURS and `eve_input:` is eve's.
|
|
143
|
+
if (customId?.startsWith(OE_DISCORD_APPROVAL_PREFIX)) {
|
|
144
|
+
return { kind: "approval-click", reason: "oe-approval: component click β ours, never forwarded", interaction: json };
|
|
145
|
+
}
|
|
146
|
+
if (customId?.startsWith(LINK_CONSENT_CLICK_PREFIX)) {
|
|
147
|
+
return { kind: "link-consent-click", reason: "oe-link: component click β ours, never forwarded", interaction: json };
|
|
148
|
+
}
|
|
149
|
+
if (customId?.startsWith(OE_GRANT_ACTION_PREFIX)) {
|
|
150
|
+
return { kind: "grant-click", reason: "oe_grant: component click β ours, never forwarded", interaction: json };
|
|
151
|
+
}
|
|
152
|
+
if (customId?.startsWith(OE_INPUT_CLICK_PREFIX)) {
|
|
153
|
+
return { kind: "input-click", reason: "oe_input: component click β ours, never forwarded", interaction: json };
|
|
154
|
+
}
|
|
155
|
+
return { kind: "forward", reason: "component click β eve's HITL owns it" };
|
|
156
|
+
}
|
|
157
|
+
function interactionJson(body) {
|
|
158
|
+
return new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" } });
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the front route handler. Buffers the body once, verifies the Ed25519 signature,
|
|
162
|
+
* then answers our approval clicks or forwards per {@link classifyDiscordInteraction}.
|
|
163
|
+
* `innerHandler` is discordChannel's own POST handler, invoked with a FRESH Request
|
|
164
|
+
* (re-readable body) + the same {@link RouteHandlerArgs}, so discordChannel re-verifies
|
|
165
|
+
* and dispatches exactly as if it owned the URL.
|
|
166
|
+
*/
|
|
167
|
+
export function buildDiscordFrontHandler(innerHandler, opts = {}) {
|
|
168
|
+
const now = opts.now ?? (() => Date.now());
|
|
169
|
+
const publicKey = () => opts.publicKey ?? process.env.DISCORD_PUBLIC_KEY;
|
|
170
|
+
const handleClick = opts.handleApprovalInteraction ?? handleDiscordApprovalInteraction;
|
|
171
|
+
const handleLinkConsent = opts.handleLinkConsentInteraction ?? handleDiscordLinkConsentInteraction;
|
|
172
|
+
const handleGrantCard = opts.handleGrantCardInteraction ?? handleDiscordGrantCardInteraction;
|
|
173
|
+
const handleApprovalCard = opts.handleApprovalCardInteraction ?? handleDiscordApprovalCardInteraction;
|
|
174
|
+
const handleOeCommand = opts.handleOeCommand ?? handleDiscordOeCommandInteraction;
|
|
175
|
+
return async (req, args) => {
|
|
176
|
+
const rawBody = await req.text();
|
|
177
|
+
if (!verifyDiscordInteraction(rawBody, req.headers, publicKey(), now())) {
|
|
178
|
+
return new Response("unauthorized", { status: 401 });
|
|
179
|
+
}
|
|
180
|
+
const decision = classifyDiscordInteraction(rawBody);
|
|
181
|
+
if (decision.kind === "approval-click") {
|
|
182
|
+
// Ours: parsed, admin-checked, CAS-decided and executed by our code (the same
|
|
183
|
+
// resolveApprovalClick core Slack clicks use), answered as an interaction response.
|
|
184
|
+
//
|
|
185
|
+
// π΄ `args.waitUntil` RIDES ALONG (OPE-1407), for the same reason the grant branch below takes
|
|
186
|
+
// it: the decision FEEDBACK β a Discord REST notice post plus a wake dispatch β runs off the
|
|
187
|
+
// response path, and on a serverless host a promise with nowhere to live is frozen with the
|
|
188
|
+
// invocation. The door awaits the feedback when this is absent, so passing it is what moves
|
|
189
|
+
// that leg out of Discord's three-second budget.
|
|
190
|
+
const response = await handleClick(decision.interaction, { waitUntil: args.waitUntil });
|
|
191
|
+
if (response)
|
|
192
|
+
return interactionJson(response);
|
|
193
|
+
// Defensive: classification said "ours" but the handler disagreed (an ours-prefixed
|
|
194
|
+
// but unparseable custom_id) β fall through to forwarding so the click is never
|
|
195
|
+
// silently swallowed; eve's HITL decode no-ops it with a harmless deferred ack.
|
|
196
|
+
}
|
|
197
|
+
if (decision.kind === "link-consent-click") {
|
|
198
|
+
// Ours: the person named in a directory-transport link request tapped their own consent ask
|
|
199
|
+
// (OPE-622). Core's `resolveLinkConsentClick` owns the decision and the writes; this route only
|
|
200
|
+
// carries Discord's verified envelope in and Discord's EPHEMERAL answer back out. A null means
|
|
201
|
+
// the handler could not read the id after all β forward rather than swallow, exactly as the
|
|
202
|
+
// approval branch does.
|
|
203
|
+
const response = await handleLinkConsent(decision.interaction);
|
|
204
|
+
if (response)
|
|
205
|
+
return interactionJson(response);
|
|
206
|
+
}
|
|
207
|
+
if (decision.kind === "grant-click") {
|
|
208
|
+
// Ours: the OWNER of a credential answered a request to spend it (OPE-1260). Core's
|
|
209
|
+
// `resolveGrantCardClick` owns the decision, the owner check and the writes; this route only
|
|
210
|
+
// carries Discord's verified envelope in and Discord's EPHEMERAL answer back out. A null means
|
|
211
|
+
// the handler could not read the id after all β forward rather than swallow, exactly as the two
|
|
212
|
+
// branches above do.
|
|
213
|
+
//
|
|
214
|
+
// π΄ `args.waitUntil` RIDES ALONG (OPE-1282), and this branch is the only one that passes it.
|
|
215
|
+
// The grant door answers with a DEFERRED response and resolves the click afterwards; without
|
|
216
|
+
// somewhere for that work to survive the reply, a serverless host would freeze the invocation
|
|
217
|
+
// and the owner would sit on "thinkingβ¦" forever. The door falls back to its old synchronous
|
|
218
|
+
// shape when this is absent, so passing it is what turns the fix on.
|
|
219
|
+
const response = await handleGrantCard(decision.interaction, { waitUntil: args.waitUntil });
|
|
220
|
+
if (response)
|
|
221
|
+
return interactionJson(response);
|
|
222
|
+
}
|
|
223
|
+
if (decision.kind === "input-click") {
|
|
224
|
+
// Ours (core channels/approval-card.ts): somebody tapped a bridged eve approval this deployment
|
|
225
|
+
// rendered onto a unified origin (OPE-1720). The handler resolves the ticket, admits the tap
|
|
226
|
+
// through the SAME floor the typed answer takes, and delivers
|
|
227
|
+
// `respond([{requestId, optionId}])` to the parked WINDOW session.
|
|
228
|
+
//
|
|
229
|
+
// π΄ THE DELIVERY GOES BY SESSION ID (OPE-1863). `args.from` is CHANNEL-LOCAL β on this route
|
|
230
|
+
// it addresses `discord:<token>`, while the parked session lives at `unified:<token>` β so the
|
|
231
|
+
// settle threw "the target session was not found" and every tap left the approval parked.
|
|
232
|
+
// `args.attachSession` dispatches by durable session id from any channel, and
|
|
233
|
+
// `approvalCardRespondVia` is the one road every surface binds. Bound HERE and injected because
|
|
234
|
+
// the route handler is the only place eve hands out either primitive.
|
|
235
|
+
//
|
|
236
|
+
// π΄ `args.waitUntil` RIDES ALONG, for the grant branch's reason and one more of this door's
|
|
237
|
+
// own: a settle RESUMES a parked turn, and Discord does not redeliver an interaction it never
|
|
238
|
+
// saw answered β so a slow settle without the defer shows the tapper a red failure over an
|
|
239
|
+
// approval that really did settle. A null means the payload carried our prefix but not a shape
|
|
240
|
+
// the handler could read: forward rather than swallow, exactly as the three branches above do.
|
|
241
|
+
const respondToApproval = approvalCardRespondVia(args.attachSession);
|
|
242
|
+
const response = await handleApprovalCard(decision.interaction, respondToApproval, { waitUntil: args.waitUntil });
|
|
243
|
+
if (response)
|
|
244
|
+
return interactionJson(response);
|
|
245
|
+
}
|
|
246
|
+
if (decision.kind === "oe-command") {
|
|
247
|
+
// Ours: zero-inference β parsed, executed as pure logic (+ the approval-card path for
|
|
248
|
+
// budget raise/clear), answered synchronously as an interaction response. NEVER reaches
|
|
249
|
+
// discordChannel's onCommand (which would dispatch a full agent turn).
|
|
250
|
+
// `args.attachSession` rides along purely for "/oe stop" (OPE-418) β it is I/O-free, so
|
|
251
|
+
// passing it costs nothing on the branches that never touch it.
|
|
252
|
+
const response = await handleOeCommand(decision.interaction, args.attachSession);
|
|
253
|
+
if (response)
|
|
254
|
+
return interactionJson(response);
|
|
255
|
+
// Defensive fallback mirrors the approval-click branch above: classification said
|
|
256
|
+
// "ours" but the handler disagreed β never silently swallow, forward instead.
|
|
257
|
+
}
|
|
258
|
+
// Fresh Request so discordChannel can re-read the raw body + re-verify the signature.
|
|
259
|
+
const forwarded = new Request(req.url, { method: "POST", headers: req.headers, body: rawBody });
|
|
260
|
+
return innerHandler(forwarded, args);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Compose the approval-click front route with a wrapped `discordChannel`. `inner` must have
|
|
265
|
+
* been built with `route: INTERNAL_DISCORD_ROUTE`. Returns the SAME channel value
|
|
266
|
+
* (preserving eve's internal `__kind`/adapter/receive) with the public front route
|
|
267
|
+
* prepended β so Discord's Interactions Endpoint URL hits us, and both routes share one
|
|
268
|
+
* channel name β one session namespace. (Byte-for-byte the frontSlackChannel pattern.)
|
|
269
|
+
*/
|
|
270
|
+
export function frontDiscordChannel(inner, opts = {}) {
|
|
271
|
+
const innerPost = inner.routes.find((r) => r.method === "POST" && r.path === INTERNAL_DISCORD_ROUTE);
|
|
272
|
+
if (!innerPost) {
|
|
273
|
+
throw new Error(`frontDiscordChannel: wrapped discordChannel must be built with route: "${INTERNAL_DISCORD_ROUTE}" ` +
|
|
274
|
+
`(found paths: ${inner.routes.map((r) => r.path).join(", ")})`);
|
|
275
|
+
}
|
|
276
|
+
const front = buildDiscordFrontHandler(innerPost.handler, opts);
|
|
277
|
+
return { ...inner, routes: [POST(PUBLIC_DISCORD_ROUTE, front), ...inner.routes] };
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=discord-front.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-front.js","sourceRoot":"","sources":["../../../agent/channels/discord-front.ts"],"names":[],"mappings":"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"}
|