@open-everywhere/provider-slack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +47 -0
  4. package/agent/access/slack-attachment-fetch.ts +168 -0
  5. package/agent/app-manifest.ts +149 -0
  6. package/agent/channels/slack-approval-card.ts +275 -0
  7. package/agent/channels/slack-attachments.ts +195 -0
  8. package/agent/channels/slack-commands.ts +227 -0
  9. package/agent/channels/slack-context.ts +185 -0
  10. package/agent/channels/slack-events.ts +5406 -0
  11. package/agent/channels/slack-front.ts +2349 -0
  12. package/agent/channels/slack-identity.ts +520 -0
  13. package/agent/channels/slack-input-response.ts +162 -0
  14. package/agent/channels/slack-mrkdwn.ts +61 -0
  15. package/agent/channels/slack-origin-codec.ts +62 -0
  16. package/agent/channels/slack-post-arm.ts +314 -0
  17. package/agent/channels/slack-read.ts +248 -0
  18. package/agent/channels/slack-route-hold.ts +107 -0
  19. package/agent/channels/slack-shortcut.ts +336 -0
  20. package/agent/channels/slack-signature.ts +53 -0
  21. package/agent/channels/slack-spend-gate.ts +62 -0
  22. package/agent/channels/slack-tenant-fence.ts +99 -0
  23. package/agent/channels/slack-wake-backscroll.ts +604 -0
  24. package/agent/channels/slack-web-api.ts +178 -0
  25. package/agent/channels/slack-webhook-verifier.ts +116 -0
  26. package/agent/channels/slack.ts +1266 -0
  27. package/agent/contributions.ts +32 -0
  28. package/agent/docs-generated.ts +15 -0
  29. package/agent/ops.ts +50 -0
  30. package/agent/provider-events.ts +91 -0
  31. package/agent/register.ts +144 -0
  32. package/agent/surface-parity.ts +99 -0
  33. package/dist/agent/access/slack-attachment-fetch.d.ts +15 -0
  34. package/dist/agent/access/slack-attachment-fetch.d.ts.map +1 -0
  35. package/dist/agent/access/slack-attachment-fetch.js +151 -0
  36. package/dist/agent/access/slack-attachment-fetch.js.map +1 -0
  37. package/dist/agent/app-manifest.d.ts +15 -0
  38. package/dist/agent/app-manifest.d.ts.map +1 -0
  39. package/dist/agent/app-manifest.js +141 -0
  40. package/dist/agent/app-manifest.js.map +1 -0
  41. package/dist/agent/channels/slack-approval-card.d.ts +71 -0
  42. package/dist/agent/channels/slack-approval-card.d.ts.map +1 -0
  43. package/dist/agent/channels/slack-approval-card.js +232 -0
  44. package/dist/agent/channels/slack-approval-card.js.map +1 -0
  45. package/dist/agent/channels/slack-attachments.d.ts +66 -0
  46. package/dist/agent/channels/slack-attachments.d.ts.map +1 -0
  47. package/dist/agent/channels/slack-attachments.js +174 -0
  48. package/dist/agent/channels/slack-attachments.js.map +1 -0
  49. package/dist/agent/channels/slack-commands.d.ts +27 -0
  50. package/dist/agent/channels/slack-commands.d.ts.map +1 -0
  51. package/dist/agent/channels/slack-commands.js +208 -0
  52. package/dist/agent/channels/slack-commands.js.map +1 -0
  53. package/dist/agent/channels/slack-context.d.ts +26 -0
  54. package/dist/agent/channels/slack-context.d.ts.map +1 -0
  55. package/dist/agent/channels/slack-context.js +178 -0
  56. package/dist/agent/channels/slack-context.js.map +1 -0
  57. package/dist/agent/channels/slack-events.d.ts +1654 -0
  58. package/dist/agent/channels/slack-events.d.ts.map +1 -0
  59. package/dist/agent/channels/slack-events.js +4240 -0
  60. package/dist/agent/channels/slack-events.js.map +1 -0
  61. package/dist/agent/channels/slack-front.d.ts +387 -0
  62. package/dist/agent/channels/slack-front.d.ts.map +1 -0
  63. package/dist/agent/channels/slack-front.js +1958 -0
  64. package/dist/agent/channels/slack-front.js.map +1 -0
  65. package/dist/agent/channels/slack-identity.d.ts +245 -0
  66. package/dist/agent/channels/slack-identity.d.ts.map +1 -0
  67. package/dist/agent/channels/slack-identity.js +405 -0
  68. package/dist/agent/channels/slack-identity.js.map +1 -0
  69. package/dist/agent/channels/slack-input-response.d.ts +26 -0
  70. package/dist/agent/channels/slack-input-response.d.ts.map +1 -0
  71. package/dist/agent/channels/slack-input-response.js +127 -0
  72. package/dist/agent/channels/slack-input-response.js.map +1 -0
  73. package/dist/agent/channels/slack-mrkdwn.d.ts +3 -0
  74. package/dist/agent/channels/slack-mrkdwn.d.ts.map +1 -0
  75. package/dist/agent/channels/slack-mrkdwn.js +53 -0
  76. package/dist/agent/channels/slack-mrkdwn.js.map +1 -0
  77. package/dist/agent/channels/slack-origin-codec.d.ts +7 -0
  78. package/dist/agent/channels/slack-origin-codec.d.ts.map +1 -0
  79. package/dist/agent/channels/slack-origin-codec.js +55 -0
  80. package/dist/agent/channels/slack-origin-codec.js.map +1 -0
  81. package/dist/agent/channels/slack-post-arm.d.ts +54 -0
  82. package/dist/agent/channels/slack-post-arm.d.ts.map +1 -0
  83. package/dist/agent/channels/slack-post-arm.js +268 -0
  84. package/dist/agent/channels/slack-post-arm.js.map +1 -0
  85. package/dist/agent/channels/slack-read.d.ts +84 -0
  86. package/dist/agent/channels/slack-read.d.ts.map +1 -0
  87. package/dist/agent/channels/slack-read.js +205 -0
  88. package/dist/agent/channels/slack-read.js.map +1 -0
  89. package/dist/agent/channels/slack-route-hold.d.ts +34 -0
  90. package/dist/agent/channels/slack-route-hold.d.ts.map +1 -0
  91. package/dist/agent/channels/slack-route-hold.js +97 -0
  92. package/dist/agent/channels/slack-route-hold.js.map +1 -0
  93. package/dist/agent/channels/slack-shortcut.d.ts +162 -0
  94. package/dist/agent/channels/slack-shortcut.d.ts.map +1 -0
  95. package/dist/agent/channels/slack-shortcut.js +262 -0
  96. package/dist/agent/channels/slack-shortcut.js.map +1 -0
  97. package/dist/agent/channels/slack-signature.d.ts +19 -0
  98. package/dist/agent/channels/slack-signature.d.ts.map +1 -0
  99. package/dist/agent/channels/slack-signature.js +50 -0
  100. package/dist/agent/channels/slack-signature.js.map +1 -0
  101. package/dist/agent/channels/slack-spend-gate.d.ts +22 -0
  102. package/dist/agent/channels/slack-spend-gate.d.ts.map +1 -0
  103. package/dist/agent/channels/slack-spend-gate.js +48 -0
  104. package/dist/agent/channels/slack-spend-gate.js.map +1 -0
  105. package/dist/agent/channels/slack-tenant-fence.d.ts +61 -0
  106. package/dist/agent/channels/slack-tenant-fence.d.ts.map +1 -0
  107. package/dist/agent/channels/slack-tenant-fence.js +88 -0
  108. package/dist/agent/channels/slack-tenant-fence.js.map +1 -0
  109. package/dist/agent/channels/slack-wake-backscroll.d.ts +188 -0
  110. package/dist/agent/channels/slack-wake-backscroll.d.ts.map +1 -0
  111. package/dist/agent/channels/slack-wake-backscroll.js +473 -0
  112. package/dist/agent/channels/slack-wake-backscroll.js.map +1 -0
  113. package/dist/agent/channels/slack-web-api.d.ts +46 -0
  114. package/dist/agent/channels/slack-web-api.d.ts.map +1 -0
  115. package/dist/agent/channels/slack-web-api.js +147 -0
  116. package/dist/agent/channels/slack-web-api.js.map +1 -0
  117. package/dist/agent/channels/slack-webhook-verifier.d.ts +5 -0
  118. package/dist/agent/channels/slack-webhook-verifier.d.ts.map +1 -0
  119. package/dist/agent/channels/slack-webhook-verifier.js +112 -0
  120. package/dist/agent/channels/slack-webhook-verifier.js.map +1 -0
  121. package/dist/agent/channels/slack.d.ts +37 -0
  122. package/dist/agent/channels/slack.d.ts.map +1 -0
  123. package/dist/agent/channels/slack.js +1169 -0
  124. package/dist/agent/channels/slack.js.map +1 -0
  125. package/dist/agent/contributions.d.ts +17 -0
  126. package/dist/agent/contributions.d.ts.map +1 -0
  127. package/dist/agent/contributions.js +32 -0
  128. package/dist/agent/contributions.js.map +1 -0
  129. package/dist/agent/docs-generated.d.ts +3 -0
  130. package/dist/agent/docs-generated.d.ts.map +1 -0
  131. package/dist/agent/docs-generated.js +13 -0
  132. package/dist/agent/docs-generated.js.map +1 -0
  133. package/dist/agent/ops.d.ts +3 -0
  134. package/dist/agent/ops.d.ts.map +1 -0
  135. package/dist/agent/ops.js +38 -0
  136. package/dist/agent/ops.js.map +1 -0
  137. package/dist/agent/provider-events.d.ts +3 -0
  138. package/dist/agent/provider-events.d.ts.map +1 -0
  139. package/dist/agent/provider-events.js +84 -0
  140. package/dist/agent/provider-events.js.map +1 -0
  141. package/dist/agent/register.d.ts +5 -0
  142. package/dist/agent/register.d.ts.map +1 -0
  143. package/dist/agent/register.js +118 -0
  144. package/dist/agent/register.js.map +1 -0
  145. package/dist/agent/surface-parity.d.ts +3 -0
  146. package/dist/agent/surface-parity.d.ts.map +1 -0
  147. package/dist/agent/surface-parity.js +95 -0
  148. package/dist/agent/surface-parity.js.map +1 -0
  149. package/package.json +209 -0
  150. package/tsconfig.build.json +22 -0
  151. package/tsconfig.json +15 -0
