@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.
Files changed (68) hide show
  1. package/agent/app-manifest.ts +3 -1
  2. package/agent/channels/slack-agent-session.ts +131 -5
  3. package/agent/channels/slack-events.ts +59 -5
  4. package/agent/channels/slack-front.ts +15 -8
  5. package/agent/channels/slack-identity.ts +42 -1
  6. package/agent/channels/slack-web-api.ts +3 -3
  7. package/agent/channels/slack-working-signs.ts +71 -0
  8. package/agent/channels/slack.ts +36 -50
  9. package/agent/docs-generated.ts +50 -13
  10. package/agent/ops.ts +4 -0
  11. package/agent/register.ts +4 -2
  12. package/agent/setup/claim.ts +9 -8
  13. package/agent/setup/credentials.ts +82 -79
  14. package/agent/setup/eve-credentials.ts +2 -2
  15. package/agent/setup/page.ts +3 -3
  16. package/agent/status.ts +12 -4
  17. package/dist/agent/app-manifest.d.ts.map +1 -1
  18. package/dist/agent/app-manifest.js +3 -1
  19. package/dist/agent/app-manifest.js.map +1 -1
  20. package/dist/agent/channels/slack-agent-session.d.ts +22 -2
  21. package/dist/agent/channels/slack-agent-session.d.ts.map +1 -1
  22. package/dist/agent/channels/slack-agent-session.js +127 -6
  23. package/dist/agent/channels/slack-agent-session.js.map +1 -1
  24. package/dist/agent/channels/slack-events.d.ts +7 -3
  25. package/dist/agent/channels/slack-events.d.ts.map +1 -1
  26. package/dist/agent/channels/slack-events.js +57 -6
  27. package/dist/agent/channels/slack-events.js.map +1 -1
  28. package/dist/agent/channels/slack-front.d.ts.map +1 -1
  29. package/dist/agent/channels/slack-front.js +16 -9
  30. package/dist/agent/channels/slack-front.js.map +1 -1
  31. package/dist/agent/channels/slack-identity.d.ts +33 -0
  32. package/dist/agent/channels/slack-identity.d.ts.map +1 -1
  33. package/dist/agent/channels/slack-identity.js +35 -1
  34. package/dist/agent/channels/slack-identity.js.map +1 -1
  35. package/dist/agent/channels/slack-web-api.js +3 -3
  36. package/dist/agent/channels/slack-web-api.js.map +1 -1
  37. package/dist/agent/channels/slack-working-signs.d.ts +34 -0
  38. package/dist/agent/channels/slack-working-signs.d.ts.map +1 -0
  39. package/dist/agent/channels/slack-working-signs.js +63 -0
  40. package/dist/agent/channels/slack-working-signs.js.map +1 -0
  41. package/dist/agent/channels/slack.d.ts.map +1 -1
  42. package/dist/agent/channels/slack.js +28 -46
  43. package/dist/agent/channels/slack.js.map +1 -1
  44. package/dist/agent/docs-generated.d.ts.map +1 -1
  45. package/dist/agent/docs-generated.js +50 -13
  46. package/dist/agent/docs-generated.js.map +1 -1
  47. package/dist/agent/ops.d.ts.map +1 -1
  48. package/dist/agent/ops.js +4 -1
  49. package/dist/agent/ops.js.map +1 -1
  50. package/dist/agent/register.d.ts.map +1 -1
  51. package/dist/agent/register.js +4 -2
  52. package/dist/agent/register.js.map +1 -1
  53. package/dist/agent/setup/claim.d.ts.map +1 -1
  54. package/dist/agent/setup/claim.js +9 -8
  55. package/dist/agent/setup/claim.js.map +1 -1
  56. package/dist/agent/setup/credentials.d.ts +21 -20
  57. package/dist/agent/setup/credentials.d.ts.map +1 -1
  58. package/dist/agent/setup/credentials.js +71 -75
  59. package/dist/agent/setup/credentials.js.map +1 -1
  60. package/dist/agent/setup/eve-credentials.js +2 -2
  61. package/dist/agent/setup/eve-credentials.js.map +1 -1
  62. package/dist/agent/setup/page.js +3 -3
  63. package/dist/agent/setup/page.js.map +1 -1
  64. package/dist/agent/status.d.ts +10 -2
  65. package/dist/agent/status.d.ts.map +1 -1
  66. package/dist/agent/status.js +12 -4
  67. package/dist/agent/status.js.map +1 -1
  68. 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: `process.env.SLACK_SIGNING_SECRET` and
