@open-everywhere/provider-discord 0.1.5 → 0.3.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/NOTICE +9 -9
- package/README.md +1 -1
- package/agent/approvals/discord-card.ts +11 -12
- package/agent/approvals/discord-flow.ts +4 -3
- package/agent/channels/discord-dispatch-target.ts +2 -2
- package/agent/channels/discord-front.ts +10 -52
- package/agent/channels/discord-gateway.ts +6 -3
- package/agent/channels/discord-post-arm.ts +0 -34
- package/agent/channels/discord-post.ts +1 -9
- package/agent/channels/discord.ts +4 -1
- package/agent/commands/discord-commands.ts +23 -9
- package/agent/contributions.ts +1 -1
- package/agent/surface-parity.ts +0 -6
- package/dist/agent/approvals/discord-card.d.ts.map +1 -1
- package/dist/agent/approvals/discord-card.js +11 -12
- package/dist/agent/approvals/discord-card.js.map +1 -1
- package/dist/agent/approvals/discord-flow.d.ts +1 -1
- package/dist/agent/approvals/discord-flow.d.ts.map +1 -1
- package/dist/agent/approvals/discord-flow.js +3 -2
- package/dist/agent/approvals/discord-flow.js.map +1 -1
- package/dist/agent/channels/discord-dispatch-target.js +2 -2
- package/dist/agent/channels/discord-dispatch-target.js.map +1 -1
- package/dist/agent/channels/discord-front.d.ts +0 -18
- package/dist/agent/channels/discord-front.d.ts.map +1 -1
- package/dist/agent/channels/discord-front.js +10 -37
- package/dist/agent/channels/discord-front.js.map +1 -1
- package/dist/agent/channels/discord-gateway.d.ts.map +1 -1
- package/dist/agent/channels/discord-gateway.js +6 -3
- package/dist/agent/channels/discord-gateway.js.map +1 -1
- package/dist/agent/channels/discord-post-arm.d.ts.map +1 -1
- package/dist/agent/channels/discord-post-arm.js +0 -34
- package/dist/agent/channels/discord-post-arm.js.map +1 -1
- package/dist/agent/channels/discord-post.d.ts +2 -10
- package/dist/agent/channels/discord-post.d.ts.map +1 -1
- package/dist/agent/channels/discord-post.js +2 -9
- package/dist/agent/channels/discord-post.js.map +1 -1
- package/dist/agent/channels/discord.js +4 -1
- package/dist/agent/channels/discord.js.map +1 -1
- package/dist/agent/commands/discord-commands.d.ts +2 -0
- package/dist/agent/commands/discord-commands.d.ts.map +1 -1
- package/dist/agent/commands/discord-commands.js +19 -9
- package/dist/agent/commands/discord-commands.js.map +1 -1
- package/dist/agent/contributions.d.ts +1 -1
- package/dist/agent/contributions.d.ts.map +1 -1
- package/dist/agent/contributions.js +1 -1
- package/dist/agent/contributions.js.map +1 -1
- package/dist/agent/surface-parity.d.ts.map +1 -1
- package/dist/agent/surface-parity.js +0 -6
- package/dist/agent/surface-parity.js.map +1 -1
- package/package.json +3 -7
- package/agent/channels/discord-approval-card.ts +0 -373
package/NOTICE
CHANGED
|
@@ -12,28 +12,28 @@ Apache License, Version 2.0.
|
|
|
12
12
|
|
|
13
13
|
Modifications to eve
|
|
14
14
|
|
|
15
|
-
The @open-everywhere/core package distributes patches/eve+0.
|
|
16
|
-
patch file is a modification of eve 0.
|
|
17
|
-
Apache License, Version 2.0. The copyright notice of eve 0.
|
|
15
|
+
The @open-everywhere/core package distributes patches/eve+0.71.0.patch. That
|
|
16
|
+
patch file is a modification of eve 0.71.0. Vercel licenses eve 0.71.0 under the
|
|
17
|
+
Apache License, Version 2.0. The copyright notice of eve 0.71.0 reads
|
|
18
18
|
"Copyright 2026 Vercel, Inc. and contributors". The Open Everywhere authors made
|
|
19
19
|
the changes in the patch file.
|
|
20
20
|
|
|
21
|
-
The patch modifies
|
|
21
|
+
The patch modifies four files of eve 0.71.0:
|
|
22
22
|
|
|
23
|
-
dist/src/context/dynamic-tool-lifecycle.js
|
|
24
23
|
dist/src/execution/web-fetch/request.js
|
|
25
24
|
dist/src/harness/tool-loop.js
|
|
26
25
|
dist/src/internal/authored-package-boundary.js
|
|
27
26
|
dist/src/protocol/message.d.ts
|
|
28
|
-
dist/src/tasks/session-index.js
|
|
29
27
|
|
|
30
28
|
Open Everywhere does not ship the eve package. eve is a peer dependency, so the
|
|
31
|
-
consumer installs eve 0.
|
|
29
|
+
consumer installs eve 0.71.0 from the registry. The patch file carries the
|
|
32
30
|
modified portions of eve. The oe-eve-patch command, which
|
|
33
31
|
@open-everywhere/core ships, applies them to the eve that the consumer
|
|
34
|
-
installed. The command
|
|
32
|
+
installed. The command patches no file outside the consumer's own project. To
|
|
33
|
+
restore an eve that holds an older Open Everywhere patch, it also writes to npm's
|
|
34
|
+
cache and to a temporary directory.
|
|
35
35
|
|
|
36
|
-
The attribution notice of eve 0.
|
|
36
|
+
The attribution notice of eve 0.71.0 follows, as section 4(d) of the Apache
|
|
37
37
|
License, Version 2.0 requires. The notices for eve's bundled code-mode runtime
|
|
38
38
|
are not reproduced, because the patch modifies no part of that runtime.
|
|
39
39
|
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ the Bot API host `discord.com/api`. Core's registries refuse a provider the depl
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
npm i @open-everywhere/core @open-everywhere/provider-discord eve@0.
|
|
18
|
+
npm i @open-everywhere/core @open-everywhere/provider-discord eve@0.71.0
|
|
19
19
|
npx oe-eve-patch
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// that already ran, carrying the one "This should not have run" button and never a decision one.
|
|
20
20
|
|
|
21
21
|
import type { PendingActionRow } from "@open-everywhere/core/agent/adapter-api.js";
|
|
22
|
-
import { grantButtonLabel, sessionGrantKind } from "@open-everywhere/core/agent/adapter-api.js";
|
|
22
|
+
import { getSettings, grantButtonLabel, pendingCardExpiryClause, sessionGrantKind } from "@open-everywhere/core/agent/adapter-api.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Every approval button custom_id this feature owns starts with this. Deliberately distinct
|
|
@@ -39,7 +39,7 @@ export const DISCORD_CUSTOM_ID_MAX = 100;
|
|
|
39
39
|
// the Slack prefix.
|
|
40
40
|
const COMPONENT_ACTION_ROW = 1;
|
|
41
41
|
const COMPONENT_BUTTON = 2;
|
|
42
|
-
const BUTTON_STYLE_SECONDARY = 2; // grey — Approve for
|
|
42
|
+
const BUTTON_STYLE_SECONDARY = 2; // grey — Approve for this routine (the grantable-kind middle button, OPE-53/OPE-11)
|
|
43
43
|
const BUTTON_STYLE_SUCCESS = 3; // green — Approve (Slack's `style: "primary"` analogue)
|
|
44
44
|
const BUTTON_STYLE_DANGER = 4; // red — Reject (Slack's `style: "danger"` analogue)
|
|
45
45
|
|
|
@@ -123,7 +123,7 @@ function cardBody(row: PendingActionRow, summaryLines: readonly string[], footer
|
|
|
123
123
|
* the org rides the custom_id on Discord). Mirrors card.ts's pendingCardBlocks.
|
|
124
124
|
*/
|
|
125
125
|
export function pendingDiscordCard(row: PendingActionRow, summaryLines: readonly string[]): DiscordCardMessage {
|
|
126
|
-
const content = cardBody(row, summaryLines, `-# Requested by <@${row.requestedBy}> ·
|
|
126
|
+
const content = cardBody(row, summaryLines, `-# Requested by <@${row.requestedBy}> · ${pendingCardExpiryClause(row.expiresAt)}`);
|
|
127
127
|
const buttons: DiscordComponent[] = [
|
|
128
128
|
{
|
|
129
129
|
type: COMPONENT_BUTTON,
|
|
@@ -132,18 +132,17 @@ export function pendingDiscordCard(row: PendingActionRow, summaryLines: readonly
|
|
|
132
132
|
custom_id: buildDiscordApprovalCustomId(row, "approve"),
|
|
133
133
|
},
|
|
134
134
|
];
|
|
135
|
-
// OPE-53: a
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
135
|
+
// OPE-53, OPE-2363: only a ROUTINE's card gets the middle button — publish this one AND skip the card
|
|
136
|
+
// for the same kind on later fires of THIS routine. sessionGrantKind() (core approvals/session-grant.ts)
|
|
137
|
+
// decides the KIND (`pr.open` alone), and grantButtonLabel() decides the reach: undefined for every
|
|
138
|
+
// card in a conversation, which offers Approve and Reject only. The label and the presence both come
|
|
139
|
+
// from core, so Slack and Discord can never disagree.
|
|
140
|
+
const grantLabel = sessionGrantKind(row) !== null ? grantButtonLabel(row, getSettings().approvals.routineGrantTtlHours) : undefined;
|
|
141
|
+
if (grantLabel !== undefined) {
|
|
141
142
|
buttons.push({
|
|
142
143
|
type: COMPONENT_BUTTON,
|
|
143
144
|
style: BUTTON_STYLE_SECONDARY,
|
|
144
|
-
|
|
145
|
-
// the click grants for this session or standing-for-this-routine (a fire's card).
|
|
146
|
-
label: grantButtonLabel(row),
|
|
145
|
+
label: grantLabel,
|
|
147
146
|
custom_id: buildDiscordApprovalCustomId(row, "approve_session"),
|
|
148
147
|
});
|
|
149
148
|
}
|
|
@@ -251,7 +251,7 @@ export interface DiscordApprovalDeps {
|
|
|
251
251
|
*
|
|
252
252
|
* 🔴 TWO LEGS MOVE, AND THE SECOND ONE EXECUTES. The decision FEEDBACK above, and the core's
|
|
253
253
|
* OPE-251 session-grant RELEASE (OPE-1709), which this file forwards into `resolveApprovalClick`
|
|
254
|
-
* below — an "Approve for
|
|
254
|
+
* below — an "Approve for this routine" click decides and runs up to `MAX_SESSION_GRANT_RELEASES`
|
|
255
255
|
* sibling cards, so it is the heavier of the two. Do not read this hook as feedback plumbing.
|
|
256
256
|
*
|
|
257
257
|
* 🔴 WHAT DOES NOT MOVE IS THE RESPONSE BODY. `resolveApprovalClick` decides it, so that CALL
|
|
@@ -398,8 +398,9 @@ export async function handleDiscordApprovalInteraction(raw: unknown, deps: Disco
|
|
|
398
398
|
// Decision FEEDBACK (task #106 — Slack click-path parity): a fresh notice beside the card (the
|
|
399
399
|
// UPDATE_MESSAGE response is an EDIT, invisible to backscroll) + the cross-thread window wake.
|
|
400
400
|
// The wake's guild id comes from the SIGNED interaction (Slack's team.id analogue — the frozen
|
|
401
|
-
// scopeKey can name a different workspace under an explicit-scope override)
|
|
402
|
-
// interaction has none
|
|
401
|
+
// scopeKey can name a different workspace under an explicit-scope override). A DM-context
|
|
402
|
+
// interaction has none, so the core takes the card's mint-frozen DM `postedTo` as the DM seat's
|
|
403
|
+
// surfaceKey instead (OPE-2376); the notice posts beside the card either way. No transport
|
|
403
404
|
// failure throws out of it; the egress spoof's Vercel refusal can (OPE-1132). On the AWAITED path
|
|
404
405
|
// below that refusal reaches the caller's own catch, which answers the interaction anyway rather
|
|
405
406
|
// than leaving Discord to retry. On the DEFERRED path the response has already gone, so the catch
|
|
@@ -36,8 +36,8 @@ export function resolveDiscordTarget(r: Routine): DispatchTarget | null {
|
|
|
36
36
|
// bindDiscord's re-derived workspace always agree.
|
|
37
37
|
const guildId = storedGuildId ?? r.surfaceKey.split("/")[1] ?? "";
|
|
38
38
|
// OPE-486 — the fire's WAKE ORIGIN, parity with resolveSlackTarget (routine-dispatch.ts). Absent
|
|
39
|
-
// before this fix, so a Discord fire could never divert into the unified window (fire-
|
|
40
|
-
//
|
|
39
|
+
// before this fix, so a Discord fire could never divert into the unified window (fire-work.ts's
|
|
40
|
+
// planFireWork fails closed on a missing origin). Two shapes, matching the codec's XOR
|
|
41
41
|
// (discord-origin-codec.ts): a DM/group-DM fire (mode "dm"/"mpim") gets the `dm: true` variant —
|
|
42
42
|
// NEVER a guild-shaped origin with a possibly-empty guildId, which the codec refuses by
|
|
43
43
|
// construction (D1/D3). Every other fire gets the guild variant; an empty guildId (only possible
|
|
@@ -33,11 +33,10 @@ import { POST, type Channel, type HttpRouteDefinition, type RouteHandlerArgs } f
|
|
|
33
33
|
import { verifyDiscordSignature } from "eve/channels/discord";
|
|
34
34
|
import { OE_DISCORD_APPROVAL_PREFIX } from "../approvals/discord-card.js";
|
|
35
35
|
import { handleDiscordApprovalInteraction, type DiscordInteractionResponse } from "../approvals/discord-flow.js";
|
|
36
|
-
import { LINK_CONSENT_CLICK_PREFIX, OE_GRANT_ACTION_PREFIX
|
|
36
|
+
import { LINK_CONSENT_CLICK_PREFIX, OE_GRANT_ACTION_PREFIX } from "@open-everywhere/core/agent/adapter-api.js";
|
|
37
37
|
import { handleDiscordLinkConsentInteraction } from "../identity/discord-link-consent.js";
|
|
38
38
|
import { handleDiscordGrantCardInteraction } from "../access/discord-grant-click.js";
|
|
39
|
-
import {
|
|
40
|
-
import { approvalCardRespondVia, OE_COMMAND_NAME, type AttachSessionForCancel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
39
|
+
import { oeCommandName, type AttachSessionForCancel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
41
40
|
import { handleDiscordOeCommandInteraction } from "../commands/discord-commands.js";
|
|
42
41
|
|
|
43
42
|
/** An HTTP route handler — `RouteHandler` isn't a public eve export, so we mirror its shape
|
|
@@ -117,11 +116,6 @@ export type DiscordFrontDecision =
|
|
|
117
116
|
/** An `oe_grant:` credential-grant card button click (core access/credential-grant-card.ts) — ours:
|
|
118
117
|
* the OWNER answering a request to spend their credential (OPE-1260). */
|
|
119
118
|
| { readonly kind: "grant-click"; readonly reason: string; readonly interaction: unknown }
|
|
120
|
-
/** An `oe_input:` BRIDGED-APPROVAL card button click (core channels/approval-card.ts) — ours: the
|
|
121
|
-
* room answering an eve `tool-approval` this deployment rendered onto a unified origin (OPE-1720).
|
|
122
|
-
* eve's own route cannot resolve it — the parked request lives on the UNIFIED window, not on the
|
|
123
|
-
* Discord channel's session — so forwarding it would drop the tap. */
|
|
124
|
-
| { readonly kind: "input-click"; readonly reason: string; readonly interaction: unknown }
|
|
125
119
|
| { readonly kind: "oe-command"; readonly reason: string; readonly interaction: unknown };
|
|
126
120
|
|
|
127
121
|
/**
|
|
@@ -151,8 +145,11 @@ export function classifyDiscordInteraction(rawBody: string): DiscordFrontDecisio
|
|
|
151
145
|
// forwards untouched — this is the ONLY command name the front route special-cases.
|
|
152
146
|
const data = typeof p.data === "object" && p.data !== null ? (p.data as Record<string, unknown>) : undefined;
|
|
153
147
|
const name = typeof data?.name === "string" ? data.name : undefined;
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
// The name is the `identity.commandName` setting, read per interaction (OPE-2334), so the
|
|
149
|
+
// router matches the same name the registration declared.
|
|
150
|
+
const commandName = oeCommandName();
|
|
151
|
+
if (name === commandName) {
|
|
152
|
+
return { kind: "oe-command", reason: `"/${commandName}" command — ours, zero-inference, never forwarded`, interaction: json };
|
|
156
153
|
}
|
|
157
154
|
return { kind: "forward", reason: `application command "${name ?? "?"}" — not ours, discordChannel dispatches it` };
|
|
158
155
|
}
|
|
@@ -161,13 +158,11 @@ export function classifyDiscordInteraction(rawBody: string): DiscordFrontDecisio
|
|
|
161
158
|
}
|
|
162
159
|
const data = typeof p.data === "object" && p.data !== null ? (p.data as Record<string, unknown>) : undefined;
|
|
163
160
|
const customId = typeof data?.custom_id === "string" ? data.custom_id : undefined;
|
|
164
|
-
//
|
|
161
|
+
// Three OE prefixes, and they are deliberately different strings so a log line names one
|
|
165
162
|
// feature: `oe-approval:` is the approvals plane, `oe-link:` is the identity-consent button
|
|
166
163
|
// (OPE-622), `oe_grant:` is the credential-grant card (OPE-1260 — before it, that card's buttons
|
|
167
|
-
// rendered here and fell through to eve, so the owner's tap was lost)
|
|
168
|
-
//
|
|
169
|
-
// Anything else — eve's own `eve_input:` HITL buttons included — forwards untouched, and the two
|
|
170
|
-
// spellings are one character apart on purpose: `oe_input:` is OURS and `eve_input:` is eve's.
|
|
164
|
+
// rendered here and fell through to eve, so the owner's tap was lost). Anything else — eve's own
|
|
165
|
+
// `eve_input:` HITL buttons included — forwards untouched.
|
|
171
166
|
if (customId?.startsWith(OE_DISCORD_APPROVAL_PREFIX)) {
|
|
172
167
|
return { kind: "approval-click", reason: "oe-approval: component click — ours, never forwarded", interaction: json };
|
|
173
168
|
}
|
|
@@ -177,9 +172,6 @@ export function classifyDiscordInteraction(rawBody: string): DiscordFrontDecisio
|
|
|
177
172
|
if (customId?.startsWith(OE_GRANT_ACTION_PREFIX)) {
|
|
178
173
|
return { kind: "grant-click", reason: "oe_grant: component click — ours, never forwarded", interaction: json };
|
|
179
174
|
}
|
|
180
|
-
if (customId?.startsWith(OE_INPUT_CLICK_PREFIX)) {
|
|
181
|
-
return { kind: "input-click", reason: "oe_input: component click — ours, never forwarded", interaction: json };
|
|
182
|
-
}
|
|
183
175
|
return { kind: "forward", reason: "component click — eve's HITL owns it" };
|
|
184
176
|
}
|
|
185
177
|
|
|
@@ -208,16 +200,6 @@ export interface DiscordFrontOptions {
|
|
|
208
200
|
raw: unknown,
|
|
209
201
|
deps?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
210
202
|
) => Promise<DiscordInteractionResponse | null>;
|
|
211
|
-
/** Bridged-approval-card `oe_input:` tap handler (OPE-1720, defaults to
|
|
212
|
-
* `./discord-approval-card.ts`). Injectable so the classification and the branch are testable
|
|
213
|
-
* without a store, a bot token or eve's `respond` primitive. It takes the SETTLE delivery as its
|
|
214
|
-
* second argument because `args.from` is the only place eve hands that primitive out, so it is
|
|
215
|
-
* bound in the route and injected rather than reached for inside the handler. */
|
|
216
|
-
readonly handleApprovalCardInteraction?: (
|
|
217
|
-
raw: unknown,
|
|
218
|
-
respond: ApprovalRespondFn,
|
|
219
|
-
deps?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
220
|
-
) => Promise<DiscordInteractionResponse | null>;
|
|
221
203
|
/** "/oe" zero-inference command handler (defaults to the real commands/discord-commands.ts).
|
|
222
204
|
* Injectable so tests can stub it without the spend/approval/memory machinery it may touch. */
|
|
223
205
|
readonly handleOeCommand?: (raw: unknown, attachSession?: AttachSessionForCancel) => Promise<DiscordInteractionResponse | null>;
|
|
@@ -240,7 +222,6 @@ export function buildDiscordFrontHandler(innerHandler: RouteHandler, opts: Disco
|
|
|
240
222
|
const handleClick = opts.handleApprovalInteraction ?? handleDiscordApprovalInteraction;
|
|
241
223
|
const handleLinkConsent = opts.handleLinkConsentInteraction ?? handleDiscordLinkConsentInteraction;
|
|
242
224
|
const handleGrantCard = opts.handleGrantCardInteraction ?? handleDiscordGrantCardInteraction;
|
|
243
|
-
const handleApprovalCard = opts.handleApprovalCardInteraction ?? handleDiscordApprovalCardInteraction;
|
|
244
225
|
const handleOeCommand = opts.handleOeCommand ?? handleDiscordOeCommandInteraction;
|
|
245
226
|
|
|
246
227
|
return async (req: Request, args: RouteHandlerArgs): Promise<Response> => {
|
|
@@ -292,29 +273,6 @@ export function buildDiscordFrontHandler(innerHandler: RouteHandler, opts: Disco
|
|
|
292
273
|
if (response) return interactionJson(response);
|
|
293
274
|
}
|
|
294
275
|
|
|
295
|
-
if (decision.kind === "input-click") {
|
|
296
|
-
// Ours (core channels/approval-card.ts): somebody tapped a bridged eve approval this deployment
|
|
297
|
-
// rendered onto a unified origin (OPE-1720). The handler resolves the ticket, admits the tap
|
|
298
|
-
// through the SAME floor the typed answer takes, and delivers
|
|
299
|
-
// `respond([{requestId, optionId}])` to the parked WINDOW session.
|
|
300
|
-
//
|
|
301
|
-
// 🔴 THE DELIVERY GOES BY SESSION ID (OPE-1863). `args.from` is CHANNEL-LOCAL — on this route
|
|
302
|
-
// it addresses `discord:<token>`, while the parked session lives at `unified:<token>` — so the
|
|
303
|
-
// settle threw "the target session was not found" and every tap left the approval parked.
|
|
304
|
-
// `args.attachSession` dispatches by durable session id from any channel, and
|
|
305
|
-
// `approvalCardRespondVia` is the one road every surface binds. Bound HERE and injected because
|
|
306
|
-
// the route handler is the only place eve hands out either primitive.
|
|
307
|
-
//
|
|
308
|
-
// 🔴 `args.waitUntil` RIDES ALONG, for the grant branch's reason and one more of this door's
|
|
309
|
-
// own: a settle RESUMES a parked turn, and Discord does not redeliver an interaction it never
|
|
310
|
-
// saw answered — so a slow settle without the defer shows the tapper a red failure over an
|
|
311
|
-
// approval that really did settle. A null means the payload carried our prefix but not a shape
|
|
312
|
-
// the handler could read: forward rather than swallow, exactly as the three branches above do.
|
|
313
|
-
const respondToApproval: ApprovalRespondFn = approvalCardRespondVia(args.attachSession);
|
|
314
|
-
const response = await handleApprovalCard(decision.interaction, respondToApproval, { waitUntil: args.waitUntil });
|
|
315
|
-
if (response) return interactionJson(response);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
276
|
if (decision.kind === "oe-command") {
|
|
319
277
|
// Ours: zero-inference — parsed, executed as pure logic (+ the approval-card path for
|
|
320
278
|
// budget raise/clear), answered synchronously as an interaction response. NEVER reaches
|
|
@@ -57,6 +57,7 @@ import { threadParentChannelId } from "./discord-channel-info.js";
|
|
|
57
57
|
import { splitForDiscord } from "./discord-post.js";
|
|
58
58
|
import { discordFetch, type DiscordFetchDeps } from "./discord-rest.js";
|
|
59
59
|
import { RateLimitExhaustedError, type RetryPolicy } from "@open-everywhere/core/agent/adapter-api.js";
|
|
60
|
+
import { withEndsTurnFinalChannel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
60
61
|
import {
|
|
61
62
|
finalMessageAction,
|
|
62
63
|
nameDropPattern,
|
|
@@ -447,7 +448,7 @@ function isStopHand(emoji: string): boolean {
|
|
|
447
448
|
* 🔴 THE REACTOR GUARD HERE FAILS CLOSED, and that is a DIFFERENT posture from the feedback path
|
|
448
449
|
* below on purpose. The ingest path skips its bot-reactor drop when `botId` is unresolved, which
|
|
449
450
|
* costs a spurious telemetry row — harmless. Behind `cancel()` the same fail-OPEN would be a
|
|
450
|
-
* SELF-CANCEL: the bot's own ✋ (it places one on
|
|
451
|
+
* SELF-CANCEL: the bot's own ✋ (it places one on a job's asking message, and on a turn that calls a declared subagent) would cancel the very turn
|
|
451
452
|
* that placed it. So a stop requires a POSITIVELY RESOLVED, non-bot reactor: no `botId` ⇒ no stop.
|
|
452
453
|
*/
|
|
453
454
|
async function routeStopHandReaction(rx: DiscordRelayReaction, attachSession: AttachSessionForCancel): Promise<ReactionIngestOutcome> {
|
|
@@ -2940,7 +2941,9 @@ export async function maybeShedWarmSurface(state: DiscordGatewayState): Promise<
|
|
|
2940
2941
|
}
|
|
2941
2942
|
}
|
|
2942
2943
|
|
|
2943
|
-
|
|
2944
|
+
// OPE-2252 — `reply`/`no_reply` end the turn natively (`endsTurn`) and eve then emits no final message,
|
|
2945
|
+
// so the wrapper decides such a turn at `turn.completed` (core `channels/ends-turn-final.ts`).
|
|
2946
|
+
export default withEndsTurnFinalChannel(defineChannel<DiscordGatewayState, DiscordGatewayContext>({
|
|
2944
2947
|
kindHint: "discord",
|
|
2945
2948
|
// 🔴 QUEUE, NEVER STEER (OPE-412, OPE-1732). eve's default is `steer`, and a steer delivery
|
|
2946
2949
|
// carrying a message CANCELS the turn in flight — rolling session history back to the last
|
|
@@ -3253,4 +3256,4 @@ export default defineChannel<DiscordGatewayState, DiscordGatewayContext>({
|
|
|
3253
3256
|
}
|
|
3254
3257
|
},
|
|
3255
3258
|
},
|
|
3256
|
-
});
|
|
3259
|
+
}));
|
|
@@ -102,34 +102,6 @@ async function liveDiscordPost(origin: DiscordOrigin, text: string): Promise<str
|
|
|
102
102
|
return first;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
/**
|
|
106
|
-
* UNIFIED, WITH A COMPONENT PLANE (OPE-1720): the same single-window post as {@link liveDiscordPost},
|
|
107
|
-
* carrying a row of tappable actions under it. Discord is the SECOND surface to implement this
|
|
108
|
-
* member, and it may only do so because the tap lands: `discord-front.ts` classifies the `oe_input:`
|
|
109
|
-
* custom_id and `discord-approval-card.ts` settles it.
|
|
110
|
-
*
|
|
111
|
-
* 🔴 NO AUTO-THREAD BRANCH, DELIBERATELY. `liveDiscordPost` may open a thread off the mention and
|
|
112
|
-
* answer there. A governed approval card must not move: the people who can answer it are watching the
|
|
113
|
-
* conversation it was asked in, and burying the buttons in a freshly-created thread is how a card
|
|
114
|
-
* goes unanswered. The anchor still rides — the card hangs under the triggering message through the
|
|
115
|
-
* same `message_reference` a plain reply uses — so it is placed and never moved.
|
|
116
|
-
*
|
|
117
|
-
* 🔴 IT FALLS BACK TO TEXT RATHER THAN SPLITTING. Discord caps a message at 2000 characters and a
|
|
118
|
-
* button row cannot be split across messages: a row attached to the wrong part would sit under a
|
|
119
|
-
* fragment. So an over-cap card posts through the ordinary splitting path and loses the SHORTCUT,
|
|
120
|
-
* never the meaning — `DirectMessageAction`'s own rule, which the question bridge's text already
|
|
121
|
-
* satisfies (it prints the prompt, the options, and the line saying a reply may name one).
|
|
122
|
-
*/
|
|
123
|
-
async function liveDiscordPostWithActions(origin: DiscordOrigin, text: string, actions: readonly DirectMessageAction[]): Promise<string> {
|
|
124
|
-
const buttons = actions.map((a) => ({ customId: a.id, label: a.label, intent: a.intent }));
|
|
125
|
-
if (buttons.length === 0 || text.length > DISCORD_MESSAGE_MAX) return liveDiscordPost(origin, text);
|
|
126
|
-
try {
|
|
127
|
-
return await discordPostMessageWithButtons(origin.channelId, text, buttons, origin.messageId ? { replyToMessageId: origin.messageId } : undefined);
|
|
128
|
-
} catch {
|
|
129
|
-
throw new Error(`unified reply couldn't post to Discord — create-message with buttons failed on channel ${origin.channelId}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
105
|
/** EDIT (OPE-793): `PATCH /channels/{id}/messages/{mid}` through the same write seam the post path
|
|
134
106
|
* uses. Ownership was already decided upstream against the own-post ledger; this is transport only. */
|
|
135
107
|
async function editDiscordMessage(locus: EditLocus, messageId: string, text: string): Promise<void> {
|
|
@@ -199,12 +171,6 @@ export const discordPostArm: PostArm = {
|
|
|
199
171
|
},
|
|
200
172
|
dispatchReply: (target, text) => discordPostMessage((target as DiscordTarget).channelId, text),
|
|
201
173
|
dispatchUnified: (origin, text, deps: UnifiedReplyDeps) => (deps.postDiscord ?? liveDiscordPost)(origin as DiscordOrigin, text),
|
|
202
|
-
// OPE-1720: Discord declares the unified component plane because the tap route exists on it —
|
|
203
|
-
// `discord-front.ts` classifies the `oe_input:` custom_id and `discord-approval-card.ts` settles
|
|
204
|
-
// it. The injected `postDiscord` override is honoured VERBATIM and drops the buttons: a test that
|
|
205
|
-
// scripted a text-only post must not start receiving a card it never asked for.
|
|
206
|
-
dispatchUnifiedActions: (origin, text, actions, deps: UnifiedReplyDeps) =>
|
|
207
|
-
deps.postDiscord ? deps.postDiscord(origin as DiscordOrigin, text) : liveDiscordPostWithActions(origin as DiscordOrigin, text, actions),
|
|
208
174
|
describeRedriveTarget: (origin) => `discord channel ${(origin as DiscordOrigin).channelId}`,
|
|
209
175
|
redriveContainerId: (origin) => (origin as DiscordOrigin).channelId,
|
|
210
176
|
editMessage: editDiscordMessage,
|
|
@@ -213,13 +213,7 @@ export interface DiscordDmButton {
|
|
|
213
213
|
* 🔴 NO SPLIT, AND THAT IS DELIBERATE. `discordPostMessage` splits an over-cap reply into several
|
|
214
214
|
* messages; buttons cannot be split, and a row attached to the wrong part would sit under a
|
|
215
215
|
* fragment. So this refuses past Discord's 2000-character cap rather than truncating or fanning out,
|
|
216
|
-
* and
|
|
217
|
-
* (channels/discord-post-arm.ts), whose own caller falls back to the splitting path past the cap.
|
|
218
|
-
*
|
|
219
|
-
* `opts.replyToMessageId` hangs the message under the one it answers, through the same
|
|
220
|
-
* `message_reference` the plain post path uses and with the same `fail_if_not_exists: false` degrade
|
|
221
|
-
* when the trigger is gone (OPE-1720 — a card posted onto a unified origin must land where the
|
|
222
|
-
* conversation is looking, exactly as its prose sibling does). ABSENT ⇒ a plain channel post.
|
|
216
|
+
* and its caller (the DM-delivery arm, channels/discord-post-arm.ts) posts core-owned copy.
|
|
223
217
|
*
|
|
224
218
|
* The bot token rides only the header, like every call in this module.
|
|
225
219
|
*/
|
|
@@ -227,7 +221,6 @@ export async function discordPostMessageWithButtons(
|
|
|
227
221
|
channelId: string,
|
|
228
222
|
content: string,
|
|
229
223
|
buttons: readonly DiscordDmButton[],
|
|
230
|
-
opts?: { readonly replyToMessageId?: string },
|
|
231
224
|
): Promise<string> {
|
|
232
225
|
if (content.length > DISCORD_MESSAGE_MAX) {
|
|
233
226
|
throw markNotApplied(
|
|
@@ -239,7 +232,6 @@ export async function discordPostMessageWithButtons(
|
|
|
239
232
|
headers: botHeaders(botToken()),
|
|
240
233
|
body: JSON.stringify({
|
|
241
234
|
content,
|
|
242
|
-
...(opts?.replyToMessageId ? { message_reference: { message_id: opts.replyToMessageId, fail_if_not_exists: false } } : {}),
|
|
243
235
|
components: [
|
|
244
236
|
{
|
|
245
237
|
type: COMPONENT_ACTION_ROW,
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
authPrincipalId,
|
|
51
51
|
type ChatAuditPartition,
|
|
52
52
|
} from "@open-everywhere/core/agent/adapter-api.js";
|
|
53
|
+
import { withEndsTurnFinalChannel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
53
54
|
import { frontDiscordChannel, INTERNAL_DISCORD_ROUTE } from "./discord-front.js";
|
|
54
55
|
import { withDiscordEventsRoute } from "./discord-events.js";
|
|
55
56
|
|
|
@@ -241,4 +242,6 @@ const discordInteractionChannel = discordChannel({
|
|
|
241
242
|
// FRONTED (interactions) + the app-level WEBHOOK EVENTS route (discord-events.ts): a separate,
|
|
242
243
|
// additive URL that audits APPLICATION_AUTHORIZED / APPLICATION_DEAUTHORIZED so install/uninstall
|
|
243
244
|
// is observable. Observability only — it gates nothing and never reaches a session.
|
|
244
|
-
|
|
245
|
+
// OPE-2252 — `reply`/`no_reply` end the turn natively (`endsTurn`) and eve then emits no final message,
|
|
246
|
+
// so the wrapper decides such a turn at `turn.completed` (core `channels/ends-turn-final.ts`).
|
|
247
|
+
export default withEndsTurnFinalChannel(withDiscordEventsRoute(frontDiscordChannel(discordInteractionChannel)));
|
|
@@ -29,7 +29,9 @@ import {
|
|
|
29
29
|
bindFrom,
|
|
30
30
|
MODEL_AB_RATING_MAX,
|
|
31
31
|
MODEL_AB_RATING_MIN,
|
|
32
|
-
|
|
32
|
+
agentDisplayName,
|
|
33
|
+
oeCommandName,
|
|
34
|
+
oeSlash,
|
|
33
35
|
runOeCommand,
|
|
34
36
|
type AttachSessionForCancel,
|
|
35
37
|
type BindableContext,
|
|
@@ -122,7 +124,7 @@ export function parseDiscordOeCommandOptions(rawOptions: unknown): OeCommand {
|
|
|
122
124
|
if (score === undefined || !Number.isInteger(score) || score < MODEL_AB_RATING_MIN || score > MODEL_AB_RATING_MAX) {
|
|
123
125
|
return {
|
|
124
126
|
kind: "error",
|
|
125
|
-
message: `"
|
|
127
|
+
message: `"${oeSlash("rate")}" needs a whole "score" from ${MODEL_AB_RATING_MIN} to ${MODEL_AB_RATING_MAX} (got ${JSON.stringify(scoreOpt?.value)}).`,
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
130
|
return { kind: "rate", score };
|
|
@@ -138,14 +140,14 @@ export function parseDiscordOeCommandOptions(rawOptions: unknown): OeCommand {
|
|
|
138
140
|
if (usd === undefined || usd <= 0) {
|
|
139
141
|
return {
|
|
140
142
|
kind: "error",
|
|
141
|
-
message: `"
|
|
143
|
+
message: `"${oeSlash("budget raise")}" needs a positive "usd" amount (got ${JSON.stringify(usdOpt?.value)}).`,
|
|
142
144
|
};
|
|
143
145
|
}
|
|
144
146
|
return { kind: "budget-raise", usd };
|
|
145
147
|
}
|
|
146
148
|
return { kind: "error", message: `Unknown "budget" subcommand "${subName}".` };
|
|
147
149
|
}
|
|
148
|
-
return { kind: "error", message: `Unknown "
|
|
150
|
+
return { kind: "error", message: `Unknown "${oeSlash()}" subcommand "${name ?? "?"}" — try "${oeSlash("help")}".` };
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
interface ParsedOeInvocation {
|
|
@@ -164,7 +166,7 @@ interface ParsedOeInvocation {
|
|
|
164
166
|
export function parseOeCommandInteraction(raw: unknown): ParsedOeInvocation | { readonly error: string } | null {
|
|
165
167
|
if (!isObject(raw) || raw["type"] !== 2) return null;
|
|
166
168
|
const data = isObject(raw["data"]) ? raw["data"] : undefined;
|
|
167
|
-
if (!data || data["name"] !==
|
|
169
|
+
if (!data || data["name"] !== oeCommandName()) return null;
|
|
168
170
|
|
|
169
171
|
const member = isObject(raw["member"]) ? raw["member"] : undefined;
|
|
170
172
|
const memberUser = member && isObject(member["user"]) ? member["user"] : undefined;
|
|
@@ -214,7 +216,7 @@ export async function handleDiscordOeCommandInteraction(
|
|
|
214
216
|
// (e.g. "propose from a DM needs an explicit scope") — surface it verbatim; anything else
|
|
215
217
|
// (a genuinely unexpected store/network failure) gets a generic fail-soft note. Either way,
|
|
216
218
|
// never throw out of this function.
|
|
217
|
-
console.error(`[open-everywhere:discord-commands] "
|
|
219
|
+
console.error(`[open-everywhere:discord-commands] "${oeSlash()}" command failed softly: ${String(err)}`);
|
|
218
220
|
return ephemeral(`Couldn't complete that: ${err instanceof Error ? err.message : String(err)}`);
|
|
219
221
|
}
|
|
220
222
|
}
|
|
@@ -228,6 +230,16 @@ const OPTION_INTEGER = 4;
|
|
|
228
230
|
const OPTION_NUMBER = 10;
|
|
229
231
|
const CHAT_INPUT = 1;
|
|
230
232
|
|
|
233
|
+
/** Discord's limit on a command description. A longer one fails the registration call. */
|
|
234
|
+
export const DISCORD_COMMAND_DESCRIPTION_MAX = 100;
|
|
235
|
+
|
|
236
|
+
/** The command's description, naming the deployment — or, when the name would push it past
|
|
237
|
+
* Discord's limit, the same sentence without the name. */
|
|
238
|
+
function discordCommandDescription(name: string): string {
|
|
239
|
+
const named = `${name} control-plane commands (status, budget) — no model turn.`;
|
|
240
|
+
return named.length <= DISCORD_COMMAND_DESCRIPTION_MAX ? named : "Control-plane commands (status, budget) — no model turn.";
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
/**
|
|
232
244
|
* The Discord ApplicationCommand body for "/oe" — pure, no network, so the registration
|
|
233
245
|
* script (scripts/register-discord-commands.ts) and its test share exactly one definition.
|
|
@@ -236,9 +248,11 @@ const CHAT_INPUT = 1;
|
|
|
236
248
|
*/
|
|
237
249
|
export function buildOeDiscordCommandDefinition(): Record<string, unknown> {
|
|
238
250
|
return {
|
|
239
|
-
|
|
251
|
+
// Both from the `identity.*` settings (OPE-2334): the name the router below matches on, and the
|
|
252
|
+
// deployment's configured name in the description.
|
|
253
|
+
name: oeCommandName(),
|
|
240
254
|
type: CHAT_INPUT,
|
|
241
|
-
description:
|
|
255
|
+
description: discordCommandDescription(agentDisplayName()),
|
|
242
256
|
options: [
|
|
243
257
|
{
|
|
244
258
|
name: "budget",
|
|
@@ -285,7 +299,7 @@ export function buildOeDiscordCommandDefinition(): Record<string, unknown> {
|
|
|
285
299
|
},
|
|
286
300
|
],
|
|
287
301
|
},
|
|
288
|
-
{ name: "help", description:
|
|
302
|
+
{ name: "help", description: `List the ${oeSlash()} commands.`, type: SUB_COMMAND },
|
|
289
303
|
],
|
|
290
304
|
};
|
|
291
305
|
}
|
package/agent/contributions.ts
CHANGED
|
@@ -25,7 +25,7 @@ export const CONTRIBUTIONS = {
|
|
|
25
25
|
package: "@open-everywhere/provider-discord",
|
|
26
26
|
register: "agent/register.js#registerDiscordProvider",
|
|
27
27
|
contributions: [
|
|
28
|
-
{ kind: "channel", name: "discord", module: "agent/channels/discord.js", summary: "The Discord surface. Wires Discord's HTTP Interactions (slash commands) → an
|
|
28
|
+
{ kind: "channel", name: "discord", module: "agent/channels/discord.js", summary: "The Discord surface. Wires Discord's HTTP Interactions (slash commands) → an agent session, and — the point of the \"Discord-binding\" slice — populates the session auth context with the REAL…" },
|
|
29
29
|
{ kind: "channel", name: "discord-gateway", module: "agent/channels/discord-gateway.js", summary: "The Discord GATEWAY ingestion surface — Milestone 2 of the Discord Gateway feature." },
|
|
30
30
|
],
|
|
31
31
|
} as const;
|
package/agent/surface-parity.ts
CHANGED
|
@@ -13,12 +13,6 @@ export const DISCORD_SURFACE_PARITY: SurfaceParityDeclaration = {
|
|
|
13
13
|
"capability-manifest": "supported",
|
|
14
14
|
"post-arm": "supported",
|
|
15
15
|
"wake-origin-codec": "supported",
|
|
16
|
-
// The arm renders a Discord action row (`discord-post-arm.ts`), the front routes the tap back
|
|
17
|
-
// (`discord-front.ts` classifies the `oe_input:` custom_id, `discord-approval-card.ts` settles it),
|
|
18
|
-
// and the settle DELIVERS through core's `approvalCardRespondVia` — all three halves, as the row
|
|
19
|
-
// requires. The third is not a formality: this front bound its own `args.from(bareToken)` until
|
|
20
|
-
// OPE-1863, and that address is channel-local, so every tap threw and the approval stayed parked.
|
|
21
|
-
"unified-action-plane": "supported",
|
|
22
16
|
// `messageId` is the `message_reference` anchor and sits outside `threadRef`, which keys on the
|
|
23
17
|
// channel id alone — Slack's shape exactly. Both members are implemented; `autoThreadName` stays
|
|
24
18
|
// OUT of the axis because opening a thread MOVES a post rather than placing one.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discord-card.d.ts","sourceRoot":"","sources":["../../../agent/approvals/discord-card.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAGnF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,iBAAiB,CAAC;AAEzD,uHAAuH;AACvH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAYzC;4FAC4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAClD;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,QAAQ,GAAG,iBAAiB,GAAG,YAAY,CAAC;AAM9F;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,uBAAuB,GAAG,MAAM,CAQ7G;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,uBAAuB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAcpI;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"discord-card.d.ts","sourceRoot":"","sources":["../../../agent/approvals/discord-card.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAGnF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,iBAAiB,CAAC;AAEzD,uHAAuH;AACvH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAYzC;4FAC4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAClD;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,QAAQ,GAAG,iBAAiB,GAAG,YAAY,CAAC;AAM9F;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,uBAAuB,GAAG,MAAM,CAQ7G;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,uBAAuB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAcpI;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CA+B7G;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAkB1I;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAalI;AAKD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO,GAAG,OAAO,CAOlE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGvF"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// THREE renders live here, and only the first two are a card: the PENDING card (Approve/Reject), the
|
|
18
18
|
// DECIDED card (no buttons), and the JUDGE-LIFT NOTICE (OPE-1103) — a review surface for an action
|
|
19
19
|
// that already ran, carrying the one "This should not have run" button and never a decision one.
|
|
20
|
-
import { grantButtonLabel, sessionGrantKind } from "@open-everywhere/core/agent/adapter-api.js";
|
|
20
|
+
import { getSettings, grantButtonLabel, pendingCardExpiryClause, sessionGrantKind } from "@open-everywhere/core/agent/adapter-api.js";
|
|
21
21
|
/**
|
|
22
22
|
* Every approval button custom_id this feature owns starts with this. Deliberately distinct
|
|
23
23
|
* from BOTH the Slack action_id prefix ("oe_approval:" — a different wire format, keeping
|
|
@@ -34,7 +34,7 @@ export const DISCORD_CUSTOM_ID_MAX = 100;
|
|
|
34
34
|
// the Slack prefix.
|
|
35
35
|
const COMPONENT_ACTION_ROW = 1;
|
|
36
36
|
const COMPONENT_BUTTON = 2;
|
|
37
|
-
const BUTTON_STYLE_SECONDARY = 2; // grey — Approve for
|
|
37
|
+
const BUTTON_STYLE_SECONDARY = 2; // grey — Approve for this routine (the grantable-kind middle button, OPE-53/OPE-11)
|
|
38
38
|
const BUTTON_STYLE_SUCCESS = 3; // green — Approve (Slack's `style: "primary"` analogue)
|
|
39
39
|
const BUTTON_STYLE_DANGER = 4; // red — Reject (Slack's `style: "danger"` analogue)
|
|
40
40
|
/** The closed verb set as a VALUE — one list, so build and parse cannot drift into a button nothing
|
|
@@ -94,7 +94,7 @@ function cardBody(row, summaryLines, footer) {
|
|
|
94
94
|
* the org rides the custom_id on Discord). Mirrors card.ts's pendingCardBlocks.
|
|
95
95
|
*/
|
|
96
96
|
export function pendingDiscordCard(row, summaryLines) {
|
|
97
|
-
const content = cardBody(row, summaryLines, `-# Requested by <@${row.requestedBy}> ·
|
|
97
|
+
const content = cardBody(row, summaryLines, `-# Requested by <@${row.requestedBy}> · ${pendingCardExpiryClause(row.expiresAt)}`);
|
|
98
98
|
const buttons = [
|
|
99
99
|
{
|
|
100
100
|
type: COMPONENT_BUTTON,
|
|
@@ -103,18 +103,17 @@ export function pendingDiscordCard(row, summaryLines) {
|
|
|
103
103
|
custom_id: buildDiscordApprovalCustomId(row, "approve"),
|
|
104
104
|
},
|
|
105
105
|
];
|
|
106
|
-
// OPE-53: a
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
106
|
+
// OPE-53, OPE-2363: only a ROUTINE's card gets the middle button — publish this one AND skip the card
|
|
107
|
+
// for the same kind on later fires of THIS routine. sessionGrantKind() (core approvals/session-grant.ts)
|
|
108
|
+
// decides the KIND (`pr.open` alone), and grantButtonLabel() decides the reach: undefined for every
|
|
109
|
+
// card in a conversation, which offers Approve and Reject only. The label and the presence both come
|
|
110
|
+
// from core, so Slack and Discord can never disagree.
|
|
111
|
+
const grantLabel = sessionGrantKind(row) !== null ? grantButtonLabel(row, getSettings().approvals.routineGrantTtlHours) : undefined;
|
|
112
|
+
if (grantLabel !== undefined) {
|
|
112
113
|
buttons.push({
|
|
113
114
|
type: COMPONENT_BUTTON,
|
|
114
115
|
style: BUTTON_STYLE_SECONDARY,
|
|
115
|
-
|
|
116
|
-
// the click grants for this session or standing-for-this-routine (a fire's card).
|
|
117
|
-
label: grantButtonLabel(row),
|
|
116
|
+
label: grantLabel,
|
|
118
117
|
custom_id: buildDiscordApprovalCustomId(row, "approve_session"),
|
|
119
118
|
});
|
|
120
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discord-card.js","sourceRoot":"","sources":["../../../agent/approvals/discord-card.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,uFAAuF;AACvF,yFAAyF;AACzF,yFAAyF;AACzF,4EAA4E;AAC5E,EAAE;AACF,0FAA0F;AAC1F,2FAA2F;AAC3F,kFAAkF;AAClF,8CAA8C;AAC9C,2FAA2F;AAC3F,2FAA2F;AAC3F,wFAAwF;AACxF,yFAAyF;AACzF,+EAA+E;AAC/E,EAAE;AACF,qGAAqG;AACrG,mGAAmG;AACnG,iGAAiG;AAGjG,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"discord-card.js","sourceRoot":"","sources":["../../../agent/approvals/discord-card.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,7 +67,7 @@ export interface DiscordApprovalDeps {
|
|
|
67
67
|
*
|
|
68
68
|
* 🔴 TWO LEGS MOVE, AND THE SECOND ONE EXECUTES. The decision FEEDBACK above, and the core's
|
|
69
69
|
* OPE-251 session-grant RELEASE (OPE-1709), which this file forwards into `resolveApprovalClick`
|
|
70
|
-
* below — an "Approve for
|
|
70
|
+
* below — an "Approve for this routine" click decides and runs up to `MAX_SESSION_GRANT_RELEASES`
|
|
71
71
|
* sibling cards, so it is the heavier of the two. Do not read this hook as feedback plumbing.
|
|
72
72
|
*
|
|
73
73
|
* 🔴 WHAT DOES NOT MOVE IS THE RESPONSE BODY. `resolveApprovalClick` decides it, so that CALL
|