@intentface/latch-channels 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/dist/ask.d.ts +69 -0
  4. package/dist/ask.d.ts.map +1 -0
  5. package/dist/ask.js +94 -0
  6. package/dist/ask.js.map +1 -0
  7. package/dist/commands.d.ts +41 -0
  8. package/dist/commands.d.ts.map +1 -0
  9. package/dist/commands.js +54 -0
  10. package/dist/commands.js.map +1 -0
  11. package/dist/index.d.ts +19 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +19 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/kernel.d.ts +207 -0
  16. package/dist/kernel.d.ts.map +1 -0
  17. package/dist/kernel.js +883 -0
  18. package/dist/kernel.js.map +1 -0
  19. package/dist/render.d.ts +43 -0
  20. package/dist/render.d.ts.map +1 -0
  21. package/dist/render.js +192 -0
  22. package/dist/render.js.map +1 -0
  23. package/dist/slack/api.d.ts +207 -0
  24. package/dist/slack/api.d.ts.map +1 -0
  25. package/dist/slack/api.js +249 -0
  26. package/dist/slack/api.js.map +1 -0
  27. package/dist/slack/blocks.d.ts +36 -0
  28. package/dist/slack/blocks.d.ts.map +1 -0
  29. package/dist/slack/blocks.js +161 -0
  30. package/dist/slack/blocks.js.map +1 -0
  31. package/dist/slack/ids.d.ts +40 -0
  32. package/dist/slack/ids.d.ts.map +1 -0
  33. package/dist/slack/ids.js +47 -0
  34. package/dist/slack/ids.js.map +1 -0
  35. package/dist/slack/index.d.ts +24 -0
  36. package/dist/slack/index.d.ts.map +1 -0
  37. package/dist/slack/index.js +18 -0
  38. package/dist/slack/index.js.map +1 -0
  39. package/dist/slack/manifest.d.ts +23 -0
  40. package/dist/slack/manifest.d.ts.map +1 -0
  41. package/dist/slack/manifest.js +102 -0
  42. package/dist/slack/manifest.js.map +1 -0
  43. package/dist/slack/normalize.d.ts +35 -0
  44. package/dist/slack/normalize.d.ts.map +1 -0
  45. package/dist/slack/normalize.js +63 -0
  46. package/dist/slack/normalize.js.map +1 -0
  47. package/dist/slack/payloads.d.ts +36 -0
  48. package/dist/slack/payloads.d.ts.map +1 -0
  49. package/dist/slack/payloads.js +80 -0
  50. package/dist/slack/payloads.js.map +1 -0
  51. package/dist/slack/slackChannel.d.ts +152 -0
  52. package/dist/slack/slackChannel.d.ts.map +1 -0
  53. package/dist/slack/slackChannel.js +650 -0
  54. package/dist/slack/slackChannel.js.map +1 -0
  55. package/dist/slack/stream.d.ts +88 -0
  56. package/dist/slack/stream.d.ts.map +1 -0
  57. package/dist/slack/stream.js +418 -0
  58. package/dist/slack/stream.js.map +1 -0
  59. package/dist/slack/verify.d.ts +20 -0
  60. package/dist/slack/verify.d.ts.map +1 -0
  61. package/dist/slack/verify.js +32 -0
  62. package/dist/slack/verify.js.map +1 -0
  63. package/dist/telegram/api.d.ts +110 -0
  64. package/dist/telegram/api.d.ts.map +1 -0
  65. package/dist/telegram/api.js +170 -0
  66. package/dist/telegram/api.js.map +1 -0
  67. package/dist/telegram/format.d.ts +26 -0
  68. package/dist/telegram/format.d.ts.map +1 -0
  69. package/dist/telegram/format.js +56 -0
  70. package/dist/telegram/format.js.map +1 -0
  71. package/dist/telegram/html.d.ts +11 -0
  72. package/dist/telegram/html.d.ts.map +1 -0
  73. package/dist/telegram/html.js +35 -0
  74. package/dist/telegram/html.js.map +1 -0
  75. package/dist/telegram/index.d.ts +15 -0
  76. package/dist/telegram/index.d.ts.map +1 -0
  77. package/dist/telegram/index.js +15 -0
  78. package/dist/telegram/index.js.map +1 -0
  79. package/dist/telegram/stream.d.ts +59 -0
  80. package/dist/telegram/stream.d.ts.map +1 -0
  81. package/dist/telegram/stream.js +185 -0
  82. package/dist/telegram/stream.js.map +1 -0
  83. package/dist/telegram/telegramChannel.d.ts +136 -0
  84. package/dist/telegram/telegramChannel.d.ts.map +1 -0
  85. package/dist/telegram/telegramChannel.js +277 -0
  86. package/dist/telegram/telegramChannel.js.map +1 -0
  87. package/dist/telegram/verify.d.ts +7 -0
  88. package/dist/telegram/verify.d.ts.map +1 -0
  89. package/dist/telegram/verify.js +14 -0
  90. package/dist/telegram/verify.js.map +1 -0
  91. package/dist/types.d.ts +240 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +12 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/whatsapp/api.d.ts +59 -0
  96. package/dist/whatsapp/api.d.ts.map +1 -0
  97. package/dist/whatsapp/api.js +154 -0
  98. package/dist/whatsapp/api.js.map +1 -0
  99. package/dist/whatsapp/index.d.ts +13 -0
  100. package/dist/whatsapp/index.d.ts.map +1 -0
  101. package/dist/whatsapp/index.js +13 -0
  102. package/dist/whatsapp/index.js.map +1 -0
  103. package/dist/whatsapp/verify.d.ts +17 -0
  104. package/dist/whatsapp/verify.d.ts.map +1 -0
  105. package/dist/whatsapp/verify.js +32 -0
  106. package/dist/whatsapp/verify.js.map +1 -0
  107. package/dist/whatsapp/whatsappChannel.d.ts +104 -0
  108. package/dist/whatsapp/whatsappChannel.d.ts.map +1 -0
  109. package/dist/whatsapp/whatsappChannel.js +217 -0
  110. package/dist/whatsapp/whatsappChannel.js.map +1 -0
  111. package/package.json +68 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Minimal Slack Web API client — just the methods a channel needs.