5
- // `process.env.SLACK_BOT_TOKEN`, read inline at each call site. A `/setup` run now captures both
6
- // into the credential store, so there are two homes and an order is needed.
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
- // 🔴 THE ORDER IS ENV-FIRST, AND IT IS THE OPPOSITE OF `getOperatorSecret`'s. Core's operator-secret
9
- // resolver puts the db store first and treats env as a fallback on its way out (OPE-408: "db-over-env
10
- // is the DEFAULT home for secrets"). This module inverts that, deliberately, on the OPE-2231 ruling
11
- // ("env as fallback (env wins if set, for existing installs)"). The reason is the migration, not a
12
- // disagreement about where secrets belong:
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. Store-first could: a `/setup` run
16
- // somebody started and abandoned, or a second workspace's install, would silently take over a
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
- // • Env-first cannot do that. A deployment with the env vars set resolves to exactly the value it
20
- // resolved to before this commit, every time. It reads the store AT MOST ONCE PER NAME PER
21
- // PROCESS — only to decide whether to warn that the env var is shadowing something — and never
22
- // again, so the per-request hot path for an existing install costs the same property read it
23
- // always did.
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 has the env vars
28
- // set will find the stored credentials ignored. {@link slackCredentialSourceNotice} is what makes that
29
- // visible instead of mysterious — the page says it before the run, and the resolver logs it once.
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. An env hit is not memoised because it is already a
48
- // property read. A MISS is remembered only BRIEFLY ({@link MISS_TTL_MS}) — long enough that an
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 env var holding the app's request-signing secret — the pre-OPE-2231 home, still first. */
57
- const SLACK_SIGNING_SECRET_ENV = "SLACK_SIGNING_SECRET";
58
- /** The env var holding the workspace bot token — the pre-OPE-2231 home, still first. */
59
- const SLACK_BOT_TOKEN_ENV = "SLACK_BOT_TOKEN";
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
- export type SlackSecretSource = "env" | "store";
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 `process.env`. */
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(envName: string): string {
145
+ function slackCredentialSourceNotice(name: string): string {
144
146
  return (
145
- `[open-everywhere:slack-setup] ${envName} is set in the environment, so this deployment uses the environment value and ` +
146
- `IGNORES the one in the credential store. That is the documented order (OPE-2231): env wins, so landing this change cannot ` +
147
- `alter which Slack app an existing deployment speaks as. To use the stored value instead, unset ${envName} and redeploy.`
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: env if set, else the credential store, else `undefined`.
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 env var either. Each caller fails in its own way (the front route 401s; an outbound
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
- envName: string,
161
- fromEnv: string,
163
+ name: string,
164
+ fromOperator: string,
162
165
  ref: string,
163
166
  deps: SlackCredentialDeps = {},
164
167
  ): Promise<ResolvedSlackSecret | undefined> {
165
- // 🔴 THE ENV VALUE IS PASSED IN, NOT READ HERE, and that is a gate's requirement rather than a
166
- // style choice. `lint:env-docs` resolves `env[SOME_CONST]` through the const so every variable the
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 (fromEnv.length > 0) {
184
- // Only say the env is shadowing a stored value when one is ACTUALLY stored — the notice is for
185
- // the operator who ran `/setup` and is about to wonder why nothing changed, not for every
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(envName)) {
195
- shadowChecked.add(envName);
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(envName);
199
- console.warn(slackCredentialSourceNotice(envName));
196
+ noticed.add(name);
197
+ console.warn(slackCredentialSourceNotice(name));
200
198
  }
201
199
  }
202
- return { value: fromEnv, source: "env" };
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 env = deps.env ?? process.env;
241
- const fromEnv = (env[SLACK_SIGNING_SECRET_ENV] ?? "").trim();
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 env = deps.env ?? process.env;
248
- const fromEnv = (env[SLACK_BOT_TOKEN_ENV] ?? "").trim();
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}: env first, then the store.
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 ENV VAR ANSWERS WITHOUT THE STORE. `slackBotToken` reads the store once per process even
257
- * when the env var is set, for the env-shadows-store notice. On the heartbeat path that read would cost
258
- * a process that does no Slack work a store round trip, and a slow store would lose the census's
259
- * two-second race: an env-configured deployment, which reported `slack` before OPE-2241, would then
260
- * report it absent. So the env home is checked here first, and only an empty env var reads the store,
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
- const env = deps.env ?? process.env;
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 `name`'s env var set, for the ONE caller that needs to ask without resolving — the `/setup`
271
- * page's "this env var will shadow what you are about to store" notice.
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 slackSecretEnvPresent(deps: SlackCredentialDeps = {}): { readonly signingSecret: boolean; readonly botToken: boolean } {
277
- const env = deps.env ?? process.env;
278
+ export async function slackOperatorSecretPresent(
279
+ deps: SlackCredentialDeps = {},
280
+ ): Promise<{ readonly signingSecret: boolean; readonly botToken: boolean }> {
278
281
  return {
279
- signingSecret: (env[SLACK_SIGNING_SECRET_ENV] ?? "").trim().length > 0,
280
- botToken: (env[SLACK_BOT_TOKEN_ENV] ?? "").trim().length > 0,
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
- // : process.env.SLACK_SIGNING_SECRET)
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
- // `process.env.SLACK_SIGNING_SECRET` fallback entirely (see the expression above), so there is no path
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";
@@ -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 variable is set,
388
- // because the resolver is env-first and a run on such a deployment would write credentials nothing
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
- /** The bundle's answer for `surfaces.slack`. */
84
- export async function slackSurfaceStatus(env: NodeJS.ProcessEnv = process.env): Promise<Partial<SurfaceStatus>> {
85
- const token = set(env.SLACK_BOT_TOKEN);
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(env.SLACK_SIGNING_SECRET) ? "yes" : "no",
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,CAsNnF;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"}
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 Slack setter core's turn-anchor hook calls (registered in register.ts). */
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":"AAqCA,OAAO,EACL,kBAAkB,EAIlB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAkC,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5F,8DAA8D;AAC9D,eAAO,MAAM,2BAA2B,0BAA0B,CAAC;AAEnE,+FAA+F;AAC/F,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,kCAAkC;AAClC,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAKvE,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AAS3G;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAsB9E;AAID,4CAA4C;AAC5C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6DAA6D;AAC7D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,CAI3E;AAED,yGAAyG;AACzG,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,GAAE,aAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,CAU3H;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAMxJ;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBAOpC,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,aAAyB,GAAG,oBAAoB,CAO/F;AAID,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,IAAI,CAgB/F;AAOD,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;IAC/C,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC5C,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzE;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CAoB/G"}
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
- /** The Slack setter core's turn-anchor hook calls (registered in register.ts). */
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
- return async (attributes, status) => {
233
+ const request = formSignRequest(form);
234
+ return async (attributes, status, end) => {
135
235
  const session = slackAgentSessionOf(attributes);
136
- if (!session)
236
+ const signs = slackWorkingSignsOf(attributes);
237
+ const named = session ?? signs;
238
+ if (!named)
137
239
  return undefined;
138
- await setSlackSessionStatus({ ...session, status }, form);
139
- return `${session.channelId}:${session.threadTs}`;
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. */