@open-everywhere/provider-slack 0.8.0 → 0.9.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/agent/app-manifest.ts +3 -1
- package/agent/channels/slack-agent-session.ts +131 -5
- package/agent/channels/slack-events.ts +59 -5
- package/agent/channels/slack-front.ts +15 -8
- package/agent/channels/slack-identity.ts +42 -1
- package/agent/channels/slack-web-api.ts +3 -3
- package/agent/channels/slack-working-signs.ts +71 -0
- package/agent/channels/slack.ts +36 -50
- package/agent/docs-generated.ts +50 -13
- package/agent/ops.ts +4 -0
- package/agent/register.ts +4 -2
- package/agent/setup/claim.ts +9 -8
- package/agent/setup/credentials.ts +82 -79
- package/agent/setup/eve-credentials.ts +2 -2
- package/agent/setup/page.ts +3 -3
- package/agent/status.ts +12 -4
- package/dist/agent/app-manifest.d.ts.map +1 -1
- package/dist/agent/app-manifest.js +3 -1
- package/dist/agent/app-manifest.js.map +1 -1
- package/dist/agent/channels/slack-agent-session.d.ts +22 -2
- package/dist/agent/channels/slack-agent-session.d.ts.map +1 -1
- package/dist/agent/channels/slack-agent-session.js +127 -6
- package/dist/agent/channels/slack-agent-session.js.map +1 -1
- package/dist/agent/channels/slack-events.d.ts +7 -3
- package/dist/agent/channels/slack-events.d.ts.map +1 -1
- package/dist/agent/channels/slack-events.js +57 -6
- package/dist/agent/channels/slack-events.js.map +1 -1
- package/dist/agent/channels/slack-front.d.ts.map +1 -1
- package/dist/agent/channels/slack-front.js +16 -9
- package/dist/agent/channels/slack-front.js.map +1 -1
- package/dist/agent/channels/slack-identity.d.ts +33 -0
- package/dist/agent/channels/slack-identity.d.ts.map +1 -1
- package/dist/agent/channels/slack-identity.js +35 -1
- package/dist/agent/channels/slack-identity.js.map +1 -1
- package/dist/agent/channels/slack-web-api.js +3 -3
- package/dist/agent/channels/slack-web-api.js.map +1 -1
- package/dist/agent/channels/slack-working-signs.d.ts +34 -0
- package/dist/agent/channels/slack-working-signs.d.ts.map +1 -0
- package/dist/agent/channels/slack-working-signs.js +63 -0
- package/dist/agent/channels/slack-working-signs.js.map +1 -0
- package/dist/agent/channels/slack.d.ts.map +1 -1
- package/dist/agent/channels/slack.js +28 -46
- 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 +50 -13
- package/dist/agent/docs-generated.js.map +1 -1
- package/dist/agent/ops.d.ts.map +1 -1
- package/dist/agent/ops.js +4 -1
- package/dist/agent/ops.js.map +1 -1
- package/dist/agent/register.d.ts.map +1 -1
- package/dist/agent/register.js +4 -2
- package/dist/agent/register.js.map +1 -1
- package/dist/agent/setup/claim.d.ts.map +1 -1
- package/dist/agent/setup/claim.js +9 -8
- package/dist/agent/setup/claim.js.map +1 -1
- package/dist/agent/setup/credentials.d.ts +21 -20
- package/dist/agent/setup/credentials.d.ts.map +1 -1
- package/dist/agent/setup/credentials.js +71 -75
- package/dist/agent/setup/credentials.js.map +1 -1
- package/dist/agent/setup/eve-credentials.js +2 -2
- package/dist/agent/setup/eve-credentials.js.map +1 -1
- package/dist/agent/setup/page.js +3 -3
- package/dist/agent/setup/page.js.map +1 -1
- package/dist/agent/status.d.ts +10 -2
- package/dist/agent/status.d.ts.map +1 -1
- package/dist/agent/status.js +12 -4
- package/dist/agent/status.js.map +1 -1
- package/package.json +6 -2
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
// WHERE THE SLACK BUNDLE'S TWO SECRETS COME FROM (OPE-2231) — the signing secret that verifies
|
|
2
2
|
// Slack's requests, and the bot token that signs ours.
|
|
3
3
|
//
|
|
4
|
-
// Before this module there was one answer:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Before this module there was one answer: the SLACK_SIGNING_SECRET and SLACK_BOT_TOKEN env vars,
|
|
5
|
+
// read inline at each call site. A `/setup` run now captures both into the credential store, so there
|
|
6
|
+
// are two homes and an order is needed.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
8
|
+
// THE OPERATOR SECRET COMES FIRST (OPE-2650). The first home is the operator secret of the same name
|
|
9
|
+
// — `oe secret set SLACK_BOT_TOKEN`, read through core's `operatorSecret`, which takes the db store
|
|
10
|
+
// first and the env var as its warned on-ramp. The second home is the value `/setup` stored under the
|
|
11
|
+
// `slack-app:` refs. Before OPE-2650 the first home was the env var alone (the OPE-2231 ruling: "env
|
|
12
|
+
// as fallback (env wins if set, for existing installs)"). The reason for that order holds unchanged:
|
|
13
13
|
//
|
|
14
14
|
// • EVERY deployment that exists today holds these two in env and works. This change must not be
|
|
15
|
-
// able to alter which Slack app a working deployment speaks as.
|
|
16
|
-
// somebody started and abandoned, or a second workspace's install, would silently take over
|
|
17
|
-
// live deployment's Slack identity at the next cold start, with a 401 loop as the symptom and
|
|
15
|
+
// able to alter which Slack app a working deployment speaks as. `/setup`-first could: a `/setup`
|
|
16
|
+
// run somebody started and abandoned, or a second workspace's install, would silently take over
|
|
17
|
+
// a live deployment's Slack identity at the next cold start, with a 401 loop as the symptom and
|
|
18
18
|
// nothing in the logs saying a different secret had been picked.
|
|
19
|
-
// •
|
|
20
|
-
// resolved to before
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// • A fresh hosted install sets neither env var (the deploy button asks for one value, the
|
|
25
|
-
// credential encryption key), so it reads the store, which is where `/setup` put them.
|
|
19
|
+
// • Operator-secret-first cannot do that. A deployment with the env vars set and no stored operator
|
|
20
|
+
// secret resolves to exactly the value it resolved to before, every time. An operator who stores
|
|
21
|
+
// the value with `oe secret set` chose it on purpose, so it wins the same way the env var did.
|
|
22
|
+
// • A fresh hosted install sets neither (the deploy button asks for one value, the credential
|
|
23
|
+
// encryption key), so it reads the `/setup` value.
|
|
26
24
|
//
|
|
27
|
-
// The cost, stated plainly: an operator who runs `/setup` on a deployment that still
|
|
28
|
-
//
|
|
29
|
-
// visible instead of mysterious — the
|
|
25
|
+
// The cost, stated plainly: an operator who runs `/setup` on a deployment that still holds the
|
|
26
|
+
// operator secrets will find the `/setup` credentials ignored. {@link slackCredentialSourceNotice} is
|
|
27
|
+
// what makes that visible instead of mysterious — the resolver logs it once per name.
|
|
30
28
|
//
|
|
31
29
|
// MEMOISED PER PROCESS WITH A SHORT TTL, store reads only. The signature check runs on every inbound
|
|
32
30
|
// Slack request and the bot token on every outbound call, so a vault round trip per request is not
|
|
@@ -44,26 +42,30 @@
|
|
|
44
42
|
//
|
|
45
43
|
// A TTL bounds that to {@link HIT_TTL_MS} without giving up the per-request cost, which is what the
|
|
46
44
|
// memo was for. It is deliberately not a cache invalidated by the reopen path: a reopen runs in ONE
|
|
47
|
-
// process and these memos live in all of them, so nothing a reopen does can reach them. Only time can.
|
|
48
|
-
//
|
|
45
|
+
// process and these memos live in all of them, so nothing a reopen does can reach them. Only time can. The operator-secret
|
|
46
|
+
// arm has its own thirty-second memo in core (`operatorSecret`). A MISS is remembered only BRIEFLY ({@link MISS_TTL_MS}) — long enough that an
|
|
49
47
|
// unauthenticated POST to the public Slack route cannot drive one database query per request, short
|
|
50
48
|
// enough that a process which booted before `/setup` finished still picks the credentials up without a
|
|
51
49
|
// restart.
|
|
52
50
|
|
|
53
|
-
import { encryptedKvVault, type EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
|
|
51
|
+
import { encryptedKvVault, operatorSecret, type EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
|
|
54
52
|
import { SLACK_BOT_TOKEN_REF, SLACK_SIGNING_SECRET_REF } from "./install-state.js";
|
|
55
53
|
|
|
56
|
-
/** The
|
|
57
|
-
const
|
|
58
|
-
/** The
|
|
59
|
-
const
|
|
54
|
+
/** The operator secret holding the app's request-signing secret — the pre-OPE-2231 home, still first. */
|
|
55
|
+
const SLACK_SIGNING_SECRET_NAME = "SLACK_SIGNING_SECRET";
|
|
56
|
+
/** The operator secret holding the workspace bot token — the pre-OPE-2231 home, still first. */
|
|
57
|
+
const SLACK_BOT_TOKEN_NAME = "SLACK_BOT_TOKEN";
|
|
60
58
|
|
|
61
|
-
/** Which home answered. Reported by {@link resolveSlackSecret} for the suite and the notice.
|
|
62
|
-
|
|
59
|
+
/** Which home answered. Reported by {@link resolveSlackSecret} for the suite and the notice.
|
|
60
|
+
* `operator` is the operator secret (`oe secret set`, with env as its on-ramp, OPE-2650); `store` is
|
|
61
|
+
* the value a `/setup` run captured. */
|
|
62
|
+
export type SlackSecretSource = "operator" | "store";
|
|
63
63
|
|
|
64
|
-
/** Injectable seams for tests — the live defaults are the real vault and
|
|
64
|
+
/** Injectable seams for tests — the live defaults are the real vault and {@link operatorSecret}. */
|
|
65
65
|
export interface SlackCredentialDeps {
|
|
66
66
|
readonly vault?: EncryptedKvVault;
|
|
67
|
+
/** TEST SEAM: a synthetic stand-in for the operator-secret answer, read by name. Absent on the live
|
|
68
|
+
* path, which resolves through {@link operatorSecret} (the db store, then env as the on-ramp). */
|
|
67
69
|
readonly env?: NodeJS.ProcessEnv;
|
|
68
70
|
/** The clock the hit and miss TTLs are read against. Tests advance it to prove a cached value stops
|
|
69
71
|
* being reused; the live default is `Date.now`. */
|
|
@@ -140,35 +142,31 @@ export function resetSlackCredentialNotices(): void {
|
|
|
140
142
|
* say. The `/setup` page renders this before a run so the operator learns it BEFORE capturing
|
|
141
143
|
* credentials the deployment will ignore, and {@link resolveSlackSecret} logs it once per name.
|
|
142
144
|
*/
|
|
143
|
-
function slackCredentialSourceNotice(
|
|
145
|
+
function slackCredentialSourceNotice(name: string): string {
|
|
144
146
|
return (
|
|
145
|
-
`[open-everywhere:slack-setup] ${
|
|
146
|
-
`IGNORES the one
|
|
147
|
-
`alter which Slack app an existing deployment speaks as. To use the
|
|
147
|
+
`[open-everywhere:slack-setup] ${name} is set as an operator secret (or as its env on-ramp), so this deployment uses that ` +
|
|
148
|
+
`value and IGNORES the one /setup stored. That is the documented order (OPE-2231, OPE-2650): the operator secret wins, so ` +
|
|
149
|
+
`landing this change cannot alter which Slack app an existing deployment speaks as. To use the /setup value instead, run ` +
|
|
150
|
+
`\`oe secret rm ${name}\`, unset the env var of the same name, and redeploy.`
|
|
148
151
|
);
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
/**
|
|
152
|
-
* Resolve one Slack secret:
|
|
155
|
+
* Resolve one Slack secret: the operator secret if set, else the `/setup` value, else `undefined`.
|
|
153
156
|
*
|
|
154
157
|
* `undefined` is a real answer, not an error — it means this deployment has not been through `/setup`
|
|
155
|
-
* and holds no
|
|
158
|
+
* and holds no operator secret either. Each caller fails in its own way (the front route 401s; an outbound
|
|
156
159
|
* call throws the message eve's primitive always threw), which is what keeps this function free of
|
|
157
160
|
* any opinion about what a missing credential should do.
|
|
158
161
|
*/
|
|
159
162
|
export async function resolveSlackSecret(
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
name: string,
|
|
164
|
+
fromOperator: string,
|
|
162
165
|
ref: string,
|
|
163
166
|
deps: SlackCredentialDeps = {},
|
|
164
167
|
): Promise<ResolvedSlackSecret | undefined> {
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// runtime reads can be named in `scripts/env-read-allowlist.ts`; a read through a PARAMETER
|
|
168
|
-
// (`env[envName]`) resolves to nothing, so the gate cannot tell which variable it reaches and
|
|
169
|
-
// refuses the site. The two public helpers below each do their own read with a module-level const
|
|
170
|
-
// as the key, which keeps both names visible to the allowlist — and leaves this function with no
|
|
171
|
-
// environment access at all.
|
|
168
|
+
// The operator-secret value is passed in, not read here, so a suite drives this function with no
|
|
169
|
+
// store and no environment. The two public helpers below do the read.
|
|
172
170
|
|
|
173
171
|
// An injected vault means a test driving a specific store state — bypass the memo entirely, so a
|
|
174
172
|
// test can rewrite a row and see the effect without reaching for a reset.
|
|
@@ -180,10 +178,10 @@ export async function resolveSlackSecret(
|
|
|
180
178
|
const cached = memoApplies ? memo.get(ref) : undefined;
|
|
181
179
|
const memoised = cached !== undefined && cached.until > now ? cached.value : undefined;
|
|
182
180
|
|
|
183
|
-
if (
|
|
184
|
-
// Only say the
|
|
185
|
-
// the operator who ran `/setup` and is about to wonder why nothing changed, not for
|
|
186
|
-
// deployment that has always used env.
|
|
181
|
+
if (fromOperator.length > 0) {
|
|
182
|
+
// Only say the operator secret is shadowing a `/setup` value when one is ACTUALLY stored — the
|
|
183
|
+
// notice is for the operator who ran `/setup` and is about to wonder why nothing changed, not for
|
|
184
|
+
// every deployment that has always used env.
|
|
187
185
|
//
|
|
188
186
|
// 🔴 THE CHECK IS MARKED DONE WHETHER OR NOT IT WARNED, and that is the whole reason this is a
|
|
189
187
|
// separate set from `noticed`. Keying the guard off "have I warned?" meant a deployment with the
|
|
@@ -191,15 +189,15 @@ export async function resolveSlackSecret(
|
|
|
191
189
|
// that it had looked, so it re-read the vault on EVERY resolution: one store round trip per
|
|
192
190
|
// inbound Slack request, for an answer that was always going to be the env value. One read per
|
|
193
191
|
// name per process, then never again.
|
|
194
|
-
if (!shadowChecked.has(
|
|
195
|
-
shadowChecked.add(
|
|
192
|
+
if (!shadowChecked.has(name)) {
|
|
193
|
+
shadowChecked.add(name);
|
|
196
194
|
const stored = memoised ?? (await readStored(ref, vault)); // best-effort; a store failure says nothing
|
|
197
195
|
if (stored !== undefined) {
|
|
198
|
-
noticed.add(
|
|
199
|
-
console.warn(slackCredentialSourceNotice(
|
|
196
|
+
noticed.add(name);
|
|
197
|
+
console.warn(slackCredentialSourceNotice(name));
|
|
200
198
|
}
|
|
201
199
|
}
|
|
202
|
-
return { value:
|
|
200
|
+
return { value: fromOperator, source: "operator" };
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
if (memoised !== undefined) return { value: memoised, source: "store" };
|
|
@@ -235,48 +233,53 @@ async function readStored(ref: string, injected: EncryptedKvVault | undefined):
|
|
|
235
233
|
}
|
|
236
234
|
}
|
|
237
235
|
|
|
236
|
+
/**
|
|
237
|
+
* The operator-secret answer for `name`, trimmed, or `""` when it is set nowhere. A test passes
|
|
238
|
+
* `deps.env` as a synthetic stand-in; the live path reads {@link operatorSecret} (the db store, then
|
|
239
|
+
* env as the on-ramp).
|
|
240
|
+
*/
|
|
241
|
+
async function fromOperatorSecret(name: string, deps: SlackCredentialDeps): Promise<string> {
|
|
242
|
+
const value = deps.env !== undefined ? deps.env[name] : await operatorSecret(name);
|
|
243
|
+
return (value ?? "").trim();
|
|
244
|
+
}
|
|
245
|
+
|
|
238
246
|
/** The app's request-signing secret, or `undefined` when this deployment holds none. */
|
|
239
247
|
export async function slackSigningSecret(deps: SlackCredentialDeps = {}): Promise<string | undefined> {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
return (await resolveSlackSecret(SLACK_SIGNING_SECRET_ENV, fromEnv, SLACK_SIGNING_SECRET_REF, deps))?.value;
|
|
248
|
+
const fromOperator = await fromOperatorSecret(SLACK_SIGNING_SECRET_NAME, deps);
|
|
249
|
+
return (await resolveSlackSecret(SLACK_SIGNING_SECRET_NAME, fromOperator, SLACK_SIGNING_SECRET_REF, deps))?.value;
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
/** The workspace bot token, or `undefined` when this deployment holds none. */
|
|
246
253
|
export async function slackBotToken(deps: SlackCredentialDeps = {}): Promise<string | undefined> {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
return (await resolveSlackSecret(SLACK_BOT_TOKEN_ENV, fromEnv, SLACK_BOT_TOKEN_REF, deps))?.value;
|
|
254
|
+
const fromOperator = await fromOperatorSecret(SLACK_BOT_TOKEN_NAME, deps);
|
|
255
|
+
return (await resolveSlackSecret(SLACK_BOT_TOKEN_NAME, fromOperator, SLACK_BOT_TOKEN_REF, deps))?.value;
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
/**
|
|
253
259
|
* Does this deployment hold a Slack bot token in EITHER home? The telemetry heartbeat's
|
|
254
|
-
* credential-presence probe (OPE-2241). Same order as {@link slackBotToken}:
|
|
260
|
+
* credential-presence probe (OPE-2241). Same order as {@link slackBotToken}: the operator secret
|
|
261
|
+
* first, then the `/setup` value.
|
|
255
262
|
*
|
|
256
|
-
* 🔴 A SET
|
|
257
|
-
* when the
|
|
258
|
-
* a process that does no Slack work a store round trip
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
* through the same memo as every outbound call.
|
|
263
|
+
* 🔴 A SET OPERATOR SECRET ANSWERS WITHOUT THE `/setup` READ. `slackBotToken` reads the `/setup` value
|
|
264
|
+
* once per process even when the operator secret is set, for the shadow notice. On the heartbeat path
|
|
265
|
+
* that read would cost a process that does no Slack work a second store round trip. So the operator
|
|
266
|
+
* secret is checked here first, and only an empty one reads the `/setup` value, through the same memo
|
|
267
|
+
* as every outbound call.
|
|
262
268
|
*/
|
|
263
269
|
export async function slackCredentialsConfigured(deps: SlackCredentialDeps = {}): Promise<boolean> {
|
|
264
|
-
|
|
265
|
-
if ((env[SLACK_BOT_TOKEN_ENV] ?? "").trim().length > 0) return true;
|
|
270
|
+
if ((await fromOperatorSecret(SLACK_BOT_TOKEN_NAME, deps)).length > 0) return true;
|
|
266
271
|
return (await slackBotToken(deps)) !== undefined;
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
/**
|
|
270
|
-
* Is
|
|
271
|
-
* page's "this
|
|
272
|
-
*
|
|
273
|
-
* Unrolled rather than looped over the two names for the same reason {@link resolveSlackSecret} takes
|
|
274
|
-
* its env value as a parameter: the gate resolves a const key and nothing else.
|
|
275
|
+
* Is each operator secret set, for the ONE caller that needs to ask without resolving — the `/setup`
|
|
276
|
+
* page's "this operator secret will shadow what you are about to store" notice.
|
|
275
277
|
*/
|
|
276
|
-
export function
|
|
277
|
-
|
|
278
|
+
export async function slackOperatorSecretPresent(
|
|
279
|
+
deps: SlackCredentialDeps = {},
|
|
280
|
+
): Promise<{ readonly signingSecret: boolean; readonly botToken: boolean }> {
|
|
278
281
|
return {
|
|
279
|
-
signingSecret: (
|
|
280
|
-
botToken: (
|
|
282
|
+
signingSecret: (await fromOperatorSecret(SLACK_SIGNING_SECRET_NAME, deps)).length > 0,
|
|
283
|
+
botToken: (await fromOperatorSecret(SLACK_BOT_TOKEN_NAME, deps)).length > 0,
|
|
281
284
|
};
|
|
282
285
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// handler, and eve verifies AGAIN — `verifyInbound` in eve's `slackChannel.js`:
|
|
6
6
|
//
|
|
7
7
|
// signingSecret: credentials?.signingSecret ?? (credentials?.webhookVerifier ? undefined
|
|
8
|
-
// :
|
|
8
|
+
// : <the SLACK_SIGNING_SECRET env var>)
|
|
9
9
|
//
|
|
10
10
|
// `channels/slack.ts` passed NO `credentials`, so eve resolved the secret from `process.env` and
|
|
11
11
|
// nowhere else. Moving OE's own read to the credential store therefore produced a deployment where
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// `webhookVerifier` rather than `signingSecret`: eve takes `signingSecret` as a plain string, which
|
|
23
23
|
// means reading it once at module load. Supplying the verifier instead also makes eve SKIP its
|
|
24
|
-
//
|
|
24
|
+
// env fallback for SLACK_SIGNING_SECRET entirely (see the expression above), so there is no path
|
|
25
25
|
// left on which eve and OE can disagree about which secret this request is held to.
|
|
26
26
|
|
|
27
27
|
import { slackSignatureMatches, SLACK_SIGNATURE_HEADER, SLACK_TIMESTAMP_HEADER } from "../channels/slack-signature.js";
|
package/agent/setup/page.ts
CHANGED
|
@@ -384,9 +384,9 @@ function manifestBotScopes(): string {
|
|
|
384
384
|
//
|
|
385
385
|
// An earlier draft warned on screen 2 that a set `SLACK_BOT_TOKEN` / `SLACK_SIGNING_SECRET` would
|
|
386
386
|
// shadow whatever the run was about to store. That warning is now unreachable, and it was the wrong
|
|
387
|
-
// remedy anyway: `slackSetupOpen` (claim.ts) CLOSES the page entirely when either
|
|
388
|
-
//
|
|
389
|
-
// would ever read. A warning the operator can click past is worse than a page that refuses — it ends
|
|
387
|
+
// remedy anyway: `slackSetupOpen` (claim.ts) CLOSES the page entirely when either operator secret is
|
|
388
|
+
// set (stored, or as its env var on-ramp), because the resolver puts the operator secret first and a
|
|
389
|
+
// run on such a deployment would write credentials nothing would ever read. A warning the operator can click past is worse than a page that refuses — it ends
|
|
390
390
|
// with a Slack app created, credentials stored, the page locked, and no behaviour change at all.
|
|
391
391
|
//
|
|
392
392
|
// The resolver still carries the warning for the case this page cannot cover: a var set AFTER a
|
package/agent/status.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
unifiedDomainSessionKey,
|
|
20
20
|
type SurfaceStatus,
|
|
21
21
|
} from "@open-everywhere/core/agent/adapter-api.js";
|
|
22
|
+
import { slackBotToken, slackSigningSecret, type SlackCredentialDeps } from "./setup/credentials.js";
|
|
22
23
|
|
|
23
24
|
const set = (v: string | undefined): boolean => v !== undefined && v.trim() !== "";
|
|
24
25
|
|
|
@@ -80,11 +81,18 @@ async function eventsVerified(): Promise<SurfaceStatus["eventsVerified"]> {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The bundle's answer for `surfaces.slack`.
|
|
86
|
+
*
|
|
87
|
+
* The two credentials resolve through the bundle's own resolver (`setup/credentials.ts`): the
|
|
88
|
+
* operator secret first (OPE-2650, with env as its on-ramp), then the value a `/setup` run stored. So
|
|
89
|
+
* a deployment configured by `/setup` alone reports `credentials: "yes"`. Before OPE-2650 this read
|
|
90
|
+
* the two env vars only and reported such a deployment as `no`.
|
|
91
|
+
*/
|
|
92
|
+
export async function slackSurfaceStatus(deps: SlackCredentialDeps = {}): Promise<Partial<SurfaceStatus>> {
|
|
93
|
+
const token = set(await slackBotToken(deps));
|
|
86
94
|
return {
|
|
87
|
-
credentials: token && set(
|
|
95
|
+
credentials: token && set(await slackSigningSecret(deps)) ? "yes" : "no",
|
|
88
96
|
// Slack issues a bot token only when the app is installed to a workspace, so holding one is
|
|
89
97
|
// evidence of an install. It is NOT evidence that the install is still live — a revoked app
|
|
90
98
|
// leaves the stale token in place — and the detail in the contract page says so.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-manifest.d.ts","sourceRoot":"","sources":["../../agent/app-manifest.ts"],"names":[],"mappings":"AAeA;;mEAEmE;AACnE,eAAO,MAAM,6BAA6B,oBAAoB,CAAC;AAE/D;yFACyF;AACzF,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAKxD,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,oGAAoG;AACpG,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;0GAC0G;AAC1G,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKnE;AAED;;kFAEkF;AAClF,MAAM,WAAW,uBAAuB;IACtC;;yBAEqB;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;0CAEsC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;8CAC0C;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7F;AAED,8FAA8F;AAC9F,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAU/C;;6DAE6D;AAC7D,eAAO,MAAM,oCAAoC,uBAAuB,CAAC;AAEzE;iGACiG;AACjG,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAM/E;AAQD;+DAC+D;AAC/D,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"app-manifest.d.ts","sourceRoot":"","sources":["../../agent/app-manifest.ts"],"names":[],"mappings":"AAeA;;mEAEmE;AACnE,eAAO,MAAM,6BAA6B,oBAAoB,CAAC;AAE/D;yFACyF;AACzF,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAKxD,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,oGAAoG;AACpG,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;0GAC0G;AAC1G,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKnE;AAED;;kFAEkF;AAClF,MAAM,WAAW,uBAAuB;IACtC;;yBAEqB;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;0CAEsC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;8CAC0C;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7F;AAED,8FAA8F;AAC9F,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAU/C;;6DAE6D;AAC7D,eAAO,MAAM,oCAAoC,uBAAuB,CAAC;AAEzE;iGACiG;AACjG,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAM/E;AAQD;+DAC+D;AAC/D,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAwNnF;AAED,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAElE,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAQrD,CAAC;AAEF;;8BAE8B;AAC9B,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKvD,CAAC"}
|
|
@@ -211,7 +211,9 @@ export function slackAppManifestTemplate(opts = {}) {
|
|
|
211
211
|
"lists:read",
|
|
212
212
|
"lists:write",
|
|
213
213
|
// The side panel (OPE-2457): Slack requires it for `app_context_changed`, the event whose
|
|
214
|
-
// subscription makes Slack attach the viewed room to each side-panel `message.im`.
|
|
214
|
+
// subscription makes Slack attach the viewed room to each side-panel `message.im`. It also
|
|
215
|
+
// covers `assistant.threads.setStatus`, the status line a turn shows while it works: the
|
|
216
|
+
// mention door's, and the DM seat's (OPE-2629, channels/slack-agent-session.ts).
|
|
215
217
|
"assistant:write",
|
|
216
218
|
// Slack requires this bot scope on an app whose people connect to Slack's MCP server
|
|
217
219
|
// (OPE-2421). Declared only when the app declares "search Slack as you".
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-manifest.js","sourceRoot":"","sources":["../../agent/app-manifest.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"app-manifest.js","sourceRoot":"","sources":["../../agent/app-manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cancelAnchoredTurn, type AttachSessionForCancel, type CancelResult, type SurfaceSessionPinner, type SurfaceSessionSetter } from "@open-everywhere/core/agent/adapter-api.js";
|
|
1
|
+
import { cancelAnchoredTurn, type AttachSessionForCancel, type CancelResult, type SurfaceSessionPinner, type SurfaceSessionSetter, type SurfaceSessionStepper } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
2
|
import { type SlackEventEnvelope } from "./slack-events.js";
|
|
3
3
|
/** Slack's event for a press of the session's Stop button. */
|
|
4
4
|
export declare const AGENT_SESSION_STOPPED_EVENT = "agent_session_stopped";
|
|
@@ -44,8 +44,28 @@ export declare function slackAgentSessionOf(attributes: Readonly<Record<string,
|
|
|
44
44
|
* `suspended` long after the delivery's auth is gone. Ids only, never text. Pure.
|
|
45
45
|
*/
|
|
46
46
|
export declare const slackSurfaceSessionPin: SurfaceSessionPinner;
|
|
47
|
-
/** The
|
|
47
|
+
/** The status line a DM turn shows at its start — the mention door's own words (slack-working-signs.ts). */
|
|
48
|
+
export declare const SLACK_DM_WORKING_STATUS = "Working...";
|
|
49
|
+
/** Test-only: forget which sign failures were already logged. */
|
|
50
|
+
export declare function _resetSignWarningsForTest(): void;
|
|
51
|
+
/** The message a DM-door delivery's signs ride, or undefined when the door stamped none. Pure. */
|
|
52
|
+
export declare function slackWorkingSignsOf(attributes: Readonly<Record<string, unknown>>): {
|
|
53
|
+
readonly channelId: string;
|
|
54
|
+
readonly threadTs: string;
|
|
55
|
+
readonly teamId?: string;
|
|
56
|
+
} | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* The Slack setter core's turn-anchor hook calls (registered in register.ts). It sets the agent
|
|
59
|
+
* session's state for a 1:1 DM, and the working signs for every DM-door delivery. It answers the id
|
|
60
|
+
* of the conversation it named: the agent session, else the message the signs ride.
|
|
61
|
+
*/
|
|
48
62
|
export declare function slackSurfaceSessionSetter(form?: SlackFormCall): SurfaceSessionSetter;
|
|
63
|
+
/**
|
|
64
|
+
* The Slack stepper (OPE-2632): each step of a DM-door turn shows its code-owned label on the status
|
|
65
|
+
* line, or a status verb — the mention door's own `actions.requested` text (slack-working-signs.ts
|
|
66
|
+
* `slackStepStatus`). A delivery with no sign stamp shows nothing here.
|
|
67
|
+
*/
|
|
68
|
+
export declare function slackSurfaceSessionStepper(form?: SlackFormCall): SurfaceSessionStepper;
|
|
49
69
|
/** One press of the Stop button, as the route reads it. */
|
|
50
70
|
export interface ParsedSessionStop {
|
|
51
71
|
readonly channelId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-agent-session.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-agent-session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slack-agent-session.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-agent-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
// registers the Slack setter: it names the session from the delivery's auth attributes. The
|
|
22
22
|
// person's next message sets `processing`, then parks `active` unless it reports again that it
|
|
23
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, OPE-2632). The same setter, and the stepper, show the mention door's
|
|
25
|
+
// signs on a DM-door turn — a 1:1 DM or a group DM: 👀, the status line with its step labels, and
|
|
26
|
+
// at the end no sign, or 🛑 when the turn failed for good. See "the working signs" below.
|
|
24
27
|
// • A FORWARDED JOB's WAIT (OPE-2536). A job the DM seat forwards to the work session can end waiting
|
|
25
28
|
// on the person: it asked a question in the room (`ask_room`), or it needs their own sign-in. The
|
|
26
29
|
// work session has no `update_status`, so the host sets the session `suspended` at the job's
|
|
@@ -34,8 +37,9 @@
|
|
|
34
37
|
//
|
|
35
38
|
// Every call here is best-effort. A workspace without Slack's agent features answers an error, which
|
|
36
39
|
// is logged and changes nothing else.
|
|
37
|
-
import { cancelAnchoredTurn, resolvePartition, turnAnchorKey, turnChannelKey, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
40
|
+
import { cancelAnchoredTurn, resolvePartition, swallowRoomOp, turnAnchorKey, turnChannelKey, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
38
41
|
import { bindableForSlackIds, slackForm } from "./slack-events.js";
|
|
42
|
+
import { setSlackStatusReaction, setSlackThreadStatus, SLACK_WORKING_STATUS, slackStepStatus } from "./slack-working-signs.js";
|
|
39
43
|
/** Slack's event for a press of the session's Stop button. */
|
|
40
44
|
export const AGENT_SESSION_STOPPED_EVENT = "agent_session_stopped";
|
|
41
45
|
/** The longest title OE sets. Slack accepts 200 characters; a session list shows far fewer. */
|
|
@@ -129,14 +133,131 @@ export const slackSurfaceSessionPin = (attributes) => {
|
|
|
129
133
|
attributes: { channel_id: session.channelId, channel_type: "im", [AGENT_SESSION_TS_ATTRIBUTE]: session.threadTs },
|
|
130
134
|
};
|
|
131
135
|
};
|
|
132
|
-
|
|
136
|
+
// --- the working signs ------------------------------------------------------------------------------
|
|
137
|
+
//
|
|
138
|
+
// 🔴 THE DM DOOR's WORKING SIGNS (OPE-2629, OPE-2632). A 1:1 DM and a group DM run on the `unified`
|
|
139
|
+
// channel, so none of the Slack channel's own handlers reach them: `onInbound` (the 👀), the
|
|
140
|
+
// `turn.started` and `actions.requested` renderers (the status line and its step labels) and the
|
|
141
|
+
// `session.failed` arm (the 🛑) serve only eve's native mention door. Core's turn-anchor hook calls
|
|
142
|
+
// the setter below at a DM turn's start (`processing`), at its park (`active` or `suspended`) and at
|
|
143
|
+
// `session.failed` (the park's state with `{ failed: true }`), and the stepper at each step. The signs
|
|
144
|
+
// ride those calls, through the SAME functions the mention door calls (slack-working-signs.ts):
|
|
145
|
+
// • the status reaction on the message the DM door stamped (`working_signs_ts`: the message itself
|
|
146
|
+
// when top-level, its thread root for a threaded reply): 👀 at the start, none at the park, 🛑
|
|
147
|
+
// when the turn failed for good;
|
|
148
|
+
// • the status line on that root: `Working...` at the start, each step's code-owned label (or a
|
|
149
|
+
// status verb) at each step, and cleared at every end. The flat DM reply does not clear it.
|
|
150
|
+
// Only a delivery the DM door stamped shows a sign: a person's 1:1 or group-DM message, and a follow-up
|
|
151
|
+
// press inside a thread (on its thread root). A channel turn carries no stamp (the mention door draws
|
|
152
|
+
// its own), and neither does a shortcut or a top-level press (a minted ts names no message) or a
|
|
153
|
+
// forwarded job's PIN (its settlement must not clear a running turn's 👀).
|
|
154
|
+
/** The status line a DM turn shows at its start — the mention door's own words (slack-working-signs.ts). */
|
|
155
|
+
export const SLACK_DM_WORKING_STATUS = SLACK_WORKING_STATUS;
|
|
156
|
+
/** The auth attribute the DM door stamps with the message the signs ride (slack-front.ts `scheduleDmWake`). */
|
|
157
|
+
const WORKING_SIGNS_TS_ATTRIBUTE = "working_signs_ts";
|
|
158
|
+
/**
|
|
159
|
+
* What an operator does when one sign call fails with a setup error (`SIGN_SETUP_ERRORS`). The sign
|
|
160
|
+
* needs a scope or the agent view, and both come from the app manifest, so the fix is the manifest
|
|
161
|
+
* paste. Logged once per method and error, so a workspace without the feature does not log it on
|
|
162
|
+
* every turn.
|
|
163
|
+
*/
|
|
164
|
+
const SIGN_SETUP_FIX = {
|
|
165
|
+
"reactions.add": "the 👀 and the 🛑 need the reactions:write bot scope",
|
|
166
|
+
"reactions.remove": "the 👀 and the 🛑 need the reactions:write bot scope",
|
|
167
|
+
"assistant.threads.setStatus": "the DM status line needs the assistant:write bot scope and the agent view (features.agent_view)",
|
|
168
|
+
};
|
|
169
|
+
/** Slack's answers that mean the app lacks a scope or a feature: a setup gap, not a passing fault. */
|
|
170
|
+
const SIGN_SETUP_ERRORS = new Set(["missing_scope", "feature_disabled", "not_allowed_token_type", "not_allowed", "access_denied"]);
|
|
171
|
+
/** Slack's answers that say the sign is already in the state asked for. */
|
|
172
|
+
const SIGN_NO_OP_ERRORS = new Set(["already_reacted", "no_reaction"]);
|
|
173
|
+
const warnedSignErrors = new Set();
|
|
174
|
+
/** The DM door's sign seam over a form call. Never throws; a failure is logged once per method and error, with the setup fix. */
|
|
175
|
+
function formSignRequest(form) {
|
|
176
|
+
return async (method, params) => {
|
|
177
|
+
let error;
|
|
178
|
+
try {
|
|
179
|
+
const res = await form(method, new URLSearchParams({ ...params }).toString());
|
|
180
|
+
if (res?.ok === true)
|
|
181
|
+
return;
|
|
182
|
+
error = String(res?.error ?? "no answer");
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
error = String(err);
|
|
186
|
+
}
|
|
187
|
+
if (SIGN_NO_OP_ERRORS.has(error))
|
|
188
|
+
return;
|
|
189
|
+
const key = `${method}:${error}`;
|
|
190
|
+
if (warnedSignErrors.has(key))
|
|
191
|
+
return;
|
|
192
|
+
warnedSignErrors.add(key);
|
|
193
|
+
const fix = SIGN_SETUP_ERRORS.has(error)
|
|
194
|
+
? `${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`
|
|
195
|
+
: "the sign is best-effort and the turn goes on";
|
|
196
|
+
console.warn(`[open-everywhere:slack-session] ${method} failed (${error}) — ${fix}. Logged once per process.`);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Test-only: forget which sign failures were already logged. */
|
|
200
|
+
export function _resetSignWarningsForTest() {
|
|
201
|
+
warnedSignErrors.clear();
|
|
202
|
+
}
|
|
203
|
+
/** The message a DM-door delivery's signs ride, or undefined when the door stamped none. Pure. */
|
|
204
|
+
export function slackWorkingSignsOf(attributes) {
|
|
205
|
+
if (attributes.channel_type !== "im" && attributes.channel_type !== "mpim")
|
|
206
|
+
return undefined;
|
|
207
|
+
const channelId = attributes.channel_id;
|
|
208
|
+
const stamped = attributes[WORKING_SIGNS_TS_ATTRIBUTE];
|
|
209
|
+
if (typeof channelId !== "string" || channelId.length === 0 || typeof stamped !== "string" || stamped.length === 0)
|
|
210
|
+
return undefined;
|
|
211
|
+
return { channelId, threadTs: stamped, ...(typeof attributes.team_id === "string" ? { teamId: attributes.team_id } : {}) };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Set the DM turn's signs for one state: 👀 and `Working...` at `processing`; at a settle, no
|
|
215
|
+
* reaction (or 🛑 when the turn failed) and a cleared status line. The two calls run in parallel,
|
|
216
|
+
* because the turn waits on this setter. A swallowed room op (the egress spoof) sends neither.
|
|
217
|
+
*/
|
|
218
|
+
async function setSlackDmWorkingSigns(root, status, end, request) {
|
|
219
|
+
const working = status === "processing";
|
|
220
|
+
if (swallowRoomOp(`a slack DM working sign (${working ? "start" : end?.failed === true ? "failed" : "end"})`))
|
|
221
|
+
return;
|
|
222
|
+
await Promise.all([
|
|
223
|
+
setSlackStatusReaction(request, { channel: root.channelId, timestamp: root.threadTs }, working ? "working" : end?.failed === true ? "blocked" : "none"),
|
|
224
|
+
setSlackThreadStatus(request, root, working ? SLACK_WORKING_STATUS : ""),
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The Slack setter core's turn-anchor hook calls (registered in register.ts). It sets the agent
|
|
229
|
+
* session's state for a 1:1 DM, and the working signs for every DM-door delivery. It answers the id
|
|
230
|
+
* of the conversation it named: the agent session, else the message the signs ride.
|
|
231
|
+
*/
|
|
133
232
|
export function slackSurfaceSessionSetter(form = slackForm) {
|
|
134
|
-
|
|
233
|
+
const request = formSignRequest(form);
|
|
234
|
+
return async (attributes, status, end) => {
|
|
135
235
|
const session = slackAgentSessionOf(attributes);
|
|
136
|
-
|
|
236
|
+
const signs = slackWorkingSignsOf(attributes);
|
|
237
|
+
const named = session ?? signs;
|
|
238
|
+
if (!named)
|
|
137
239
|
return undefined;
|
|
138
|
-
await
|
|
139
|
-
|
|
240
|
+
await Promise.all([
|
|
241
|
+
...(session ? [setSlackSessionStatus({ ...session, status }, form)] : []),
|
|
242
|
+
...(signs ? [setSlackDmWorkingSigns(signs, status, end, request)] : []),
|
|
243
|
+
]);
|
|
244
|
+
return `${named.channelId}:${named.threadTs}`;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The Slack stepper (OPE-2632): each step of a DM-door turn shows its code-owned label on the status
|
|
249
|
+
* line, or a status verb — the mention door's own `actions.requested` text (slack-working-signs.ts
|
|
250
|
+
* `slackStepStatus`). A delivery with no sign stamp shows nothing here.
|
|
251
|
+
*/
|
|
252
|
+
export function slackSurfaceSessionStepper(form = slackForm) {
|
|
253
|
+
const request = formSignRequest(form);
|
|
254
|
+
return async (attributes, actions) => {
|
|
255
|
+
const signs = slackWorkingSignsOf(attributes);
|
|
256
|
+
if (!signs)
|
|
257
|
+
return;
|
|
258
|
+
if (swallowRoomOp("a slack DM step label"))
|
|
259
|
+
return;
|
|
260
|
+
await setSlackThreadStatus(request, signs, slackStepStatus(actions, { channelId: signs.channelId, teamId: signs.teamId ?? null }));
|
|
140
261
|
};
|
|
141
262
|
}
|
|
142
263
|
/** Parse an `agent_session_stopped` envelope, or null for anything else. Pure. */
|