@@ -0,0 +1,53 @@
1
+ // slack-mrkdwn — markdown (what the model writes) → Slack's mrkdwn dialect (OPE-870).
2
+ //
3
+ // Slack renders mrkdwn, not GitHub markdown: `*bold*` (not `**bold**`), `_italic_`, `~strike~`,
4
+ // `<url|text>` links, no heading syntax. Every Slack post shipped the model's raw markdown
5
+ // verbatim until this file existed, so `**bold**` and `[text](url)` rendered as literal
6
+ // characters — observed live 2026-08-20, #oe-soak-ops, the channel-join intro's
7
+ // `**Track soak health**`.
8
+ //
9
+ // PURE — no I/O. Applied at the Slack provider's delivery seam (slack-post-arm.ts): the reply
10
+ // tool, edit_reply, and the join intro all post through dispatchSlackReply / liveSlackPost /
11
+ // editSlackMessage, so converting there covers all three without touching core or any other
12
+ // provider (Linear/GitHub take real markdown; Notion has its own block model).
13
+ //
14
+ // Code fences and inline code are protected FIRST and restored LAST — their contents must never
15
+ // change, even when they look like markdown. Text that is already valid Slack mrkdwn (a single
16
+ // `*bold*`, an existing `_italic_`, a `>` blockquote) is left untouched: Slack has no `**`/`__`
17
+ // bold marker, so those are unambiguous GFM syntax safe to convert, but a single `*`/`_` is
18
+ // ALREADY Slack's own syntax, and converting it again would corrupt it. That is also why this
19
+ // function is idempotent — running it twice on its own output is a no-op.
20
+ const CODE_PLACEHOLDER = "";
21
+ /** Convert model-authored markdown to Slack mrkdwn. */
22
+ export function markdownToSlackMrkdwn(text) {
23
+ if (!text)
24
+ return text;
25
+ // 1. Protect fenced code blocks, then inline code spans — restored verbatim at the end. Fences
26
+ // first: an inline-code regex would otherwise match the single backticks INSIDE a fence.
27
+ const blocks = [];
28
+ const stash = (s) => {
29
+ const token = `${CODE_PLACEHOLDER}${blocks.length}${CODE_PLACEHOLDER}`;
30
+ blocks.push(s);
31
+ return token;
32
+ };
33
+ let out = text.replace(/```[\s\S]*?```/g, (m) => stash(m));
34
+ out = out.replace(/`[^`\n]+`/g, (m) => stash(m));
35
+ // 2. Headers → a bold line (Slack has no heading syntax).
36
+ out = out.replace(/^ {0,3}#{1,6}[ \t]+(.+)$/gm, (_m, body) => `*${body.trim()}*`);
37
+ // 3. Links: [text](url) → <url|text>.
38
+ out = out.replace(/\[([^\]\n]+)\]\((\S+?)\)/g, (_m, label, url) => `<${url}|${label}>`);
39
+ // 4. Bold: **text** / __text__ → *text* — unambiguous, Slack has no ** or __ marker so this
40
+ // never fires on text that is already Slack mrkdwn.
41
+ out = out.replace(/\*\*([^*\n]+)\*\*/g, (_m, body) => `*${body}*`);
42
+ out = out.replace(/__([^_\n]+)__/g, (_m, body) => `*${body}*`);
43
+ // 5. Strikethrough: ~~text~~ → ~text~.
44
+ out = out.replace(/~~([^~\n]+)~~/g, (_m, body) => `~${body}~`);
45
+ // 6. Bullet lists: "- item" / "* item" → "• item". The dash/star must be followed by
46
+ // whitespace to match — a line opening on **bold** or *bold* never qualifies (no space
47
+ // right after the marker), so this never collides with rule 4's untouched single-star text.
48
+ out = out.replace(/^(\s*)[-*][ \t]+/gm, (_m, indent) => `${indent}• `);
49
+ // 7. Restore code spans/fences untouched — no rule above ever saw their contents.
50
+ out = out.replace(new RegExp(`${CODE_PLACEHOLDER}(\\d+)${CODE_PLACEHOLDER}`, "g"), (_m, i) => blocks[Number(i)] ?? "");
51
+ return out;
52
+ }
53
+ //# sourceMappingURL=slack-mrkdwn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-mrkdwn.js","sourceRoot":"","sources":["../../../agent/channels/slack-mrkdwn.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,EAAE;AACF,gGAAgG;AAChG,2FAA2F;AAC3F,wFAAwF;AACxF,gFAAgF;AAChF,2BAA2B;AAC3B,EAAE;AACF,8FAA8F;AAC9F,6FAA6F;AAC7F,4FAA4F;AAC5F,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,gGAAgG;AAChG,4FAA4F;AAC5F,8FAA8F;AAC9F,0EAA0E;AAE1E,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,uDAAuD;AACvD,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,+FAA+F;IAC/F,4FAA4F;IAC5F,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE;QAClC,MAAM,KAAK,GAAG,GAAG,gBAAgB,GAAG,MAAM,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACvE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjD,0DAA0D;IAC1D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAE1F,sCAAsC;IACtC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,2BAA2B,EAAE,CAAC,EAAE,EAAE,KAAa,EAAE,GAAW,EAAE,EAAE,CAAC,IAAI,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC;IAExG,4FAA4F;IAC5F,uDAAuD;IACvD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IAC3E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IAEvE,uCAAuC;IACvC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IAEvE,qFAAqF;IACrF,0FAA0F;IAC1F,+FAA+F;IAC/F,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC;IAE/E,kFAAkF;IAClF,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,GAAG,gBAAgB,SAAS,gBAAgB,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAE/H,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { UnifiedWakeOrigin, WakeOriginCodec } from "@open-everywhere/core/agent/adapter-api.js";
2
+ type SlackOrigin = Extract<UnifiedWakeOrigin, {
3
+ platform: "slack";
4
+ }>;
5
+ export declare const slackWakeOriginCodec: WakeOriginCodec<SlackOrigin>;
6
+ export {};
7
+ //# sourceMappingURL=slack-origin-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-origin-codec.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-origin-codec.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAErG,KAAK,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAMrE,eAAO,MAAM,oBAAoB,EAAE,eAAe,CAAC,WAAW,CAwC7D,CAAC"}
@@ -0,0 +1,55 @@
1
+ // The Slack UnifiedWakeOrigin codec — MOVED VERBATIM out of core's
2
+ // channels/wake-origin-codecs.ts (OPE-114 stage 4). STORED-DATA COMPATIBILITY IS THE
3
+ // CONTRACT: a Slack origin is JSON-serialised into the durable operational store and
4
+ // fail-closed re-validated on read, so every behaviour here is frozen by core's
5
+ // scripts/wake-origin-fixtures-test.ts (run from the app root with this bundle's boot
6
+ // wiring — scripts/wake-origin-fixtures-boot.ts). Validator tolerances (typeof-only
7
+ // optional checks, extra unknown fields) are LOAD-BEARING for rows already on disk — never
8
+ // tighten them here.
9
+ //
10
+ // The `UnifiedWakeOrigin` TYPE union (including its slack arm) stays in core — it is the
11
+ // wire schema for stored rows; only the codec BEHAVIOUR lives in the bundle, registered by
12
+ // registerSlackProvider() (../register.ts).
13
+ /** COLON-FREE by construction (a task windowId is `[^:]+` — core channels/window-key.ts): any colon
14
+ * in a raw surface id is folded to `_`, deterministically, so spawn + ingress agree on identity. */
15
+ const noColon = (s) => s.replace(/:/g, "_");
16
+ export const slackWakeOriginCodec = {
17
+ validate: (o) => typeof o.channelId === "string" &&
18
+ o.channelId.length > 0 &&
19
+ (o.threadTs === undefined || typeof o.threadTs === "string") &&
20
+ (o.replyAnchorTs === undefined || typeof o.replyAnchorTs === "string"),
21
+ threadRef: (origin) => ({ channelKey: noColon(`slack/${origin.channelId}`), threadKey: noColon(origin.threadTs ?? origin.channelId) }),
22
+ // threadTs present ⇔ the trigger was itself in a thread (the exact same presence threadRef keys
23
+ // on). Absent = a top-level trigger, which stays top-level for IDENTITY even though its reply
24
+ // opens a thread on it — that placement rides `replyAnchorTs`, which nothing here reads (OPE-759).
25
+ isThreadScoped: (origin) => typeof origin.threadTs === "string" && origin.threadTs.length > 0,
26
+ containerId: (origin) => origin.channelId,
27
+ // THE PLACEMENT CARRY (OPE-1824) — Slack is the one surface that needs it, and the line above says
28
+ // why: `replyAnchorTs` is placement ONLY and stays outside `threadRef`, so two top-level wakes in
29
+ // one channel are ONE conversation holding two different anchors, and a wake minted for a message
30
+ // that answers nothing (a routine fire, a webhook post-back) holds none. The reply fence admits a
31
+ // sibling wake on the conversation test alone; without this carry it would hand the post arm an
32
+ // anchorless origin and the answer would land FLAT on the channel.
33
+ // NARROWING-ONLY, and both guards are load-bearing. A `threadTs` target is already placed — its
34
+ // answer goes in that thread and must never re-anchor on a trigger message (the rule
35
+ // slackThreadTarget states) — and a target that already holds an anchor keeps it, because the model
36
+ // named THAT wake. So this fills exactly one gap: no placement at all, where the source has one.
37
+ // EMPTINESS IS ABSENCE ON ALL THREE READS, matching `slackThreadTarget` and `isThreadScoped`: a
38
+ // stored `""` is not a thread, not an anchor, and not an anchor worth carrying. No mint site writes
39
+ // one today, and reading it three ways in one file is how that stops being true quietly.
40
+ carryPlacement: (target, source) => !(target.threadTs && target.threadTs.length > 0) && !(target.replyAnchorTs && target.replyAnchorTs.length > 0) && typeof source.replyAnchorTs === "string" && source.replyAnchorTs.length > 0
41
+ ? { ...target, replyAnchorTs: source.replyAnchorTs }
42
+ : target,
43
+ // THE PLACEMENT AXIS ITSELF (OPE-1826) — the declaration `carryPlacement` above always implied and
44
+ // never stated. `replyAnchorTs` is the whole of it: `threadTs` is IDENTITY (it is what `threadRef`
45
+ // keys on and what `isThreadScoped` answers from), so a threaded origin's placement is already
46
+ // settled by the conversation test and naming it here would make two origins of one thread compare
47
+ // unequal on an axis that cannot differ. EMPTINESS IS ABSENCE, the third of the three reads this
48
+ // file takes that way.
49
+ placementRef: (origin) => (typeof origin.replyAnchorTs === "string" && origin.replyAnchorTs.length > 0 ? origin.replyAnchorTs : undefined),
50
+ // A `dmOrigin` arm lived here (OPE-306) to mint a DM delegation anchor's reply origin from the
51
+ // binding. OPE-368 deleted the anchor and the interface member with it — a Slack 1:1 or group DM
52
+ // now diverts into its own first-class window (OPE-351 stages 2 + 4), so its origin is minted and
53
+ // STORED at dispatch time by `scheduleDmWake` like every other wake.
54
+ };
55
+ //# sourceMappingURL=slack-origin-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-origin-codec.js","sourceRoot":"","sources":["../../../agent/channels/slack-origin-codec.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,qFAAqF;AACrF,qFAAqF;AACrF,gFAAgF;AAChF,sFAAsF;AACtF,oFAAoF;AACpF,2FAA2F;AAC3F,qBAAqB;AACrB,EAAE;AACF,yFAAyF;AACzF,2FAA2F;AAC3F,4CAA4C;AAM5C;qGACqG;AACrG,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACd,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;QAC/B,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QACtB,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC;QAC5D,CAAC,CAAC,CAAC,aAAa,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ,CAAC;IACxE,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,SAAS,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IACtI,gGAAgG;IAChG,8FAA8F;IAC9F,mGAAmG;IACnG,cAAc,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;IAC7F,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS;IACzC,mGAAmG;IACnG,kGAAkG;IAClG,kGAAkG;IAClG,kGAAkG;IAClG,gGAAgG;IAChG,mEAAmE;IACnE,gGAAgG;IAChG,qFAAqF;IACrF,oGAAoG;IACpG,iGAAiG;IACjG,gGAAgG;IAChG,oGAAoG;IACpG,yFAAyF;IACzF,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;QAC3L,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE;QACpD,CAAC,CAAC,MAAM;IACZ,mGAAmG;IACnG,mGAAmG;IACnG,+FAA+F;IAC/F,mGAAmG;IACnG,iGAAiG;IACjG,uBAAuB;IACvB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1I,+FAA+F;IAC/F,iGAAiG;IACjG,kGAAkG;IAClG,qEAAqE;CACtE,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { PostArm, ReplierLabel, UnifiedWakeOrigin, DirectMessageAction } from "@open-everywhere/core/agent/adapter-api.js";
2
+ type SlackOrigin = Extract<UnifiedWakeOrigin, {
3
+ platform: "slack";
4
+ }>;
5
+ /** Pure: the extra `chat.postMessage` params for a worker-turn reply. `username` is set to
6
+ * "<agentName> [<taskLabel>]" only when BOTH a resolvable agent name and a replier label are
7
+ * present; `{}` (no override — the bot's own default name renders, bare) for a supervisor/ambient
8
+ * reply OR when the agent name can't be resolved — fail-open to today's behaviour, never blocks
9
+ * the post over a cosmetic name. */
10
+ export declare function slackReplierParams(agentName: string | undefined, replier: ReplierLabel | undefined): Record<string, string>;
11
+ /** The minimal shape {@link postSlackMessage} needs from a `chat.postMessage` response — the same
12
+ * fields `slackForm` already returns, narrowed so a test double doesn't need the real client. */
13
+ export interface SlackPostResult {
14
+ readonly ok?: boolean;
15
+ readonly ts?: unknown;
16
+ readonly error?: unknown;
17
+ }
18
+ /** The injectable `chat.postMessage` dispatch {@link postSlackMessage} calls — defaults to the live
19
+ * `slackForm` client; a test supplies a scripted one. */
20
+ export type SlackPostFn = (body: URLSearchParams) => Promise<SlackPostResult | undefined>;
21
+ /**
22
+ * POST `chat.postMessage` with the OPE-508 replier-tier fallback: `base` (channel/thread/text, no
23
+ * tier params) is the request that must ALWAYS have a chance to land. When `replier` resolves to
24
+ * non-empty params (a worker turn with a resolvable agent name), the FIRST attempt adds them; on
25
+ * any non-`ok` response to THAT attempt, retries ONCE with `base` alone. A response that was never
26
+ * tier-bearing (no replier, or an unresolvable agent name) gets no retry — a real failure there is
27
+ * real, not scope-shaped. `post` defaults to the live Slack client; `resolveDisplayName` defaults
28
+ * to the live `botDisplayName()`; a test injects scripted versions of both (the live
29
+ * `botDisplayName` reaches its own module-level memoized Slack call, which a `post`-only test
30
+ * double can't reach — see slack-replier-label-test.ts).
31
+ */
32
+ export declare function postSlackMessage(base: URLSearchParams, replier: ReplierLabel | undefined, errPrefix: string, post?: SlackPostFn, resolveDisplayName?: () => Promise<string | undefined>): Promise<string>;
33
+ /**
34
+ * The `thread_ts` a unified reply posts under, or undefined for a top-level post. PURE, and the ONE
35
+ * statement of Slack's reply placement:
36
+ * • `threadTs` — the trigger was already in a thread, so the answer goes in that thread.
37
+ * • `replyAnchorTs` — the trigger was a top-level channel message, so the answer opens a thread on
38
+ * it (OPE-759; slack-events.ts mints the anchor for an addressed mention AND an ambient wake).
39
+ * • neither — a post with no triggering message (a routine fire, a webhook post-back). It lands
40
+ * top-level, which is the right placement for a message that answers nothing.
41
+ * Exported so the placement is pinned by a test rather than by reading one `??` inside an
42
+ * un-injectable network call (scripts/slack-replier-label-test.ts).
43
+ */
44
+ export declare function slackThreadTarget(origin: Pick<SlackOrigin, "threadTs" | "replyAnchorTs">): string | undefined;
45
+ /**
46
+ * PURE: the exact `chat.postMessage` form a unified card post sends. Split out from the dispatch so
47
+ * the BLOCK PAYLOAD itself is pinned by a contract test with no Slack token and no transport — the
48
+ * shape is the contract, and a shape Slack rejects costs the room the whole message, not just the
49
+ * buttons.
50
+ */
51
+ export declare function slackUnifiedActionForm(origin: Pick<SlackOrigin, "channelId" | "threadTs" | "replyAnchorTs">, text: string, actions: readonly DirectMessageAction[]): URLSearchParams;
52
+ export declare const slackPostArm: PostArm;
53
+ export {};
54
+ //# sourceMappingURL=slack-post-arm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-post-arm.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-post-arm.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EAEZ,iBAAiB,EAIjB,mBAAmB,EACpB,MAAM,4CAA4C,CAAC;AAGpD,KAAK,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAyBrE;;;;qCAIqC;AACrC,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3H;AAED;kGACkG;AAClG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;0DAC0D;AAC1D,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;AAI1F;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,WAAsB,EAC5B,kBAAkB,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAkB,GACrE,OAAO,CAAC,MAAM,CAAC,CAuBjB;AA4BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,eAAe,CAAC,GAAG,MAAM,GAAG,SAAS,CAG7G;AAuBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,eAAe,CAapL;AA6FD,eAAO,MAAM,YAAY,EAAE,OAwB1B,CAAC"}
@@ -0,0 +1,268 @@
1
+ // slack-post-arm — Slack's POST arm for the post-arms registry (OPE-114 stage 1c). Absorbs, VERBATIM,
2
+ // Slack's arms out of the three reply/delivery spines: reply-post.ts (resolveReplyTarget's slack
3
+ // case + dispatchReply's chat.postMessage branch), unified-reply.ts (liveSlackPost), and
4
+ // unified-redrive.ts (the slack describe/container cases). Slack's ONE bot-token write client is
5
+ // slackForm (slack-events.ts) — every Slack post here funnels through it.
6
+ import { slackForm, botDisplayName } from "./slack-events.js";
7
+ import { markdownToSlackMrkdwn } from "./slack-mrkdwn.js";
8
+ import { markNotApplied } from "@open-everywhere/core/agent/adapter-api.js";
9
+ // --- OPE-508: the reply TIER signal, native on Slack ------------------------------------------
10
+ //
11
+ // Slack is the one surface OE's send path can vary the display NAME on, per message: chat.postMessage
12
+ // takes an optional `username` override on a bot-token post — needs the `chat:write.customize` scope
13
+ // (see docs/deploy/surfaces/slack.md). Every other wired surface (Discord's bot-token REST message
14
+ // create, Teams' Bot-Framework activity, Notion's/Linear's comment APIs) has no equivalent per-message
15
+ // override in what OE's send path already uses, so they get the shared bracket-BODY fallback instead
16
+ // (reply-post.ts / unified-reply.ts, gated on `displayNameCapable` being false/absent for them).
17
+ //
18
+ // 🔴 SCOPE-OPTIONAL, GRACEFUL FALLBACK — BY CONSTRUCTION, NOT BY AN ASSUMPTION ABOUT SLACK'S OWN
19
+ // BEHAVIOUR. Whether an app WITHOUT `chat:write.customize` gets `username` silently ignored or the
20
+ // WHOLE post refused (`missing_scope`) is a Slack-side behaviour this codebase has not measured
21
+ // against a real unscoped app — asserting one over the other would be an unmeasured vendor claim
22
+ // baked into an availability-critical path. {@link postSlackMessage} makes the answer not matter: a
23
+ // username-bearing post that comes back non-`ok` is retried EXACTLY ONCE with the tier-signal params
24
+ // stripped, so a missing scope costs only the bracket, never the reply. A genuine failure (a
25
+ // non-scope error, or a failure on the retry too) still throws — the fallback only ever widens what
26
+ // counts as "the reply landed", never swallows a real one.
27
+ //
28
+ // PURE core: {@link slackReplierParams} decides WHAT to send; {@link postSlackMessage} is the thin
29
+ // impure shell (resolve the bot's own name, dispatch, retry-on-reject). The `post` seam is injected
30
+ // so the retry behaviour is unit-testable against a SCRIPTED bridge, without a live Slack token.
31
+ /** Pure: the extra `chat.postMessage` params for a worker-turn reply. `username` is set to
32
+ * "<agentName> [<taskLabel>]" only when BOTH a resolvable agent name and a replier label are
33
+ * present; `{}` (no override — the bot's own default name renders, bare) for a supervisor/ambient
34
+ * reply OR when the agent name can't be resolved — fail-open to today's behaviour, never blocks
35
+ * the post over a cosmetic name. */
36
+ export function slackReplierParams(agentName, replier) {
37
+ if (!replier || !agentName || agentName.trim().length === 0)
38
+ return {};
39
+ return { username: `${agentName} [${replier.taskLabel}]` };
40
+ }
41
+ const livePost = (body) => slackForm("chat.postMessage", body.toString());
42
+ /**
43
+ * POST `chat.postMessage` with the OPE-508 replier-tier fallback: `base` (channel/thread/text, no
44
+ * tier params) is the request that must ALWAYS have a chance to land. When `replier` resolves to
45
+ * non-empty params (a worker turn with a resolvable agent name), the FIRST attempt adds them; on
46
+ * any non-`ok` response to THAT attempt, retries ONCE with `base` alone. A response that was never
47
+ * tier-bearing (no replier, or an unresolvable agent name) gets no retry — a real failure there is
48
+ * real, not scope-shaped. `post` defaults to the live Slack client; `resolveDisplayName` defaults
49
+ * to the live `botDisplayName()`; a test injects scripted versions of both (the live
50
+ * `botDisplayName` reaches its own module-level memoized Slack call, which a `post`-only test
51
+ * double can't reach — see slack-replier-label-test.ts).
52
+ */
53
+ export async function postSlackMessage(base, replier, errPrefix, post = livePost, resolveDisplayName = botDisplayName) {
54
+ let usedReplierParams = false;
55
+ let attempt = base;
56
+ if (replier) {
57
+ const name = await resolveDisplayName().catch(() => undefined);
58
+ const params = slackReplierParams(name, replier);
59
+ if (Object.keys(params).length > 0) {
60
+ attempt = new URLSearchParams(base);
61
+ for (const [k, v] of Object.entries(params))
62
+ attempt.set(k, v);
63
+ usedReplierParams = true;
64
+ }
65
+ }
66
+ const res = await post(attempt);
67
+ const ts = typeof res?.ts === "string" ? res.ts : undefined;
68
+ if (res?.ok === true && ts)
69
+ return ts;
70
+ if (usedReplierParams) {
71
+ // A rejected tier-signal param must never cost the reply itself — retry bare, exactly once.
72
+ const retry = await post(base);
73
+ const retryTs = typeof retry?.ts === "string" ? retry.ts : undefined;
74
+ if (retry?.ok === true && retryTs)
75
+ return retryTs;
76
+ throw classifiedPostError(retry, `${errPrefix} — ${retry?.error ?? res?.error ?? "no ts returned (check SLACK_BOT_TOKEN)"}`);
77
+ }
78
+ throw classifiedPostError(res, `${errPrefix} — ${res?.error ?? "no ts returned (check SLACK_BOT_TOKEN)"}`);
79
+ }
80
+ /**
81
+ * The failure error for a `chat.postMessage` that did not return a usable ts, CLASSIFIED (OPE-821).
82
+ *
83
+ * `ok:false` IS DEFINITIVE, AND ONLY THAT. Slack answers HTTP 200 with `ok:false` when it read the
84
+ * request and refused it (`channel_not_found`, `not_in_channel`, `invalid_auth`) — nothing reached
85
+ * the room, so the post spine may release its dedupe claim and let an identical retry through. There
86
+ * is no HTTP status to carry that fact, so the arm states it (channels/platform-failure.ts).
87
+ *
88
+ * 🔴 `ok:true` WITH NO `ts` IS NOT DEFINITIVE, and the distinction is the whole point. Slack saying
89
+ * "ok" means the message DID post; a missing `ts` then means this host cannot name it, not that the
90
+ * room is empty. Marking that case would tell the spine to let a retry through and put the message
91
+ * in the room twice. A transport throw from `post()` never reaches here at all and stays AMBIGUOUS,
92
+ * which is the honest answer: a request that got no reply may still have applied.
93
+ */
94
+ function classifiedPostError(res, message) {
95
+ const err = new Error(message);
96
+ return res?.ok === false ? markNotApplied(err) : err;
97
+ }
98
+ /** MID-TURN: post via chat.postMessage (returns `ts`), thread-anchored when the trigger was threaded. */
99
+ async function dispatchSlackReply(target, text, replier) {
100
+ const body = new URLSearchParams({ channel: target.channelId, text: markdownToSlackMrkdwn(text) });
101
+ if (target.threadTs)
102
+ body.set("thread_ts", target.threadTs);
103
+ return postSlackMessage(body, replier, "reply couldn't post to Slack");
104
+ }
105
+ /**
106
+ * The `thread_ts` a unified reply posts under, or undefined for a top-level post. PURE, and the ONE
107
+ * statement of Slack's reply placement:
108
+ * • `threadTs` — the trigger was already in a thread, so the answer goes in that thread.
109
+ * • `replyAnchorTs` — the trigger was a top-level channel message, so the answer opens a thread on
110
+ * it (OPE-759; slack-events.ts mints the anchor for an addressed mention AND an ambient wake).
111
+ * • neither — a post with no triggering message (a routine fire, a webhook post-back). It lands
112
+ * top-level, which is the right placement for a message that answers nothing.
113
+ * Exported so the placement is pinned by a test rather than by reading one `??` inside an
114
+ * un-injectable network call (scripts/slack-replier-label-test.ts).
115
+ */
116
+ export function slackThreadTarget(origin) {
117
+ const anchor = origin.threadTs ?? origin.replyAnchorTs;
118
+ return anchor && anchor.length > 0 ? anchor : undefined;
119
+ }
120
+ /** UNIFIED: post into the wake origin's channel/thread (planSupervisorReply's exact semantics). */
121
+ async function liveSlackPost(origin, text, replier) {
122
+ const body = new URLSearchParams({ channel: origin.channelId, text: markdownToSlackMrkdwn(text) });
123
+ const threadTarget = slackThreadTarget(origin);
124
+ if (threadTarget)
125
+ body.set("thread_ts", threadTarget);
126
+ return postSlackMessage(body, replier, "unified reply couldn't post to Slack");
127
+ }
128
+ /**
129
+ * UNIFIED, WITH BUTTONS (OPE-1720) — `liveSlackPost` plus one `actions` block, so an interactive card
130
+ * can reach a unified origin (a DM seat, a single-window channel) instead of only a DM.
131
+ *
132
+ * IDENTICAL GEOMETRY TO THE PLAIN POST: same channel, same `thread_ts` resolution, same replier
133
+ * fallback through {@link postSlackMessage} — the ONLY difference is the `blocks` field, so a card
134
+ * lands exactly where the same reply would have. An `actions` list that renders to NOTHING (every
135
+ * entry over Slack's caps) posts the plain message rather than a `blocks` array with an empty row.
136
+ */
137
+ async function liveSlackPostWithActions(origin, text, actions, replier) {
138
+ return postSlackMessage(slackUnifiedActionForm(origin, text, actions), replier, "unified reply couldn't post to Slack");
139
+ }
140
+ /**
141
+ * PURE: the exact `chat.postMessage` form a unified card post sends. Split out from the dispatch so
142
+ * the BLOCK PAYLOAD itself is pinned by a contract test with no Slack token and no transport — the
143
+ * shape is the contract, and a shape Slack rejects costs the room the whole message, not just the
144
+ * buttons.
145
+ */
146
+ export function slackUnifiedActionForm(origin, text, actions) {
147
+ const rendered = markdownToSlackMrkdwn(text);
148
+ const body = new URLSearchParams({ channel: origin.channelId, text: rendered });
149
+ const threadTarget = slackThreadTarget(origin);
150
+ if (threadTarget)
151
+ body.set("thread_ts", threadTarget);
152
+ const actionBlock = slackDirectMessageActionBlock(actions);
153
+ if (actionBlock !== undefined) {
154
+ // The text block rides ALONGSIDE the buttons, for the reason `deliverSlackDirectMessage` states:
155
+ // once `blocks` is present `text` is only the notification fallback, so without the section the
156
+ // room would see a row of buttons over an empty message.
157
+ body.set("blocks", JSON.stringify([{ text: { text: rendered, type: "mrkdwn" }, type: "section" }, actionBlock]));
158
+ }
159
+ return body;
160
+ }
161
+ /**
162
+ * THE COMPONENT PLANE (OPE-1060 S3) — the `actions` a {@link DirectMessageRequest} carries, rendered
163
+ * as ONE Slack `actions` block of buttons. Shared since OPE-1720 with the unified post above, so a
164
+ * bridged approval card and a DM card cannot drift in how a button is built or capped.
165
+ *
166
+ * 🔴 THE TEXT STILL STANDS ON ITS OWN. `post-arms.ts` states the contract: an arm with no component
167
+ * plane delivers the text alone and never fakes the buttons, so every caller's copy already says what
168
+ * the answers ARE in words. These buttons are a shortcut over that copy, never a replacement for it —
169
+ * which is also why the `text` field below stays the full message rather than a placeholder.
170
+ *
171
+ * 🔴 THE ACTION ID IS THE WHOLE DECISION AND THE LABEL IS HOST COPY. Neither is model-authored: the
172
+ * caller composes both (`access/credential-grant-card.ts`), and this arm only transports them. Slack
173
+ * caps an `action_id` at 255 characters and a button label at 75; an id or a label past its cap makes
174
+ * Slack reject the WHOLE `chat.postMessage`, which would cost the person the message as well as the
175
+ * buttons. So an over-long action drops out and the text still goes.
176
+ *
177
+ * `intent` maps to Slack's own button styles: `reject` renders `danger`, `affirm` renders default
178
+ * (never `primary` — four buttons of which three are primary reads as no emphasis at all, and the
179
+ * grant card's three affirms are genuinely equal choices).
180
+ */
181
+ function slackDirectMessageActionBlock(actions) {
182
+ const elements = actions
183
+ .filter((a) => a.id.length > 0 && a.id.length <= 255 && a.label.length > 0 && a.label.length <= 75)
184
+ .map((a) => ({
185
+ action_id: a.id,
186
+ text: { emoji: true, text: a.label, type: "plain_text" },
187
+ type: "button",
188
+ ...(a.intent === "reject" ? { style: "danger" } : {}),
189
+ }));
190
+ return elements.length > 0 ? { elements, type: "actions" } : undefined;
191
+ }
192
+ /**
193
+ * DM: open (or reuse) the 1:1 IM with a Slack member and post there (OPE-44's DM-delivery arm).
194
+ * `conversations.open` is idempotent — repeat calls return the SAME `im` channel id — so there is no
195
+ * "already open" branch to write. Needs the `im:write` bot scope on top of `chat:write`; without it
196
+ * Slack answers `missing_scope`, which this surfaces as a loud token-free throw so the governance
197
+ * arm refuses (and never falls back to rendering pane substance in the room).
198
+ *
199
+ * A request carrying `actions` also gets a row of buttons (OPE-1060 S3). The clicks come back on the
200
+ * app's interactivity URL, which the front route classifies by the action-id prefix.
201
+ */
202
+ async function deliverSlackDirectMessage(input) {
203
+ const opened = await slackForm("conversations.open", new URLSearchParams({ users: input.principalId }).toString());
204
+ const channel = opened?.channel;
205
+ const dmChannelId = typeof channel?.id === "string" ? channel.id : undefined;
206
+ if (opened?.ok !== true || !dmChannelId) {
207
+ throw new Error(`couldn't open a Slack DM with the requester — ${opened?.error ?? "no channel returned (check SLACK_BOT_TOKEN and the im:write bot scope)"}`);
208
+ }
209
+ const rendered = markdownToSlackMrkdwn(input.text);
210
+ const actionBlock = input.actions === undefined ? undefined : slackDirectMessageActionBlock(input.actions);
211
+ const form = new URLSearchParams({ channel: dmChannelId, text: rendered });
212
+ if (actionBlock !== undefined) {
213
+ // The text block rides ALONGSIDE the buttons. `text` alone is only the notification fallback once
214
+ // `blocks` is present, so without this section the person's Slack client would show a row of
215
+ // buttons over an empty message.
216
+ form.set("blocks", JSON.stringify([{ text: { text: rendered, type: "mrkdwn" }, type: "section" }, actionBlock]));
217
+ }
218
+ const posted = await slackForm("chat.postMessage", form.toString());
219
+ const ts = typeof posted?.ts === "string" ? posted.ts : undefined;
220
+ if (posted?.ok !== true || !ts) {
221
+ throw new Error(`couldn't post the Slack DM — ${posted?.error ?? "no ts returned (check SLACK_BOT_TOKEN)"}`);
222
+ }
223
+ return { surfaceId: dmChannelId, messageId: ts };
224
+ }
225
+ /**
226
+ * EDIT (OPE-793): rewrite one of our own posted messages via `chat.update` (channel + ts + text).
227
+ * Slack renders an "(edited)" marker on the message, so the room sees that it changed.
228
+ *
229
+ * Ownership is NOT decided here — channels/reply-edit.ts already refused anything outside the
230
+ * own-post ledger before this runs. Slack's own rule (an app may only update a message its own token
231
+ * posted) is the backstop under that, and its refusal arrives as a token-free throw the model reads.
232
+ * `chat.update` needs the same `chat:write` scope the post path already has.
233
+ */
234
+ async function editSlackMessage(locus, messageId, text) {
235
+ const body = new URLSearchParams({ channel: locus.channelId, ts: messageId, text: markdownToSlackMrkdwn(text) });
236
+ const res = await slackForm("chat.update", body.toString());
237
+ if (res?.ok !== true) {
238
+ // DEFINITIVE (OPE-802): `chat.update` answers HTTP 200 with `ok:false`, so Slack read the request
239
+ // and decided against it — the message did not change. There is no status to carry that, so the
240
+ // arm says it. A transport throw from `slackForm` carries neither and stays AMBIGUOUS, which is
241
+ // the honest answer: a request that never got a reply may still have applied.
242
+ throw markNotApplied(new Error(`edit_reply couldn't update the Slack message — ${res?.error ?? "chat.update returned no ok (check SLACK_BOT_TOKEN)"}`));
243
+ }
244
+ }
245
+ export const slackPostArm = {
246
+ platform: "slack",
247
+ resolveTarget: (bag) => {
248
+ const channelId = bag.str("channelId");
249
+ // Slack threads its replies; bag.threadTs is the anchor (undefined ⇒ top-level).
250
+ return channelId ? { platform: "slack", channelId, threadTs: bag.threadTs } : null;
251
+ },
252
+ displayNameCapable: true,
253
+ dispatchReply: (target, text, replier) => dispatchSlackReply(target, text, replier),
254
+ dispatchUnified: (origin, text, deps, replier) => deps.postSlack ? deps.postSlack(origin, text) : liveSlackPost(origin, text, replier),
255
+ // OPE-1720: Slack declares the unified component plane because the tap route exists on it —
256
+ // `slack-front.ts` classifies the `oe_input:` click and `slack-approval-card.ts` settles it. The
257
+ // injected `postSlack` override is honoured VERBATIM and drops the buttons: a test that scripted a
258
+ // text-only post must not start receiving a card it never asked for.
259
+ dispatchUnifiedActions: (origin, text, actions, deps, replier) => deps.postSlack ? deps.postSlack(origin, text) : liveSlackPostWithActions(origin, text, actions, replier),
260
+ describeRedriveTarget: (origin) => {
261
+ const o = origin;
262
+ return `slack ${o.threadTs ? `thread in ${o.channelId}` : `channel ${o.channelId}`}`;
263
+ },
264
+ redriveContainerId: (origin) => origin.channelId,
265
+ deliverDirectMessage: deliverSlackDirectMessage,
266
+ editMessage: editSlackMessage,
267
+ };
268
+ //# sourceMappingURL=slack-post-arm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-post-arm.js","sourceRoot":"","sources":["../../../agent/channels/slack-post-arm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { ChannelScopeError, type BindableContext, type ReadScopeAuditDeps, type RenderedLine, type WakeReadAddress } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import { type SlackMemberProbe } from "./slack-events.js";
3
+ export { ChannelScopeError };
4
+ /**
5
+ * The Slack channel ids this session may read: every Slack channel in its explicit domain,
6
+ * PLUS its own current channel (always readable — it's where the agent was engaged). A DM,
7
+ * an unassigned channel, or a criteria pseudo-domain (org/public/dm — no channel list)
8
+ * yields just the current channel. Empty only when the surface isn't a Slack channel.
9
+ */
10
+ export declare function allowedChannelIds(ctx: BindableContext): {
11
+ ids: Set<string>;
12
+ domain?: string;
13
+ };
14
+ /**
15
+ * Assert a requested channel id is readable by this session — else AUDIT the refusal and throw
16
+ * (fail closed). The error names the domain so the model gets an actionable message, not a bare
17
+ * denial. The decision is unchanged from the silent version; only the row is new
18
+ * (core channels/read-scope-audit.ts).
19
+ */
20
+ export declare function assertChannelAllowed(ctx: BindableContext, channelId: string, deps?: ReadScopeAuditDeps): Promise<void>;
21
+ interface SlackReadResponse {
22
+ readonly ok: boolean;
23
+ readonly error?: string;
24
+ readonly [key: string]: unknown;
25
+ }
26
+ /**
27
+ * Low-level POST to a (read-only) Slack Web API method with the bot token, via OE's
28
+ * `callSlackWebApi` (form-encoded — Slack's JSON support is partial: `conversations.replies`/
29
+ * `history` want form). Throws on a missing token or a non-ok Slack response so the tool
30
+ * surfaces a real error. The explicit env pre-check keeps the actionable "not set" message
31
+ * (the seam itself throws a terser "SLACK_BOT_TOKEN is required.").
32
+ */
33
+ export declare function callSlackRead(operation: string, params: Record<string, string | number | boolean>): Promise<SlackReadResponse>;
34
+ /** One Slack message rendered to a compact, model-friendly line. */
35
+ export interface RenderedMessage {
36
+ readonly author: string;
37
+ readonly ts: string;
38
+ readonly text: string;
39
+ /** The author's Slack USER id, for the OPE-474 non-member probe. Absent on a machine-authored or
40
+ * unattributable line. It never reaches the model — the read verbs project author/ts/text only. */
41
+ readonly authorId?: string;
42
+ /** A machine author (`bot_id` present). Excluded from the probe: a bot is below a member but is
43
+ * not a person outside the org — the same exclusion access/taint-signal.ts makes for `relay`. */
44
+ readonly bot?: boolean;
45
+ }
46
+ /**
47
+ * Normalize a raw Slack messages array (history/replies) to rendered lines, oldest-first.
48
+ *
49
+ * `author` IS UNCHANGED and stays the display string the model reads (`user`, else `bot_id`, else
50
+ * `(unknown)`) — every render golden depends on it. OPE-474 adds the two GRADING fields beside it
51
+ * rather than reinterpreting it: `author` is chosen for legibility and cannot be parsed back into an
52
+ * identity, which is exactly why the read leg had nothing to probe before.
53
+ *
54
+ * `bot_id` WINS over `user` for the machine classification, even on the messages that carry both (an
55
+ * app posting as a bot user). That direction is fail-OPEN — a machine is dropped from the probe and
56
+ * marks nothing — which is the posture the whole author leg takes.
57
+ */
58
+ export declare function renderMessages(raw: unknown, opts?: {
59
+ oldestFirst?: boolean;
60
+ }): RenderedMessage[];
61
+ /** The Slack read transport — injectable for offline tests (defaults to the live callSlackRead). */
62
+ export type SlackReadCall = typeof callSlackRead;
63
+ /** Slack read via OE's callSlackWebApi — domain-allowlist-gated, rendered oldest-first. */
64
+ export declare const slackReadAdapter: {
65
+ platform: "slack";
66
+ /**
67
+ * OPE-929 — Slack's own spelling of "which room and which thread is this turn in".
68
+ *
69
+ * The CONTAINER is the channel the post-back target names. The THREAD ROOT is `threadTs`, which
70
+ * eve's Slack adapter falls back to the triggering message's own ts — so a bare channel mention
71
+ * carries one too, and reading "this thread" on such a turn reads the one-message thread the
72
+ * mention started, which is what a person means by it.
73
+ */
74
+ wakeAddress(ctx: BindableContext): WakeReadAddress | undefined;
75
+ readChannel(ctx: BindableContext, channel: string, limit?: number, call?: SlackReadCall): Promise<readonly RenderedLine[]>;
76
+ readThread(ctx: BindableContext, channel: string, threadTs: string, limit?: number, call?: SlackReadCall): Promise<readonly RenderedLine[]>;
77
+ /** OPE-474 / OPE-534. Both legs run: Slack's own `users.info` refutation, and the operator's
78
+ * author-trust register (which most Slack deployments leave unarmed, so it costs nothing there).
79
+ * It answers with the refuted AUTHOR IDS, because core reads the one answer twice — the turn mark
80
+ * and the `withhold` body replacement. `probe` is injected ONLY so a test can drive the grading
81
+ * without a live Slack — every live call defaults. */
82
+ refutedAuthorIds(ctx: BindableContext, lines: readonly RenderedLine[], probe?: SlackMemberProbe): Promise<readonly string[]>;
83
+ };
84
+ //# sourceMappingURL=slack-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-read.d.ts","sourceRoot":"","sources":["../../../agent/channels/slack-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAOL,iBAAiB,EAEjB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,eAAe,EAErB,MAAM,4CAA4C,CAAC;AAIpD,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM/E,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAW7B;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAa7F;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc5H;AAED,UAAU,iBAAiB;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAcpI;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;wGACoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;sGACkG;IAClG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,eAAe,EAAE,CAmBpG;AAUD,oGAAoG;AACpG,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC;AA2BjD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB;IAC3B,QAAQ,EAAE,OAAO;IACjB;;;;;;;OAOG;IACH,WAAW,MAAM,eAAe,GAAG,eAAe,GAAG,SAAS;IAMxD,WAAW,MAAM,eAAe,WAAW,MAAM,UAAU,MAAM,SAAQ,aAAa,GAAmB,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAKzI,UAAU,MAAM,eAAe,WAAW,MAAM,YAAY,MAAM,UAAU,MAAM,SAAQ,aAAa,GAAmB,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAKhK;;;;2DAIuD;IACjD,gBAAgB,MAAM,eAAe,SAAS,SAAS,YAAY,EAAE,UAAS,gBAAgB,GAAyB,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;CAWxJ,CAAC"}