@open-everywhere/provider-slack 0.7.0 → 0.8.1
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/agent/access/slack-attachment-fetch.ts +2 -1
- package/agent/app-manifest.ts +18 -2
- package/agent/channels/slack-agent-session.ts +338 -0
- package/agent/channels/slack-attachments.ts +20 -11
- package/agent/channels/slack-commands.ts +172 -12
- package/agent/channels/slack-context.ts +0 -5
- package/agent/channels/slack-events.ts +161 -33
- package/agent/channels/slack-follow-ups.ts +148 -0
- package/agent/channels/slack-front.ts +232 -26
- package/agent/channels/slack-home.ts +9 -3
- package/agent/channels/slack-identity.ts +43 -2
- package/agent/channels/slack-memory-canvas.ts +192 -51
- package/agent/channels/slack-origin-codec.ts +9 -0
- package/agent/channels/slack-post-arm.ts +68 -11
- package/agent/channels/slack-read.ts +27 -2
- package/agent/channels/slack-reply-blocks.ts +180 -0
- package/agent/channels/slack-reply-feedback.ts +75 -0
- package/agent/channels/slack-shortcut.ts +1 -1
- package/agent/channels/slack-voice-clips.ts +82 -0
- package/agent/channels/slack-wake-backscroll.ts +0 -5
- package/agent/channels/slack.ts +74 -13
- package/agent/docs-generated.ts +1299 -2
- package/agent/ops.ts +14 -21
- package/agent/register.ts +13 -0
- package/agent/setup/credentials.ts +17 -0
- package/agent/setup/page.ts +6 -3
- package/agent/surface-parity.ts +2 -0
- package/dist/agent/access/slack-attachment-fetch.d.ts.map +1 -1
- package/dist/agent/access/slack-attachment-fetch.js.map +1 -1
- package/dist/agent/app-manifest.d.ts.map +1 -1
- package/dist/agent/app-manifest.js +18 -2
- package/dist/agent/app-manifest.js.map +1 -1
- package/dist/agent/channels/slack-agent-session.d.ts +85 -0
- package/dist/agent/channels/slack-agent-session.d.ts.map +1 -0
- package/dist/agent/channels/slack-agent-session.js +282 -0
- package/dist/agent/channels/slack-agent-session.js.map +1 -0
- package/dist/agent/channels/slack-attachments.d.ts +11 -0
- package/dist/agent/channels/slack-attachments.d.ts.map +1 -1
- package/dist/agent/channels/slack-attachments.js +21 -14
- package/dist/agent/channels/slack-attachments.js.map +1 -1
- package/dist/agent/channels/slack-commands.d.ts +113 -10
- package/dist/agent/channels/slack-commands.d.ts.map +1 -1
- package/dist/agent/channels/slack-commands.js +146 -13
- package/dist/agent/channels/slack-commands.js.map +1 -1
- package/dist/agent/channels/slack-context.d.ts +1 -2
- package/dist/agent/channels/slack-context.d.ts.map +1 -1
- package/dist/agent/channels/slack-context.js +0 -4
- package/dist/agent/channels/slack-context.js.map +1 -1
- package/dist/agent/channels/slack-events.d.ts +53 -100
- package/dist/agent/channels/slack-events.d.ts.map +1 -1
- package/dist/agent/channels/slack-events.js +142 -26
- package/dist/agent/channels/slack-events.js.map +1 -1
- package/dist/agent/channels/slack-follow-ups.d.ts +44 -0
- package/dist/agent/channels/slack-follow-ups.d.ts.map +1 -0
- package/dist/agent/channels/slack-follow-ups.js +129 -0
- package/dist/agent/channels/slack-follow-ups.js.map +1 -0
- package/dist/agent/channels/slack-front.d.ts +28 -45
- package/dist/agent/channels/slack-front.d.ts.map +1 -1
- package/dist/agent/channels/slack-front.js +207 -22
- package/dist/agent/channels/slack-front.js.map +1 -1
- package/dist/agent/channels/slack-home.d.ts.map +1 -1
- package/dist/agent/channels/slack-home.js +10 -3
- package/dist/agent/channels/slack-home.js.map +1 -1
- package/dist/agent/channels/slack-identity.d.ts +33 -21
- package/dist/agent/channels/slack-identity.d.ts.map +1 -1
- package/dist/agent/channels/slack-identity.js +36 -2
- package/dist/agent/channels/slack-identity.js.map +1 -1
- package/dist/agent/channels/slack-memory-canvas.d.ts +21 -5
- package/dist/agent/channels/slack-memory-canvas.d.ts.map +1 -1
- package/dist/agent/channels/slack-memory-canvas.js +162 -52
- package/dist/agent/channels/slack-memory-canvas.js.map +1 -1
- package/dist/agent/channels/slack-origin-codec.d.ts.map +1 -1
- package/dist/agent/channels/slack-origin-codec.js +10 -0
- package/dist/agent/channels/slack-origin-codec.js.map +1 -1
- package/dist/agent/channels/slack-post-arm.d.ts +21 -1
- package/dist/agent/channels/slack-post-arm.d.ts.map +1 -1
- package/dist/agent/channels/slack-post-arm.js +57 -10
- package/dist/agent/channels/slack-post-arm.js.map +1 -1
- package/dist/agent/channels/slack-read.d.ts +11 -1
- package/dist/agent/channels/slack-read.d.ts.map +1 -1
- package/dist/agent/channels/slack-read.js +27 -3
- package/dist/agent/channels/slack-read.js.map +1 -1
- package/dist/agent/channels/slack-reply-blocks.d.ts +29 -0
- package/dist/agent/channels/slack-reply-blocks.d.ts.map +1 -0
- package/dist/agent/channels/slack-reply-blocks.js +172 -0
- package/dist/agent/channels/slack-reply-blocks.js.map +1 -0
- package/dist/agent/channels/slack-reply-feedback.d.ts +23 -0
- package/dist/agent/channels/slack-reply-feedback.d.ts.map +1 -0
- package/dist/agent/channels/slack-reply-feedback.js +63 -0
- package/dist/agent/channels/slack-reply-feedback.js.map +1 -0
- package/dist/agent/channels/slack-shortcut.d.ts +10 -0
- package/dist/agent/channels/slack-shortcut.d.ts.map +1 -1
- package/dist/agent/channels/slack-shortcut.js +1 -1
- package/dist/agent/channels/slack-shortcut.js.map +1 -1
- package/dist/agent/channels/slack-voice-clips.d.ts +16 -0
- package/dist/agent/channels/slack-voice-clips.d.ts.map +1 -0
- package/dist/agent/channels/slack-voice-clips.js +80 -0
- package/dist/agent/channels/slack-voice-clips.js.map +1 -0
- package/dist/agent/channels/slack-wake-backscroll.d.ts +0 -2
- package/dist/agent/channels/slack-wake-backscroll.d.ts.map +1 -1
- package/dist/agent/channels/slack-wake-backscroll.js +0 -4
- package/dist/agent/channels/slack-wake-backscroll.js.map +1 -1
- package/dist/agent/channels/slack.d.ts +22 -1
- package/dist/agent/channels/slack.d.ts.map +1 -1
- package/dist/agent/channels/slack.js +63 -13
- package/dist/agent/channels/slack.js.map +1 -1
- package/dist/agent/docs-generated.d.ts.map +1 -1
- package/dist/agent/docs-generated.js +1299 -2
- package/dist/agent/docs-generated.js.map +1 -1
- package/dist/agent/ops.d.ts +1 -1
- package/dist/agent/ops.d.ts.map +1 -1
- package/dist/agent/ops.js +15 -21
- package/dist/agent/ops.js.map +1 -1
- package/dist/agent/register.d.ts.map +1 -1
- package/dist/agent/register.js +12 -1
- package/dist/agent/register.js.map +1 -1
- package/dist/agent/setup/credentials.d.ts +12 -0
- package/dist/agent/setup/credentials.d.ts.map +1 -1
- package/dist/agent/setup/credentials.js +17 -0
- package/dist/agent/setup/credentials.js.map +1 -1
- package/dist/agent/setup/page.d.ts.map +1 -1
- package/dist/agent/setup/page.js +6 -3
- package/dist/agent/setup/page.js.map +1 -1
- package/dist/agent/surface-parity.d.ts.map +1 -1
- package/dist/agent/surface-parity.js +2 -0
- package/dist/agent/surface-parity.js.map +1 -1
- package/package.json +31 -3
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// THE SLACK AGENT SESSION (OPE-2519) — Slack's native Stop, the session title, and the "needs you" state.
|
|
2
|
+
//
|
|
3
|
+
// Slack's agent experience shows each conversation with the app as an AGENT SESSION, keyed by the
|
|
4
|
+
// channel and the thread root (docs.slack.dev/ai/agent-sessions, re-read 2026-10-14). A session has a
|
|
5
|
+
// lifecycle status (`processing`, `active`, `suspended`, `closed`) and a title. This file owns the three
|
|
6
|
+
// things OE does with it:
|
|
7
|
+
//
|
|
8
|
+
// • STOP. While a session is `processing`, Slack shows a Stop button, but only to an app that
|
|
9
|
+
// subscribes to `agent_session_stopped`. A press arrives as that event. It takes the SAME cancel
|
|
10
|
+
// path as the ✋ reaction and `/oe stop` (`cancelAnchoredTurn`): no session is minted, no model is
|
|
11
|
+
// called, nothing is spent. Slack does not move the status itself, so the route sets `active`.
|
|
12
|
+
// • TITLE. A top-level message the person sends in their DM with the app opens a new conversation,
|
|
13
|
+
// and gets a short title from the words the person TYPED (`authoredText`). The lines of a
|
|
14
|
+
// forwarded or quoted message are another conversation's words, so they never title it. The host
|
|
15
|
+
// derives the title in code: no model writes it. Slack applies a `title` only when the call
|
|
16
|
+
// CREATES the session, so a title the person typed over ours is never overwritten. A thread reply,
|
|
17
|
+
// a shortcut and a group DM get no title.
|
|
18
|
+
// • STATE. Core's turn-anchor hook sets the session `processing` when a turn starts, and at the park
|
|
19
|
+
// `suspended` when the turn reported through `update_status` that it waits on a person
|
|
20
|
+
// (`needs_input` or `blocked`), else `active` (channels/surface-session-state.ts). This file
|
|
21
|
+
// registers the Slack setter: it names the session from the delivery's auth attributes. The
|
|
22
|
+
// person's next message sets `processing`, then parks `active` unless it reports again that it
|
|
23
|
+
// waits on the person. A job's result wake names no session, so its turn sets no state.
|
|
24
|
+
// • THE WORKING SIGNS (OPE-2629). The same setter shows the 👀 and the status line on the session's
|
|
25
|
+
// root while a turn works, and clears both at the park. See `setSlackDmWorkingSigns`.
|
|
26
|
+
// • A FORWARDED JOB's WAIT (OPE-2536). A job the DM seat forwards to the work session can end waiting
|
|
27
|
+
// on the person: it asked a question in the room (`ask_room`), or it needs their own sign-in. The
|
|
28
|
+
// work session has no `update_status`, so the host sets the session `suspended` at the job's
|
|
29
|
+
// settlement (core channels/work-surface-wait.ts). This file registers the PIN for that: the
|
|
30
|
+
// session's id and the three attributes that name it again, kept on the job.
|
|
31
|
+
//
|
|
32
|
+
// WHICH SESSIONS. Only the person's 1:1 DM with the app (the Messages tab and the side panel), where
|
|
33
|
+
// Slack lists the person's agent sessions. The DM door stamps the session's thread root on the wake's
|
|
34
|
+
// auth (`agent_session_ts`): a top-level message opens its own session, keyed by its own `ts`, and a
|
|
35
|
+
// thread reply belongs to its thread's session. A channel thread gets no state from OE.
|
|
36
|
+
//
|
|
37
|
+
// Every call here is best-effort. A workspace without Slack's agent features answers an error, which
|
|
38
|
+
// is logged and changes nothing else.
|
|
39
|
+
import { cancelAnchoredTurn, resolvePartition, STATUS_REACTION, swallowRoomOp, turnAnchorKey, turnChannelKey, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
40
|
+
import { bindableForSlackIds, slackForm } from "./slack-events.js";
|
|
41
|
+
/** Slack's event for a press of the session's Stop button. */
|
|
42
|
+
export const AGENT_SESSION_STOPPED_EVENT = "agent_session_stopped";
|
|
43
|
+
/** The longest title OE sets. Slack accepts 200 characters; a session list shows far fewer. */
|
|
44
|
+
export const SLACK_SESSION_TITLE_MAX = 60;
|
|
45
|
+
/** The auth attribute the DM door stamps with the session's thread root (slack-front.ts `scheduleDmWake`). */
|
|
46
|
+
const AGENT_SESSION_TS_ATTRIBUTE = "agent_session_ts";
|
|
47
|
+
/**
|
|
48
|
+
* The mark a PINNED bag carries (OPE-2629). A forwarded job's settlement sets the session's state from
|
|
49
|
+
* its pin, not from a turn, so the setter must not touch the turn's working signs for it: a turn of the
|
|
50
|
+
* same session can be running at that moment, and its 👀 must stay until its own park.
|
|
51
|
+
*/
|
|
52
|
+
const SESSION_PIN_ATTRIBUTE = "oe_session_pin";
|
|
53
|
+
// --- the title ------------------------------------------------------------------------------------
|
|
54
|
+
/** Slack's three escaped characters, decoded. */
|
|
55
|
+
function decodeSlackEntities(text) {
|
|
56
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The session title for a conversation that opens with `text`, or undefined when nothing readable is
|
|
60
|
+
* left. Pure. It keeps the first non-empty line, renders a link as its label, a channel as `#name`, and
|
|
61
|
+
* drops a member or group mention (no raw id reaches the title). It strips `*`, `~` and backticks, and
|
|
62
|
+
* keeps `_`, so an identifier such as `oe_config` stays intact. A long line is cut at a word and ends
|
|
63
|
+
* with `…`.
|
|
64
|
+
*/
|
|
65
|
+
export function slackSessionTitle(text) {
|
|
66
|
+
if (typeof text !== "string")
|
|
67
|
+
return undefined;
|
|
68
|
+
const rendered = text
|
|
69
|
+
// A member mention, with or without a label: dropped.
|
|
70
|
+
.replace(/<@[A-Z0-9]+(?:\|[^>]*)?>/g, " ")
|
|
71
|
+
// A channel mention: its name when Slack sent one, else dropped.
|
|
72
|
+
.replace(/<#[A-Z0-9]+\|([^>]+)>/g, "#$1")
|
|
73
|
+
.replace(/<#[A-Z0-9]+>/g, " ")
|
|
74
|
+
// A special or group mention (`<!here>`, `<!subteam^S1|@team>`): dropped.
|
|
75
|
+
.replace(/<![^>]*>/g, " ")
|
|
76
|
+
// A link: its label, else the address.
|
|
77
|
+
.replace(/<[^>|]+\|([^>]+)>/g, "$1")
|
|
78
|
+
.replace(/<([^>]+)>/g, "$1");
|
|
79
|
+
const line = decodeSlackEntities(rendered)
|
|
80
|
+
.split(/\r?\n/)
|
|
81
|
+
.map((l) => l.replace(/[*~`]+/g, "").replace(/\s+/g, " ").trim())
|
|
82
|
+
.find((l) => l.length > 0);
|
|
83
|
+
if (line === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (line.length <= SLACK_SESSION_TITLE_MAX)
|
|
86
|
+
return line;
|
|
87
|
+
const head = line.slice(0, SLACK_SESSION_TITLE_MAX - 1);
|
|
88
|
+
const cut = head.lastIndexOf(" ");
|
|
89
|
+
return `${(cut >= SLACK_SESSION_TITLE_MAX / 2 ? head.slice(0, cut) : head).trimEnd()}…`;
|
|
90
|
+
}
|
|
91
|
+
/** The form body of {@link SlackSessionStatusCall}. Pure. */
|
|
92
|
+
export function slackSessionStatusForm(call) {
|
|
93
|
+
const body = new URLSearchParams({ channel_id: call.channelId, thread_ts: call.threadTs, status: call.status });
|
|
94
|
+
if (call.title !== undefined && call.title.length > 0)
|
|
95
|
+
body.set("title", call.title);
|
|
96
|
+
return body.toString();
|
|
97
|
+
}
|
|
98
|
+
/** Set a session's status (and, on creation, its title). True when Slack answered `ok`. Never throws. */
|
|
99
|
+
export async function setSlackSessionStatus(call, form = slackForm) {
|
|
100
|
+
try {
|
|
101
|
+
const res = await form("agents.sessions.setStatus", slackSessionStatusForm(call));
|
|
102
|
+
if (res?.ok === true)
|
|
103
|
+
return true;
|
|
104
|
+
console.warn(`[open-everywhere:slack-session] agents.sessions.setStatus ${call.status} was not applied (${call.channelId}/${call.threadTs}): ${String(res?.error ?? "no answer")}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.warn(`[open-everywhere:slack-session] agents.sessions.setStatus ${call.status} failed (${call.channelId}/${call.threadTs}): ${String(err)}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The session a delivery's auth attributes name, or undefined for none. Only a delivery the DM door
|
|
114
|
+
* stamped (`agent_session_ts`) names one: a shortcut, a group DM and every channel carry no stamp, so
|
|
115
|
+
* they never set a session's state, even on a threaded message. Pure.
|
|
116
|
+
*/
|
|
117
|
+
export function slackAgentSessionOf(attributes) {
|
|
118
|
+
if (attributes.channel_type !== "im")
|
|
119
|
+
return undefined;
|
|
120
|
+
const channelId = attributes.channel_id;
|
|
121
|
+
const stamped = attributes[AGENT_SESSION_TS_ATTRIBUTE];
|
|
122
|
+
if (typeof channelId !== "string" || channelId.length === 0 || typeof stamped !== "string" || stamped.length === 0)
|
|
123
|
+
return undefined;
|
|
124
|
+
return { channelId, threadTs: stamped };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The Slack pinner (OPE-2536): the session a delivery names, as the setter's own id and the three
|
|
128
|
+
* attributes that name it again. A forwarded job keeps this, so its settlement can set the session
|
|
129
|
+
* `suspended` long after the delivery's auth is gone. Ids only, never text. Pure.
|
|
130
|
+
*/
|
|
131
|
+
export const slackSurfaceSessionPin = (attributes) => {
|
|
132
|
+
const session = slackAgentSessionOf(attributes);
|
|
133
|
+
if (!session)
|
|
134
|
+
return undefined;
|
|
135
|
+
return {
|
|
136
|
+
id: `${session.channelId}:${session.threadTs}`,
|
|
137
|
+
attributes: { channel_id: session.channelId, channel_type: "im", [AGENT_SESSION_TS_ATTRIBUTE]: session.threadTs, [SESSION_PIN_ATTRIBUTE]: "1" },
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
// --- the working signs ------------------------------------------------------------------------------
|
|
141
|
+
//
|
|
142
|
+
// 🔴 THE DM SEAT'S 👀 AND STATUS LINE LIVE HERE (OPE-2629). A 1:1 DM runs on the `unified` channel, so
|
|
143
|
+
// none of the Slack channel's own handlers reach it: `onInbound` (the 👀) and the `turn.started`
|
|
144
|
+
// renderer (the status line) serve only eve's native mention door. Before this, a person who DMed
|
|
145
|
+
// the app saw neither during the turn. Core's turn-anchor hook calls this setter at every DM turn's
|
|
146
|
+
// start (`processing`) and park (`active` or `suspended`), so the signs ride the same two calls:
|
|
147
|
+
// • 👀 on the session's thread root — the message itself when it is top-level, the same root the
|
|
148
|
+
// mention door's 👀 uses for a threaded one. Added at `processing`, removed at the park.
|
|
149
|
+
// • Slack's assistant status (`assistant.threads.setStatus`) on that root: `Working...` at
|
|
150
|
+
// `processing`, cleared at the park. The flat DM reply does not clear it, so the park must.
|
|
151
|
+
// A forwarded job's settlement (OPE-2536) also calls this setter, from the job's PIN. A turn of the
|
|
152
|
+
// same session can run at that moment, so a pinned bag (`SESSION_PIN_ATTRIBUTE`) moves the session
|
|
153
|
+
// state only and leaves the signs to the turn's own park.
|
|
154
|
+
// A turn that fails for good emits `session.failed` and never parks, so its signs stay: the 👀 until
|
|
155
|
+
// the session's next turn parks, and the status line until Slack expires it.
|
|
156
|
+
/** The status line a DM turn shows while it works — the same words the mention door's `turn.started` shows. */
|
|
157
|
+
export const SLACK_DM_WORKING_STATUS = "Working...";
|
|
158
|
+
/** The 👀 the bot sets while it works (core status/status.ts `STATUS_REACTION.working`). */
|
|
159
|
+
const WORKING_REACTION = STATUS_REACTION.working;
|
|
160
|
+
/**
|
|
161
|
+
* What an operator does when one sign call fails with a setup error (`SIGN_SETUP_ERRORS`). The sign
|
|
162
|
+
* needs a scope or the agent view, and both come from the app manifest, so the fix is the manifest
|
|
163
|
+
* paste. Logged once per method and error, so a workspace without the feature does not log it on
|
|
164
|
+
* every turn.
|
|
165
|
+
*/
|
|
166
|
+
const SIGN_SETUP_FIX = {
|
|
167
|
+
"reactions.add": "the 👀 needs the reactions:write bot scope",
|
|
168
|
+
"reactions.remove": "the 👀 needs the reactions:write bot scope",
|
|
169
|
+
"assistant.threads.setStatus": "the DM status line needs the assistant:write bot scope and the agent view (features.agent_view)",
|
|
170
|
+
};
|
|
171
|
+
/** Slack's answers that mean the app lacks a scope or a feature: a setup gap, not a passing fault. */
|
|
172
|
+
const SIGN_SETUP_ERRORS = new Set(["missing_scope", "feature_disabled", "not_allowed_token_type", "not_allowed", "access_denied"]);
|
|
173
|
+
/** Slack's answers that say the sign is already in the state asked for. */
|
|
174
|
+
const SIGN_NO_OP_ERRORS = new Set(["already_reacted", "no_reaction"]);
|
|
175
|
+
const warnedSignErrors = new Set();
|
|
176
|
+
/** One sign call. Never throws; a failure is logged once per method and error, with the setup fix. */
|
|
177
|
+
async function sendSign(form, method, body) {
|
|
178
|
+
let error;
|
|
179
|
+
try {
|
|
180
|
+
const res = await form(method, body.toString());
|
|
181
|
+
if (res?.ok === true)
|
|
182
|
+
return;
|
|
183
|
+
error = String(res?.error ?? "no answer");
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
error = String(err);
|
|
187
|
+
}
|
|
188
|
+
if (SIGN_NO_OP_ERRORS.has(error))
|
|
189
|
+
return;
|
|
190
|
+
const key = `${method}:${error}`;
|
|
191
|
+
if (warnedSignErrors.has(key))
|
|
192
|
+
return;
|
|
193
|
+
warnedSignErrors.add(key);
|
|
194
|
+
const fix = SIGN_SETUP_ERRORS.has(error)
|
|
195
|
+
? `${SIGN_SETUP_FIX[method] ?? "this sign needs a manifest scope"}. Fix: paste the app manifest again (App Manifest → Save Changes) and reinstall if Slack asks`
|
|
196
|
+
: "the sign is best-effort and the turn goes on";
|
|
197
|
+
console.warn(`[open-everywhere:slack-session] ${method} failed (${error}) — ${fix}. Logged once per process.`);
|
|
198
|
+
}
|
|
199
|
+
/** Test-only: forget which sign failures were already logged. */
|
|
200
|
+
export function _resetSignWarningsForTest() {
|
|
201
|
+
warnedSignErrors.clear();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Show or clear the DM turn's working signs on the session's root: the 👀 and the status line. The
|
|
205
|
+
* two calls run in parallel, because the turn waits on this setter. A swallowed room op (the egress
|
|
206
|
+
* spoof) sends neither. Never throws.
|
|
207
|
+
*/
|
|
208
|
+
async function setSlackDmWorkingSigns(session, working, form = slackForm) {
|
|
209
|
+
if (swallowRoomOp(`a slack DM working sign ${working ? "add" : "remove"}`))
|
|
210
|
+
return;
|
|
211
|
+
const reaction = new URLSearchParams({ channel: session.channelId, name: WORKING_REACTION, timestamp: session.threadTs });
|
|
212
|
+
const status = new URLSearchParams({ channel_id: session.channelId, thread_ts: session.threadTs, status: working ? SLACK_DM_WORKING_STATUS : "" });
|
|
213
|
+
await Promise.all([sendSign(form, working ? "reactions.add" : "reactions.remove", reaction), sendSign(form, "assistant.threads.setStatus", status)]);
|
|
214
|
+
}
|
|
215
|
+
/** The Slack setter core's turn-anchor hook calls (registered in register.ts). */
|
|
216
|
+
export function slackSurfaceSessionSetter(form = slackForm) {
|
|
217
|
+
return async (attributes, status) => {
|
|
218
|
+
const session = slackAgentSessionOf(attributes);
|
|
219
|
+
if (!session)
|
|
220
|
+
return undefined;
|
|
221
|
+
const fromTurn = attributes[SESSION_PIN_ATTRIBUTE] === undefined;
|
|
222
|
+
await Promise.all([setSlackSessionStatus({ ...session, status }, form), ...(fromTurn ? [setSlackDmWorkingSigns(session, status === "processing", form)] : [])]);
|
|
223
|
+
return `${session.channelId}:${session.threadTs}`;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Parse an `agent_session_stopped` envelope, or null for anything else. Pure. */
|
|
227
|
+
export function parseAgentSessionStopped(envelope) {
|
|
228
|
+
if (envelope?.type !== "event_callback")
|
|
229
|
+
return null;
|
|
230
|
+
const e = envelope.event;
|
|
231
|
+
if (!e || e.type !== AGENT_SESSION_STOPPED_EVENT)
|
|
232
|
+
return null;
|
|
233
|
+
const channelId = typeof e.channel === "string" ? e.channel : "";
|
|
234
|
+
const threadTs = typeof e.thread_ts === "string" ? e.thread_ts : "";
|
|
235
|
+
const userId = typeof e.user === "string" ? e.user : "";
|
|
236
|
+
if (!channelId || !threadTs || !userId)
|
|
237
|
+
return null;
|
|
238
|
+
const teamId = typeof envelope.team_id === "string" && envelope.team_id.length > 0 ? envelope.team_id : undefined;
|
|
239
|
+
return {
|
|
240
|
+
channelId,
|
|
241
|
+
threadTs,
|
|
242
|
+
userId,
|
|
243
|
+
...(teamId ? { teamId } : {}),
|
|
244
|
+
...(typeof e.event_ts === "string" ? { eventTs: e.event_ts } : {}),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/** A 1:1 DM channel id. Only there does the stop fall back to the channel's newest turn. */
|
|
248
|
+
function isDmChannel(channelId) {
|
|
249
|
+
return channelId.startsWith("D");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Stop the turn a Stop press names, then set the session `active`.
|
|
253
|
+
*
|
|
254
|
+
* The turn is found by the session's thread first (the key the ✋ arm uses for a thread root). In the
|
|
255
|
+
* person's 1:1 DM it falls back to the DM's newest turn, because a top-level DM message keys its turn by
|
|
256
|
+
* the channel alone. In a shared channel it does not fall back: there the newest turn can belong to
|
|
257
|
+
* another thread, and cancelling nothing beats cancelling a guess. The status is set whatever the
|
|
258
|
+
* cancel answers, because a session left `processing` keeps its spinner for an hour. Never throws.
|
|
259
|
+
*/
|
|
260
|
+
export async function routeAgentSessionStop(ev, deps) {
|
|
261
|
+
const p = resolvePartition(bindableForSlackIds({ teamId: ev.teamId, channelId: ev.channelId, userId: ev.userId }));
|
|
262
|
+
const anchorKey = turnAnchorKey("slack", ev.channelId, ev.threadTs);
|
|
263
|
+
const channelKey = isDmChannel(ev.channelId) ? turnChannelKey("slack", ev.channelId) : undefined;
|
|
264
|
+
let result;
|
|
265
|
+
try {
|
|
266
|
+
result = await (deps.cancel ?? cancelAnchoredTurn)({
|
|
267
|
+
org: p.org,
|
|
268
|
+
platform: "slack",
|
|
269
|
+
workspace: p.workspace,
|
|
270
|
+
requestedBy: ev.userId,
|
|
271
|
+
via: "stop-button",
|
|
272
|
+
query: { ...(anchorKey ? { anchorKey } : {}), ...(channelKey ? { channelKey } : {}) },
|
|
273
|
+
attachSession: deps.attachSession,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
result = { outcome: "error", message: `stop failed: ${String(err)}` };
|
|
278
|
+
}
|
|
279
|
+
await (deps.setStatus ?? setSlackSessionStatus)({ channelId: ev.channelId, threadTs: ev.threadTs, status: "active" }).catch(() => false);
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=slack-agent-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-agent-session.js","sourceRoot":"","sources":["../../../agent/channels/slack-agent-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type BindableContext } from "@open-everywhere/core/agent/adapter-api.js";
|
|
1
2
|
/** A resolved attachment record — its `urlPrivate` came from a Slack event's `files[].url_private`
|
|
2
3
|
* (trusted provenance), never a model-supplied string. */
|
|
3
4
|
export interface SlackAttachmentRecord {
|
|
@@ -48,6 +49,16 @@ export declare function persistSlackAttachment(fileId: string | undefined, rec:
|
|
|
48
49
|
* defaults to the deployment org (single-tenant) when omitted.
|
|
49
50
|
*/
|
|
50
51
|
export declare function resolveSlackAttachmentDurable(fileId: string, org?: string): Promise<SlackAttachmentRecord | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* The size-guarded byte pull over a TRUSTED `url_private`: the bot-token, host-pinned fetch, then the Content-Length
|
|
54
|
+
* pre-check (rejects with {@link AttachmentOversizeError} phase "declared" BEFORE the body is read), then the post-read
|
|
55
|
+
* cap (phase "actual"). Two callers: the guarded resolver below, and the voice-clip loader (slack-voice-clips.ts),
|
|
56
|
+
* which reads a clip off the event that woke the agent. Both pass an event-supplied URL, never a model string.
|
|
57
|
+
*/
|
|
58
|
+
export declare function pullSlackAttachmentBytes(ctx: BindableContext, ref: {
|
|
59
|
+
readonly urlPrivate: string;
|
|
60
|
+
readonly id?: string;
|
|
61
|
+
}, maxBytes: number): Promise<Uint8Array>;
|
|
51
62
|
/**
|
|
52
63
|
* Register Slack as an attachment-plane resolver (channels/attachments.ts) — the neutral seam
|
|
53
64
|
* describe_image consults so its contract carries no per-platform union arm. The resolver wraps the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-attachments.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-attachments.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slack-attachments.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-attachments.ts"],"names":[],"mappings":"AA4BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AAGpD;2DAC2D;AAC3D,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;gFAC4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;wEACoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;2GACuG;IACvG,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,qBAAqB,GAAG,IAAI,CASpG;AAED;;oFAEoF;AACpF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAExF;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAahI;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAY5H;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtK;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,IAAI,IAAI,CA0BtD;AAMD,2EAA2E;AAC3E,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
|
|
@@ -109,6 +109,26 @@ export async function resolveSlackAttachmentDurable(fileId, org) {
|
|
|
109
109
|
return undefined;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* The size-guarded byte pull over a TRUSTED `url_private`: the bot-token, host-pinned fetch, then the Content-Length
|
|
114
|
+
* pre-check (rejects with {@link AttachmentOversizeError} phase "declared" BEFORE the body is read), then the post-read
|
|
115
|
+
* cap (phase "actual"). Two callers: the guarded resolver below, and the voice-clip loader (slack-voice-clips.ts),
|
|
116
|
+
* which reads a clip off the event that woke the agent. Both pass an event-supplied URL, never a model string.
|
|
117
|
+
*/
|
|
118
|
+
export async function pullSlackAttachmentBytes(ctx, ref, maxBytes) {
|
|
119
|
+
const res = await fetchSlackAttachment(ctx, ref);
|
|
120
|
+
if (!res.ok)
|
|
121
|
+
throw new Error(`fetching the upload failed (HTTP ${res.status})`);
|
|
122
|
+
// Content-Length pre-check off the declared length — reject before reading the body.
|
|
123
|
+
const declared = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
124
|
+
if (Number.isFinite(declared) && declared > maxBytes)
|
|
125
|
+
throw new AttachmentOversizeError(declared, "declared", maxBytes);
|
|
126
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
127
|
+
// Post-read cap — a chunked response can exceed the (possibly-absent) Content-Length.
|
|
128
|
+
if (bytes.byteLength > maxBytes)
|
|
129
|
+
throw new AttachmentOversizeError(bytes.byteLength, "actual", maxBytes);
|
|
130
|
+
return bytes;
|
|
131
|
+
}
|
|
112
132
|
/**
|
|
113
133
|
* Register Slack as an attachment-plane resolver (channels/attachments.ts) — the neutral seam
|
|
114
134
|
* describe_image consults so its contract carries no per-platform union arm. The resolver wraps the
|
|
@@ -146,20 +166,7 @@ export function registerSlackAttachmentResolver() {
|
|
|
146
166
|
ref: `slack-file:${attachmentId}`,
|
|
147
167
|
mimeType: rec.mimeType,
|
|
148
168
|
name: rec.name,
|
|
149
|
-
|
|
150
|
-
const res = await fetchSlackAttachment(ctx, { urlPrivate: rec.urlPrivate, id: attachmentId });
|
|
151
|
-
if (!res.ok)
|
|
152
|
-
throw new Error(`fetching the upload failed (HTTP ${res.status})`);
|
|
153
|
-
// Content-Length pre-check off the declared length — reject before reading the body.
|
|
154
|
-
const declared = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
155
|
-
if (Number.isFinite(declared) && declared > maxBytes)
|
|
156
|
-
throw new AttachmentOversizeError(declared, "declared", maxBytes);
|
|
157
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
158
|
-
// Post-read cap — a chunked response can exceed the (possibly-absent) Content-Length.
|
|
159
|
-
if (bytes.byteLength > maxBytes)
|
|
160
|
-
throw new AttachmentOversizeError(bytes.byteLength, "actual", maxBytes);
|
|
161
|
-
return bytes;
|
|
162
|
-
},
|
|
169
|
+
pull: ({ maxBytes }) => pullSlackAttachmentBytes(ctx, { urlPrivate: rec.urlPrivate, id: attachmentId }, maxBytes),
|
|
163
170
|
};
|
|
164
171
|
},
|
|
165
172
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-attachments.js","sourceRoot":"","sources":["../../../agent/channels/slack-attachments.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,uDAAuD;AACvD,EAAE;AACF,+FAA+F;AAC/F,gGAAgG;AAChG,iGAAiG;AACjG,iGAAiG;AACjG,gGAAgG;AAChG,iGAAiG;AACjG,kGAAkG;AAClG,mGAAmG;AACnG,gGAAgG;AAChG,EAAE;AACF,kGAAkG;AAClG,gGAAgG;AAChG,+FAA+F;AAC/F,sFAAsF;AACtF,EAAE;AACF,gGAAgG;AAChG,qGAAqG;AACrG,iGAAiG;AACjG,iGAAiG;AACjG,kGAAkG;AAClG,qGAAqG;AACrG,gGAAgG;AAChG,mGAAmG;AACnG,4FAA4F;AAE5F,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,0BAA0B,EAC1B,uBAAuB,
|
|
1
|
+
{"version":3,"file":"slack-attachments.js","sourceRoot":"","sources":["../../../agent/channels/slack-attachments.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import { type BindableContext } from "@open-everywhere/core/agent/adapter-api.js";
|
|
1
|
+
import { type BindableContext, type SurfaceBinding } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
2
|
/** The public URL Slack POSTs the "/oe" slash command to (the app's Slash Command Request URL). */
|
|
3
3
|
export declare const SLACK_COMMANDS_ROUTE = "/eve/v1/slack/commands";
|
|
4
|
+
/** What the route learned about a command's trust before it binds. `dmPartnerTrust` is the invoker's own
|
|
5
|
+
* verdict in a 1:1 DM. `room` is a channel's `conversations.info` verdict, present whenever Slack answered
|
|
6
|
+
* it, whoever typed the command. `invokerMember` is present only when `users.info` CONFIRMED the invoker a
|
|
7
|
+
* member (see {@link slashRoomVerdict}). The person's binding stamps `room` only beside `invokerMember`;
|
|
8
|
+
* the room's binding stamps it alone (OPE-2588). */
|
|
9
|
+
export interface SlashTrustFacts {
|
|
10
|
+
readonly dmPartnerTrust?: "internal" | "external";
|
|
11
|
+
readonly room?: {
|
|
12
|
+
readonly extShared: boolean;
|
|
13
|
+
};
|
|
14
|
+
readonly invokerMember?: true;
|
|
15
|
+
}
|
|
16
|
+
/** Which verdict a slash binding resolves: the PERSON's (the room's verdict folded with the invoker
|
|
17
|
+
* check, which every member gate reads) or the ROOM's alone (which the room's budget reads). */
|
|
18
|
+
export type SlashTrustLeg = "person" | "room";
|
|
4
19
|
/** Build the BindableContext the identity seam (bindFrom/bindSlack) expects from Slack's
|
|
5
20
|
* slash-command form fields. Slack's slash-command payload carries no `channel_type` /
|
|
6
21
|
* `is_private` bit (unlike an app_mention/message event) — `bindSlack`'s existing fallbacks
|
|
@@ -13,15 +28,103 @@ export declare const SLACK_COMMANDS_ROUTE = "/eve/v1/slack/commands";
|
|
|
13
28
|
* reads — so a Grid declared once now answers here the same way it answers at every other door,
|
|
14
29
|
* instead of resolving as though the line were absent.
|
|
15
30
|
*
|
|
16
|
-
* 🔴 STATE ITS REACH EXACTLY
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
31
|
+
* 🔴 STATE ITS REACH EXACTLY. For a CHANNEL command the rung does NOT change `binding.trust`: the
|
|
32
|
+
* room's own verdict answers there (see below), and `bindFrom` takes that raw verdict ahead of any
|
|
33
|
+
* vouch. The rung reaches `binding.trust` only on a DM-shaped `/oe` (a `D…` channel id), and only
|
|
34
|
+
* when the invoker probe gave no verdict: the mapper then leaves trust undefined and the vouch
|
|
35
|
+
* answers (see below). Spread conditionally, like every other door: a single-workspace install
|
|
36
|
+
* stamps nothing.
|
|
37
|
+
*
|
|
38
|
+
* 🔴 A CHANNEL COMMAND CARRIES THE ROOM'S OWN VERDICT (OPE-2579). Slack's slash payload carries no
|
|
39
|
+
* sharing bit, so before this every channel `/oe` bound `slackTrust(undefined, undefined,
|
|
40
|
+
* "channel")`, which fails closed to `external`. A member in an internal channel was then refused
|
|
41
|
+
* `/oe suggest on` as an outsider. The route now passes {@link slashRoomVerdict}'s answer as
|
|
42
|
+
* `is_ext_shared` + `trust_checked`, the two attributes a channel message stamps from the same
|
|
43
|
+
* TTL-cached `conversations.info`. So a channel command reads the room's `internal` or `external` as a
|
|
44
|
+
* message in that room does. On the `person` leg (the default) the verdict is stamped only for an
|
|
45
|
+
* invoker `users.info` confirms a member. On the `room` leg it is stamped for anybody, because that
|
|
46
|
+
* leg answers the room's budget, not a member gate (OPE-2588, {@link bindSlashCommand}). No verdict
|
|
47
|
+
* stamps nothing, and the command stays fail-closed `external`.
|
|
48
|
+
*
|
|
49
|
+
* 🔴 A DM COMMAND CARRIES THE INVOKER'S OWN VERDICT (OPE-2579). In a 1:1 DM the invoker IS the
|
|
50
|
+
* partner, and the route passes {@link slashInvokerDmTrust}'s answer as `dm_partner_trust`, the
|
|
51
|
+
* attribute the chat DM leg stamps from the same `users.info` probe. Before this, a workspace vouch
|
|
52
|
+
* alone answered a DM command, so a guest or a Connect stranger in their DM with the app read
|
|
53
|
+
* `internal` here while the same person read `external` in a DM turn. A verdict outranks the vouch
|
|
54
|
+
* in both directions, exactly as in a DM turn: a guest reads `external` under a workspace vouch, and
|
|
55
|
+
* a confirmed member reads `internal` with no declaration at all. No verdict leaves the attribute
|
|
56
|
+
* unset and the binding resolves as before. This reaches every `/oe` command typed in a DM, not
|
|
57
|
+
* only `watch`. */
|
|
58
|
+
export declare function bindableForSlashCommand(params: URLSearchParams, invoker?: SlashTrustFacts, leg?: SlashTrustLeg): BindableContext;
|
|
59
|
+
/**
|
|
60
|
+
* The invoker's own membership verdict for a 1:1 DM command, in `dm_partner_trust`'s vocabulary, or
|
|
61
|
+
* `{}` when there is none to give (OPE-2579). It is the chat DM leg's reading of the same probe
|
|
62
|
+
* (slack-front.ts `liveDmPartnerTrust`): `slackAuthorIsMember` decides, so the two doors cannot
|
|
63
|
+
* drift on what a guest is. A channel or group DM asks nothing here: there the ROOM's verdict is the
|
|
64
|
+
* binding's trust, and {@link slashRoomVerdict} reads it.
|
|
65
|
+
*
|
|
66
|
+
* 🔴 NO VERDICT IS NOT A VERDICT. No user id, no bot token, a refused `users.info`, a transport
|
|
67
|
+
* failure or a probe slower than {@link INVOKER_PROBE_MS} all answer `{}`, which leaves the binding
|
|
68
|
+
* exactly as it resolved before this probe existed. An unanswered probe never invents a guest, and
|
|
69
|
+
* never invents a member.
|
|
70
|
+
*/
|
|
71
|
+
export declare function slashInvokerDmTrust(params: URLSearchParams, probe?: (userId: string, teamId: string | undefined) => Promise<boolean | undefined>): Promise<{
|
|
72
|
+
readonly dmPartnerTrust?: "internal" | "external";
|
|
73
|
+
}>;
|
|
74
|
+
/** The room facts {@link slashRoomVerdict} reads: `slackChannelFactsFor`'s shape, which every channel wake reads. */
|
|
75
|
+
export type SlashRoomFacts = (channelId: string) => Promise<{
|
|
76
|
+
readonly channelType?: string;
|
|
77
|
+
readonly extShared?: boolean;
|
|
78
|
+
readonly trustChecked?: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* A CHANNEL command's room verdict and invoker check, or `{}` when there is no room verdict to give
|
|
82
|
+
* (OPE-2579). The room read is `slackChannelFactsFor`, the TTL-cached `conversations.info` every
|
|
83
|
+
* channel wake reads, so the command door and the message door cannot disagree about one room.
|
|
84
|
+
*
|
|
85
|
+
* 🔴 THE INVOKER LEG, AND IT FAILS CLOSED. A channel message keeps the room's verdict on the auth and marks
|
|
86
|
+
* a guest author on the side (slack-events.ts `slackWakeAuthorFacts`), because one guest's message must not
|
|
87
|
+
* demote the channel's memory for everybody. A command has no session to protect: its binding exists for
|
|
88
|
+
* one answer, and `/oe suggest on` and `/oe watch more|less` read `binding.trust` as "is this person a
|
|
89
|
+
* member". So the route folds the author leg into the binding's `trust`, the composition Discord and
|
|
90
|
+
* Teams make: `invokerMember` is set only when `users.info` CONFIRMS the invoker a member, and the
|
|
91
|
+
* person's binding stamps the room's verdict only beside it. A guest, a Slack Connect stranger, and a
|
|
92
|
+
* person the probe could not check (no `users:read`, a refusal, a timeout) all leave `trust` fail-closed
|
|
93
|
+
* `external`. The message door fails OPEN on an unanswered probe because it marks a turn. Here `trust`
|
|
94
|
+
* IS the member gate, and a channel has no declaration to fall back to the way a DM command does.
|
|
95
|
+
*
|
|
96
|
+
* 🔴 THE ROOM'S VERDICT IS RETURNED FOR EVERY INVOKER (OPE-2588). `/oe budget` names the room's budget,
|
|
97
|
+
* and a channel's turns are charged under the room's domain, whoever typed them. So {@link bindSlashCommand}
|
|
98
|
+
* binds the room's verdict alone into `roomTrust`, and the budget reads that. A guest's `trust` is
|
|
99
|
+
* unchanged, and `/oe budget` reads both to refuse a guest in an internal channel (commands/core.ts
|
|
100
|
+
* `budgetWithheldFrom`). `/oe cover on` refuses outside a DM before it reads trust.
|
|
101
|
+
*
|
|
102
|
+
* 🔴 ONLY A ROOM IN THE CHANNEL MODE IS STAMPED. A 1:1 DM has its own leg ({@link slashInvokerDmTrust}).
|
|
103
|
+
* A group DM, or a room whose type Slack did not report, stamps nothing, so its binding is unchanged.
|
|
104
|
+
* Stamping a sharing verdict on a group DM the binding still calls a channel would mix two modes' rules.
|
|
105
|
+
*
|
|
106
|
+
* 🔴 NO VERDICT IS NOT A VERDICT. A failed or refused `conversations.info`, a response without the check,
|
|
107
|
+
* or a read slower than {@link INVOKER_PROBE_MS} answers `{}`. The command then binds `external`, as
|
|
108
|
+
* before this read existed. The room read and the invoker probe run in parallel inside one ack budget.
|
|
109
|
+
*/
|
|
110
|
+
export declare function slashRoomVerdict(params: URLSearchParams, roomFacts?: SlashRoomFacts, probe?: (userId: string, teamId: string | undefined) => Promise<boolean | undefined>): Promise<SlashTrustFacts>;
|
|
111
|
+
/** Both legs, for whichever kind of room the command came from. Never throws. */
|
|
112
|
+
export declare function slashTrustFacts(params: URLSearchParams): Promise<SlashTrustFacts>;
|
|
113
|
+
/**
|
|
114
|
+
* The slash command's binding, with the room's trust and the person's trust kept apart (OPE-2588).
|
|
115
|
+
*
|
|
116
|
+
* `trust` is the PERSON leg: the member gate `/oe suggest on`, `/oe watch more|less` and `/oe cover on`
|
|
117
|
+
* read. `roomTrust` is the ROOM leg: the same `bindFrom` walk over the room's verdict alone, so the
|
|
118
|
+
* declarations and the fail-closed default apply to it unchanged. `/oe budget` and the caps behind
|
|
119
|
+
* `raise` and `clear` read it through `roomTrustOf`. Before this the binding carried one value, so a
|
|
120
|
+
* guest in an internal channel read the external domain's budget while that channel's turns were
|
|
121
|
+
* charged under the internal domain. Now `/oe budget` refuses that guest, because a guest must not read
|
|
122
|
+
* an internal room's caps and spend (Tommy's ruling on #1431).
|
|
123
|
+
*
|
|
124
|
+
* In a DM both legs bind the same context, so the two values are equal. Every other field comes from
|
|
125
|
+
* the person's binding. The two bindings differ only in the two trust attributes, which move no key.
|
|
126
|
+
*/
|
|
127
|
+
export declare function bindSlashCommand(params: URLSearchParams, facts: SlashTrustFacts): SurfaceBinding;
|
|
25
128
|
declare const _default: import("eve/channels").Channel<undefined, Record<string, unknown>, Record<string, unknown>>;
|
|
26
129
|
export default _default;
|
|
27
130
|
//# sourceMappingURL=slack-commands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-commands.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-commands.ts"],"names":[],"mappings":"AAqCA,OAAO,
|
|
1
|
+
{"version":3,"file":"slack-commands.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-commands.ts"],"names":[],"mappings":"AAqCA,OAAO,EAYL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,4CAA4C,CAAC;AASpD,mGAAmG;AACnG,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAS7D;;;;qDAIqD;AACrD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;CAC/B;AAED;iGACiG;AACjG,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAsCoB;AACpB,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,GAAG,GAAE,aAAwB,GAAG,eAAe,CAqB1I;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,eAAe,EACvB,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAuB,GACxG,OAAO,CAAC;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAA;CAAE,CAAC,CAKhE;AAED,qHAAqH;AACrH,MAAM,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAE9J;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,eAAe,EACvB,SAAS,GAAE,cAAqC,EAChD,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAuB,GACxG,OAAO,CAAC,eAAe,CAAC,CAW1B;AAED,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAEvF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,cAAc,CAIhG"}
|