3
+ *
4
+ * Form-encoded POSTs with a bearer bot token (Slack accepts form on every
5
+ * method used here, including the streaming ones; complex values like
6
+ * `blocks`/`chunks`/`metadata` go as JSON-in-form fields). Typed errors, a
7
+ * bounded retry policy, and an injectable fetch for tests:
8
+ *
9
+ * 429 → honor `Retry-After` (capped) and retry ONCE
10
+ * 5xx → retry once after a beat
11
+ * ok:false, 4xx → throw {@link SlackApiError} immediately (the caller
12
+ * decides what's fatal — e.g. `stopped_by_user` on a
13
+ * streaming append is a signal, not a failure)
14
+ */
15
+ export class SlackApiError extends Error {
16
+ /** The Slack method that failed (`chat.postMessage`). */
17
+ method;
18
+ /** HTTP status of the response (200 for ok:false payload errors). */
19
+ status;
20
+ /** Slack's error code (`channel_not_found`, `stopped_by_user`, …) if any. */
21
+ code;
22
+ /** Seconds Slack asked us to wait, when it said. */
23
+ retryAfterS;
24
+ /**
25
+ * Slack's `response_metadata.messages`, when present — for
26
+ * `invalid_arguments` it names the offending field as a JSON pointer,
27
+ * which is the difference between a fix and a guessing game.
28
+ */
29
+ detail;
30
+ constructor(method, status, code, retryAfterS, detail) {
31
+ super(`slack:${method} failed: ${code ?? status}${detail ? ` (${detail})` : ""}`);
32
+ this.name = "SlackApiError";
33
+ this.method = method;
34
+ this.status = status;
35
+ if (code !== undefined)
36
+ this.code = code;
37
+ if (retryAfterS !== undefined)
38
+ this.retryAfterS = retryAfterS;
39
+ if (detail !== undefined)
40
+ this.detail = detail;
41
+ }
42
+ }
43
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
44
+ const MAX_RETRY_AFTER_S = 10;
45
+ async function resolveToken(token) {
46
+ return typeof token === "function" ? token() : token;
47
+ }
48
+ /** A bot-scoped client bound to one token. */
49
+ export function slackApi(token, options = {}) {
50
+ const fetchImpl = options.fetchImpl ?? fetch;
51
+ const baseUrl = options.baseUrl ?? "https://slack.com/api";
52
+ const sleep = options.sleep ?? defaultSleep;
53
+ const timeoutMs = options.timeoutMs ?? 10_000;
54
+ /** Methods whose nested payloads (chunks/blocks) go as real JSON. */
55
+ const JSON_METHODS = new Set(["chat.startStream", "chat.appendStream", "chat.stopStream"]);
56
+ async function once(method, params) {
57
+ const auth = `Bearer ${await resolveToken(token)}`;
58
+ if (JSON_METHODS.has(method)) {
59
+ const body = {};
60
+ for (const [k, v] of Object.entries(params)) {
61
+ if (v !== undefined && v !== null)
62
+ body[k] = v;
63
+ }
64
+ return fetchImpl(`${baseUrl}/${method}`, {
65
+ method: "POST",
66
+ headers: { authorization: auth, "content-type": "application/json; charset=utf-8" },
67
+ body: JSON.stringify(body),
68
+ signal: AbortSignal.timeout(timeoutMs),
69
+ });
70
+ }
71
+ const form = new URLSearchParams();
72
+ for (const [k, v] of Object.entries(params)) {
73
+ if (v === undefined || v === null)
74
+ continue;
75
+ form.set(k, typeof v === "string" ? v : JSON.stringify(v));
76
+ }
77
+ return fetchImpl(`${baseUrl}/${method}`, {
78
+ method: "POST",
79
+ headers: {
80
+ authorization: auth,
81
+ "content-type": "application/x-www-form-urlencoded; charset=utf-8",
82
+ },
83
+ body: form,
84
+ signal: AbortSignal.timeout(timeoutMs),
85
+ });
86
+ }
87
+ async function call(method, params) {
88
+ let retried = false;
89
+ for (;;) {
90
+ const res = await once(method, params);
91
+ if (res.status === 429 && !retried) {
92
+ retried = true;
93
+ const after = Number(res.headers.get("retry-after") ?? 1);
94
+ await sleep(Math.min(Number.isFinite(after) ? after : 1, MAX_RETRY_AFTER_S) * 1000);
95
+ continue;
96
+ }
97
+ if (res.status >= 500 && !retried) {
98
+ retried = true;
99
+ await sleep(1000);
100
+ continue;
101
+ }
102
+ if (res.status === 429 || res.status >= 500) {
103
+ throw new SlackApiError(method, res.status, undefined, Number(res.headers.get("retry-after") ?? 0) || undefined);
104
+ }
105
+ const json = (await res.json());
106
+ if (json.ok !== true) {
107
+ const meta = json.response_metadata?.messages;
108
+ throw new SlackApiError(method, res.status, String(json.error ?? "unknown_error"), undefined, meta?.length ? meta.join("; ").slice(0, 500) : undefined);
109
+ }
110
+ return json;
111
+ }
112
+ }
113
+ return {
114
+ /**
115
+ * Post a message. NOTE: `markdown_text` is EXCLUSIVE with `text`/`blocks`
116
+ * (`markdown_text_conflict`) — for markdown with a notification fallback,
117
+ * use `markdown` blocks + `text` instead.
118
+ */
119
+ async postMessage(input) {
120
+ const r = await call("chat.postMessage", { unfurl_links: false, ...input });
121
+ return { ts: String(r.ts), channel: String(r.channel) };
122
+ },
123
+ /** Edit a previously posted message in place (e.g. resolve an approval card). */
124
+ async update(input) {
125
+ await call("chat.update", input);
126
+ },
127
+ /** Delete one of the bot's own messages (dead-stream cleanup). */
128
+ async deleteMessage(input) {
129
+ await call("chat.delete", input);
130
+ },
131
+ /** Post a message only the given user sees (sign-in links, refusals). */
132
+ async postEphemeral(input) {
133
+ await call("chat.postEphemeral", input);
134
+ },
135
+ // ── Streaming (AI apps) ──────────────────────────────────────────────────
136
+ /**
137
+ * Open a streamed message. In a CHANNEL Slack requires `recipient_team_id`
138
+ * (`missing_recipient_team_id` otherwise); in the bot's DM it's optional.
139
+ */
140
+ async startStream(input) {
141
+ const r = await call("chat.startStream", input);
142
+ return { ts: String(r.ts), channel: String(r.channel) };
143
+ },
144
+ /**
145
+ * Append to a live stream. Throws `SlackApiError` with code
146
+ * `stopped_by_user` when the viewer pressed the native Stop button —
147
+ * callers treat that as a stop signal, not a failure.
148
+ */
149
+ async appendStream(input) {
150
+ await call("chat.appendStream", input);
151
+ },
152
+ /** Close a stream, optionally with final content and (≤50) blocks. */
153
+ async stopStream(input) {
154
+ await call("chat.stopStream", input);
155
+ },
156
+ // ── Agent sessions ───────────────────────────────────────────────────────
157
+ /**
158
+ * Set a session's lifecycle status (`processing` shows the working UI and,
159
+ * with the `agent_session_stopped` subscription, the Stop button). Creates
160
+ * the session if it doesn't exist. Status does NOT auto-clear.
161
+ */
162
+ async sessionsSetStatus(input) {
163
+ await call("agents.sessions.setStatus", input);
164
+ },
165
+ // ── Conversations ────────────────────────────────────────────────────────
166
+ /** Fetch a thread's messages (oldest first) — for seeding prior context. */
167
+ async threadReplies(channel, ts, limit = 50) {
168
+ const r = await call("conversations.replies", { channel, ts, limit: String(limit) });
169
+ const msgs = r.messages ?? [];
170
+ return msgs.map((m) => ({
171
+ userId: m.user ? String(m.user) : undefined,
172
+ text: typeof m.text === "string" ? m.text : "",
173
+ isBot: Boolean(m.bot_id) || m.subtype === "bot_message",
174
+ ts: String(m.ts),
175
+ }));
176
+ },
177
+ /** Channel history (newest first, as Slack returns it). */
178
+ async conversationsHistory(input) {
179
+ const r = await call("conversations.history", {
180
+ channel: input.channel,
181
+ limit: String(input.limit ?? 50),
182
+ ...(input.oldest ? { oldest: input.oldest } : {}),
183
+ ...(input.latest ? { latest: input.latest } : {}),
184
+ });
185
+ const msgs = r.messages ?? [];
186
+ return msgs.map((m) => ({
187
+ userId: m.user ? String(m.user) : undefined,
188
+ text: typeof m.text === "string" ? m.text : "",
189
+ isBot: Boolean(m.bot_id) || m.subtype === "bot_message",
190
+ ts: String(m.ts),
191
+ threadTs: m.thread_ts ? String(m.thread_ts) : undefined,
192
+ }));
193
+ },
194
+ /**
195
+ * ALL member ids of a conversation, paginated. `maxPages` bounds the walk;
196
+ * hitting the bound throws (callers gating access must fail CLOSED, never
197
+ * conclude "not a member" from a truncated list).
198
+ */
199
+ async conversationsMembers(channel, maxPages = 25) {
200
+ const members = [];
201
+ let cursor;
202
+ for (let page = 0; page < maxPages; page++) {
203
+ const r = await call("conversations.members", {
204
+ channel,
205
+ limit: "1000",
206
+ ...(cursor ? { cursor } : {}),
207
+ });
208
+ for (const id of r.members ?? [])
209
+ members.push(id);
210
+ cursor = r.response_metadata?.next_cursor;
211
+ if (!cursor)
212
+ return members;
213
+ }
214
+ throw new SlackApiError("conversations.members", 200, "member_list_truncated");
215
+ },
216
+ /** Open (or fetch) a DM channel with a user; returns the IM channel id. */
217
+ async openDm(user) {
218
+ const r = await call("conversations.open", { users: user });
219
+ return String(r.channel?.id ?? "");
220
+ },
221
+ /**
222
+ * The channels this bot can post to, for pickers. PUBLIC channels only:
223
+ * the manifest asks for `channels:read` but deliberately not `groups:read`
224
+ * for listing purposes — a private channel never appears here and has to
225
+ * be entered by id. One page — a picker doesn't need to paginate a
226
+ * workspace, and Slack caps `limit` at 1000 anyway.
227
+ */
228
+ async conversationsList(limit = 200) {
229
+ const r = await call("conversations.list", {
230
+ exclude_archived: "true",
231
+ types: "public_channel",
232
+ limit: String(limit),
233
+ });
234
+ const channels = r.channels ?? [];
235
+ return channels.map((c) => ({ id: String(c.id), name: String(c.name ?? c.id) }));
236
+ },
237
+ /**
238
+ * v1 compatibility — the free-text assistant status line. Superseded by
239
+ * `sessionsSetStatus`; removed with the v1 channel.
240
+ * @deprecated
241
+ */
242
+ async setStatus(input) {
243
+ await call("assistant.threads.setStatus", input).catch(() => {
244
+ /* not an assistant thread / missing scope — status is best-effort */
245
+ });
246
+ },
247
+ };
248
+ }
249
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/slack/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Hand-rolled Slack Block Kit builders for the small surface a Latch channel
3
+ * needs. Answer text ships as native `markdown` blocks (Slack renders GFM —
4
+ * headings, tables, fenced code — itself); approval buttons carry SIGNED
5
+ * payloads built in payloads.ts. Hand-rolled to keep the package
6
+ * dependency-free — no Chat SDK runtime.
7
+ */
8
+ export declare const APPROVE_ACTION = "latch_approve";
9
+ export declare const DENY_ACTION = "latch_deny";
10
+ /** The settled-approval card shown after a decision (replaces the buttons). */
11
+ export declare function approvalResolvedBlocks(toolName: string, approved: boolean): unknown[];
12
+ /** Ephemeral OAuth sign-in prompt for a `connect_<name>` approval. */
13
+ export declare function connectBlocks(connectionName: string, authorizeUrl: string): unknown[];
14
+ /** Slack's `markdown` block cap. */
15
+ export declare const MARKDOWN_BLOCK_LIMIT = 12000;
16
+ /**
17
+ * Split answer text into native `markdown` blocks — Slack renders headings,
18
+ * tables, fenced code and task lists itself, so the text goes through verbatim.
19
+ * Splits prefer a blank line, then a newline, then a space near the limit, so
20
+ * a boundary almost never lands mid-word (mid-code-fence remains possible for
21
+ * a single 12k+ fence — unsplittable by construction).
22
+ */
23
+ export declare function markdownBlocks(text: string): unknown[];
24
+ /** The plain-notification `text` fallback Slack shows in pings/previews. */
25
+ export declare function notificationText(text: string): string;
26
+ /** v2 approval card — same shape as v1 but the button value is a SIGNED payload. */
27
+ export declare function approvalBlocksV2(signedValue: string, toolName: string, input: unknown): unknown[];
28
+ /** Ephemeral sign-in card for a user who hasn't linked a Latch account. */
29
+ export declare function signInCard(linkUrl: string, message?: string): unknown[];
30
+ /**
31
+ * Error surface for a failed run. A plain section, deliberately: the 2026
32
+ * `alert` block would be nicer but its exact JSON shape isn't verifiable
33
+ * without a live workspace — swap in once confirmed in the dev workspace.
34
+ */
35
+ export declare function errorBlocks(message: string): unknown[];
36
+ //# sourceMappingURL=blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/slack/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,eAAO,MAAM,WAAW,eAAe,CAAC;AAYxC,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,EAAE,CAYrF;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,EAAE,CAmBrF;AAED,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAmBtD;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CA8BjG;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAsBvE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAOtD"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Hand-rolled Slack Block Kit builders for the small surface a Latch channel
3
+ * needs. Answer text ships as native `markdown` blocks (Slack renders GFM —
4
+ * headings, tables, fenced code — itself); approval buttons carry SIGNED
5
+ * payloads built in payloads.ts. Hand-rolled to keep the package
6
+ * dependency-free — no Chat SDK runtime.
7
+ */
8
+ const SECTION_LIMIT = 2900; // Slack section text hard limit is 3000.
9
+ export const APPROVE_ACTION = "latch_approve";
10
+ export const DENY_ACTION = "latch_deny";
11
+ function truncateJson(value, max = 2500) {
12
+ let s;
13
+ try {
14
+ s = JSON.stringify(value, null, 2) ?? String(value);
15
+ }
16
+ catch {
17
+ s = String(value);
18
+ }
19
+ return s.length > max ? `${s.slice(0, max)}\n… (truncated)` : s;
20
+ }
21
+ /** The settled-approval card shown after a decision (replaces the buttons). */
22
+ export function approvalResolvedBlocks(toolName, approved) {
23
+ return [
24
+ {
25
+ type: "section",
26
+ text: {
27
+ type: "mrkdwn",
28
+ text: approved
29
+ ? `:white_check_mark: Approved *${toolName}*`
30
+ : `:x: Denied *${toolName}*`,
31
+ },
32
+ },
33
+ ];
34
+ }
35
+ /** Ephemeral OAuth sign-in prompt for a `connect_<name>` approval. */
36
+ export function connectBlocks(connectionName, authorizeUrl) {
37
+ return [
38
+ {
39
+ type: "section",
40
+ text: { type: "mrkdwn", text: `Connect *${connectionName}* to continue.` },
41
+ },
42
+ {
43
+ type: "actions",
44
+ elements: [
45
+ {
46
+ type: "button",
47
+ action_id: "latch_connect_noop", // link button: handled by url, no callback
48
+ style: "primary",
49
+ text: { type: "plain_text", text: `Sign in to ${connectionName}` },
50
+ url: authorizeUrl,
51
+ },
52
+ ],
53
+ },
54
+ ];
55
+ }
56
+ /** Slack's `markdown` block cap. */
57
+ export const MARKDOWN_BLOCK_LIMIT = 12_000;
58
+ /**
59
+ * Split answer text into native `markdown` blocks — Slack renders headings,
60
+ * tables, fenced code and task lists itself, so the text goes through verbatim.
61
+ * Splits prefer a blank line, then a newline, then a space near the limit, so
62
+ * a boundary almost never lands mid-word (mid-code-fence remains possible for
63
+ * a single 12k+ fence — unsplittable by construction).
64
+ */
65
+ export function markdownBlocks(text) {
66
+ const blocks = [];
67
+ let rest = text;
68
+ while (rest.length > MARKDOWN_BLOCK_LIMIT) {
69
+ const window = rest.slice(0, MARKDOWN_BLOCK_LIMIT);
70
+ const cut = (() => {
71
+ const para = window.lastIndexOf("\n\n");
72
+ if (para > MARKDOWN_BLOCK_LIMIT / 2)
73
+ return para;
74
+ const line = window.lastIndexOf("\n");
75
+ if (line > MARKDOWN_BLOCK_LIMIT / 2)
76
+ return line;
77
+ const space = window.lastIndexOf(" ");
78
+ if (space > MARKDOWN_BLOCK_LIMIT / 2)
79
+ return space;
80
+ return MARKDOWN_BLOCK_LIMIT;
81
+ })();
82
+ blocks.push({ type: "markdown", text: rest.slice(0, cut) });
83
+ rest = rest.slice(cut).replace(/^\s+/, "");
84
+ }
85
+ blocks.push({ type: "markdown", text: rest.length ? rest : " " });
86
+ return blocks;
87
+ }
88
+ /** The plain-notification `text` fallback Slack shows in pings/previews. */
89
+ export function notificationText(text) {
90
+ return text.replace(/\s+/g, " ").trim().slice(0, 200) || "New message";
91
+ }
92
+ /** v2 approval card — same shape as v1 but the button value is a SIGNED payload. */
93
+ export function approvalBlocksV2(signedValue, toolName, input) {
94
+ const blocks = [
95
+ { type: "section", text: { type: "mrkdwn", text: `:lock: Approve *${toolName}*?` } },
96
+ ];
97
+ if (input !== undefined) {
98
+ blocks.push({
99
+ type: "section",
100
+ text: { type: "mrkdwn", text: "```" + truncateJson(input) + "```" },
101
+ });
102
+ }
103
+ blocks.push({
104
+ type: "actions",
105
+ elements: [
106
+ {
107
+ type: "button",
108
+ action_id: APPROVE_ACTION,
109
+ style: "primary",
110
+ text: { type: "plain_text", text: "Approve" },
111
+ value: signedValue,
112
+ },
113
+ {
114
+ type: "button",
115
+ action_id: DENY_ACTION,
116
+ style: "danger",
117
+ text: { type: "plain_text", text: "Deny" },
118
+ value: signedValue,
119
+ },
120
+ ],
121
+ });
122
+ return blocks;
123
+ }
124
+ /** Ephemeral sign-in card for a user who hasn't linked a Latch account. */
125
+ export function signInCard(linkUrl, message) {
126
+ return [
127
+ {
128
+ type: "section",
129
+ text: {
130
+ type: "mrkdwn",
131
+ text: message ?? "Sign in to Latch to use this agent — takes a few seconds.",
132
+ },
133
+ },
134
+ {
135
+ type: "actions",
136
+ elements: [
137
+ {
138
+ type: "button",
139
+ action_id: "latch_link_noop", // link button: handled by url, no callback
140
+ style: "primary",
141
+ text: { type: "plain_text", text: "Sign in to Latch" },
142
+ url: linkUrl,
143
+ },
144
+ ],
145
+ },
146
+ ];
147
+ }
148
+ /**
149
+ * Error surface for a failed run. A plain section, deliberately: the 2026
150
+ * `alert` block would be nicer but its exact JSON shape isn't verifiable
151
+ * without a live workspace — swap in once confirmed in the dev workspace.
152
+ */
153
+ export function errorBlocks(message) {
154
+ return [
155
+ {
156
+ type: "section",
157
+ text: { type: "mrkdwn", text: `:warning: ${message.slice(0, SECTION_LIMIT - 20)}` },
158
+ },
159
+ ];
160
+ }
161
+ //# sourceMappingURL=blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../src/slack/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,aAAa,GAAG,IAAI,CAAC,CAAC,yCAAyC;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG,eAAe,CAAC;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,SAAS,YAAY,CAAC,KAAc,EAAE,GAAG,GAAG,IAAI;IAC9C,IAAI,CAAS,CAAC;IACd,IAAI,CAAC;QACH,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAE,QAAiB;IACxE,OAAO;QACL;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;oBACZ,CAAC,CAAC,gCAAgC,QAAQ,GAAG;oBAC7C,CAAC,CAAC,eAAe,QAAQ,GAAG;aAC/B;SACF;KACF,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,cAAsB,EAAE,YAAoB;IACxE,OAAO;QACL;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,cAAc,gBAAgB,EAAE;SAC3E;QACD;YACE,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,QAAQ;oBACd,SAAS,EAAE,oBAAoB,EAAE,2CAA2C;oBAC5E,KAAK,EAAE,SAAS;oBAChB,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,cAAc,EAAE,EAAE;oBAClE,GAAG,EAAE,YAAY;iBAClB;aACF;SACF;KACF,CAAC;AACJ,CAAC;AAED,oCAAoC;AACpC,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO,IAAI,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE;YAChB,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,IAAI,GAAG,oBAAoB,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjD,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,IAAI,GAAG,oBAAoB,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,KAAK,GAAG,oBAAoB,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YACnD,OAAO,oBAAoB,CAAC;QAC9B,CAAC,CAAC,EAAE,CAAC;QACL,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,aAAa,CAAC;AACzE,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,WAAmB,EAAE,QAAgB,EAAE,KAAc;IACpF,MAAM,MAAM,GAAc;QACxB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,mBAAmB,QAAQ,IAAI,EAAE,EAAE;KACrF,CAAC;IACF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,cAAc;gBACzB,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC7C,KAAK,EAAE,WAAW;aACnB;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,WAAW;gBACtB,KAAK,EAAE,QAAQ;gBACf,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE;gBAC1C,KAAK,EAAE,WAAW;aACnB;SACF;KACF,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAgB;IAC1D,OAAO;QACL;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO,IAAI,2DAA2D;aAC7E;SACF;QACD;YACE,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,QAAQ;oBACd,SAAS,EAAE,iBAAiB,EAAE,2CAA2C;oBACzE,KAAK,EAAE,SAAS;oBAChB,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,kBAAkB,EAAE;oBACtD,GAAG,EAAE,OAAO;iBACb;aACF;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO;QACL;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,GAAG,EAAE,CAAC,EAAE,EAAE;SACpF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Chat-id scheme v2 — `slack2_<app>_<channel>_<root>`.
3
+ *
4
+ * One Latch chat is one Slack conversation anchor:
5
+ *
6
+ * slack2_A123_C456_1712345678.000100 a thread (channel thread OR a session
7
+ * in the Agents pane — the pane is just
8
+ * threads in the bot's DM channel)
9
+ * slack2_A123_D789_dm the ONE chat for un-threaded messages
10
+ * in a DM channel — plain DMs keep their
11
+ * history instead of forking a new chat
12
+ * per message
13
+ *
14
+ * The app id is mandatory: two bots in the same channel can never share a chat,
15
+ * by construction. The `agent_session_stopped` event carries `channel` +
16
+ * `thread_ts`, so a stop maps to its chat with a pure function — no reverse
17
+ * index.
18
+ *
19
+ * v1 ids (`slack_…`) deliberately do NOT parse. The v1 scheme had an unscoped
20
+ * legacy form whose "adopt the old chat" fallback carried a documented
21
+ * cross-app hole; v2 is a clean break.
22
+ */
23
+ /** Root marker for the per-DM-channel chat (un-threaded messages). */
24
+ export declare const DM_ROOT = "dm";
25
+ export interface SlackChatRef {
26
+ /** Slack app id (`A…`) — the namespace. */
27
+ app: string;
28
+ /** Slack conversation id (`C…`/`G…`/`D…`). */
29
+ channel: string;
30
+ /** Thread root ts, or {@link DM_ROOT} for the un-threaded DM chat. */
31
+ root: string;
32
+ }
33
+ /** Build a v2 chat id. Throws on a malformed root — a bad id must never be minted. */
34
+ export declare function slackChatId(app: string, channel: string, root: string): string;
35
+ /**
36
+ * Parse a v2 chat id, or null for anything else — including every v1
37
+ * (`slack_…`) id, which is the clean break.
38
+ */
39
+ export declare function parseSlackChatId(id: string): SlackChatRef | null;
40
+ //# sourceMappingURL=ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/slack/ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,sEAAsE;AACtE,eAAO,MAAM,OAAO,OAAO,CAAC;AAK5B,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAIhE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Chat-id scheme v2 — `slack2_<app>_<channel>_<root>`.
3
+ *
4
+ * One Latch chat is one Slack conversation anchor:
5
+ *
6
+ * slack2_A123_C456_1712345678.000100 a thread (channel thread OR a session
7
+ * in the Agents pane — the pane is just
8
+ * threads in the bot's DM channel)
9
+ * slack2_A123_D789_dm the ONE chat for un-threaded messages
10
+ * in a DM channel — plain DMs keep their
11
+ * history instead of forking a new chat
12
+ * per message
13
+ *
14
+ * The app id is mandatory: two bots in the same channel can never share a chat,
15
+ * by construction. The `agent_session_stopped` event carries `channel` +
16
+ * `thread_ts`, so a stop maps to its chat with a pure function — no reverse
17
+ * index.
18
+ *
19
+ * v1 ids (`slack_…`) deliberately do NOT parse. The v1 scheme had an unscoped
20
+ * legacy form whose "adopt the old chat" fallback carried a documented
21
+ * cross-app hole; v2 is a clean break.
22
+ */
23
+ /** Reversible segment escaping: `_` is the id separator, `%` the escape. */
24
+ const encodeSeg = (v) => v.replace(/%/g, "%25").replace(/_/g, "%5F");
25
+ const decodeSeg = (v) => v.replace(/%5F/g, "_").replace(/%25/g, "%");
26
+ /** Root marker for the per-DM-channel chat (un-threaded messages). */
27
+ export const DM_ROOT = "dm";
28
+ /** A Slack message timestamp (`1712345678.000100`) — the only other root form. */
29
+ const THREAD_ROOT = /^\d+\.\d+$/;
30
+ /** Build a v2 chat id. Throws on a malformed root — a bad id must never be minted. */
31
+ export function slackChatId(app, channel, root) {
32
+ if (root !== DM_ROOT && !THREAD_ROOT.test(root)) {
33
+ throw new Error(`slack chat id: invalid root ${JSON.stringify(root)}`);
34
+ }
35
+ return `slack2_${encodeSeg(app)}_${encodeSeg(channel)}_${root}`;
36
+ }
37
+ /**
38
+ * Parse a v2 chat id, or null for anything else — including every v1
39
+ * (`slack_…`) id, which is the clean break.
40
+ */
41
+ export function parseSlackChatId(id) {
42
+ const m = /^slack2_([^_]+)_([^_]+)_(\d+\.\d+|dm)$/.exec(id);
43
+ if (!m)
44
+ return null;
45
+ return { app: decodeSeg(m[1]), channel: decodeSeg(m[2]), root: m[3] };
46
+ }
47
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../src/slack/ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,4EAA4E;AAC5E,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7E,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE7E,sEAAsE;AACtE,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC;AAE5B,kFAAkF;AAClF,MAAM,WAAW,GAAG,YAAY,CAAC;AAWjC,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,OAAe,EAAE,IAAY;IACpE,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,UAAU,SAAS,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,MAAM,CAAC,GAAG,wCAAwC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @intentface/latch-channels/slack
3
+ *
4
+ * The Slack adapter, v2: `slackChannel(...)` drives one agent's dedicated app
5
+ * (own-app-per-agent) — agent sessions, native streaming with task cards,
6
+ * initiator-owned threads, atomic turn admission. All hand-rolled Block Kit,
7
+ * no Chat SDK runtime dependency.
8
+ */
9
+ export { slackChannel } from "./slackChannel.js";
10
+ export type { SlackChannelConfig, SlackChatMeta, SlackThreadStore } from "./slackChannel.js";
11
+ export { slackChatId, parseSlackChatId, DM_ROOT } from "./ids.js";
12
+ export type { SlackChatRef } from "./ids.js";
13
+ export { normalizeSlackText, isRenderableUserMessage } from "./normalize.js";
14
+ export { encodeAction, decodeAction } from "./payloads.js";
15
+ export type { ActionPayload } from "./payloads.js";
16
+ export { renderSlackStream } from "./stream.js";
17
+ export type { SlackStreamOptions, SlackStreamResult } from "./stream.js";
18
+ export { slackManifest, createFromManifestUrl, SLACK_BOT_SCOPES } from "./manifest.js";
19
+ export type { SlackManifestInput } from "./manifest.js";
20
+ export { verifySlackSignature } from "./verify.js";
21
+ export { slackApi, SlackApiError } from "./api.js";
22
+ export type { SlackClient, SlackToken, SlackApiOptions, StreamChunk } from "./api.js";
23
+ export { markdownBlocks, notificationText, approvalBlocksV2, approvalResolvedBlocks, connectBlocks, signInCard, errorBlocks, APPROVE_ACTION, DENY_ACTION, } from "./blocks.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slack/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3D,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvF,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,aAAa,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @intentface/latch-channels/slack
3
+ *
4
+ * The Slack adapter, v2: `slackChannel(...)` drives one agent's dedicated app
5
+ * (own-app-per-agent) — agent sessions, native streaming with task cards,
6
+ * initiator-owned threads, atomic turn admission. All hand-rolled Block Kit,
7
+ * no Chat SDK runtime dependency.
8
+ */
9
+ export { slackChannel } from "./slackChannel.js";
10
+ export { slackChatId, parseSlackChatId, DM_ROOT } from "./ids.js";
11
+ export { normalizeSlackText, isRenderableUserMessage } from "./normalize.js";
12
+ export { encodeAction, decodeAction } from "./payloads.js";
13
+ export { renderSlackStream } from "./stream.js";
14
+ export { slackManifest, createFromManifestUrl, SLACK_BOT_SCOPES } from "./manifest.js";
15
+ export { verifySlackSignature } from "./verify.js";
16
+ export { slackApi, SlackApiError } from "./api.js";
17
+ export { markdownBlocks, notificationText, approvalBlocksV2, approvalResolvedBlocks, connectBlocks, signInCard, errorBlocks, APPROVE_ACTION, DENY_ACTION, } from "./blocks.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/slack/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,aAAa,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generate the Slack **app manifest** for a single agent's dedicated app. Used
3
+ * both ways: posted to `apps.manifest.create` (config-token / "one-button"
4
+ * flow), or shown to the user with a "Create from manifest" deep-link (manual
5
+ * flow). One app per agent → its own bot user and @-handle.
6
+ */
7
+ export interface SlackManifestInput {
8
+ /** Display name + handle of the bot (the agent's title). */
9
+ displayName: string;
10
+ /** Short description shown in Slack's app directory. */
11
+ description?: string;
12
+ /** The single webhook URL Slack sends events + interactions + commands to. */
13
+ requestUrl: string;
14
+ /** OAuth redirect URL (the install callback) — required for install flow. */
15
+ redirectUrl?: string;
16
+ }
17
+ /** Bot scopes for the supported feature set (mentions, DMs, posting, status). */
18
+ export declare const SLACK_BOT_SCOPES: string[];
19
+ /** Build the Slack manifest object (JSON-serializable). */
20
+ export declare function slackManifest(input: SlackManifestInput): Record<string, unknown>;
21
+ /** A pre-filled "Create New App → From a manifest" deep-link for the manual flow. */
22
+ export declare function createFromManifestUrl(manifest: Record<string, unknown>): string;
23
+ //# sourceMappingURL=manifest.d.ts.map