@stratusagent/channel-slack 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Slack adapter for Stratus agents. **One Slack app per agent** — Slack has no w
4
4
 
5
5
  - **Resumable conversations**: session keys are `slack:<agent>:<team>:<channel>:<thread_ts ?? ts>` (DMs: the DM channel id) — a thread is a conversation, it survives daemon restarts, and two agents sharing a thread keep fully separate sessions. A turn parked on a human when the daemon died is re-asked after the restart, and when it finishes its reply is posted into the thread as a fresh message — the placeholder it would have edited belonged to the old process.
6
6
  - **Streaming replies**: post a placeholder, edit as deltas arrive (throttled for `chat.update` limits), show `⚙ tool…` status lines, finalize with the full reply — split across messages when it outgrows one.
7
- - **Markdown is translated to Slack's mrkdwn** on the way out — `**bold**` to `*bold*`, `*italic*` to `_italic_`, `~~struck~~` to `~struck~`, `[text](https://…)` to `<https://…|text>`, and a `#` heading to a bold line, since mrkdwn has no headings — unless the heading already carries an asterisk of its own, which is left as it is: Slack has one bold delimiter and no way to nest it, so a second pair would print rather than render. Code spans and fences are left exactly as written, an unclosed fence included, so a `**` inside a snippet stays part of the snippet — a run of N backticks opens code and only a run of exactly N closes it, which is what lets ``a span with a ` in it`` and a ````-fence holding a ```-fence work. One or two unmatched backticks are literal text, as they are in Markdown. Anything whose spelling already matches — lists, block quotes, inline code — is untouched. A marker still being streamed stays literal until its closing half arrives.
7
+ - **Markdown is translated to Slack's mrkdwn** on the way out — `**bold**` to `*bold*`, `*italic*` to `_italic_`, `~~struck~~` to `~struck~`, `[text](https://…)` to `<https://…|text>`, and a `#` heading to a bold line, since mrkdwn has no headings — unless the heading already carries an asterisk of its own, which is left as it is: Slack has one bold delimiter and no way to nest it, so a second pair would print rather than render. Code spans and fences are left exactly as written, an unclosed fence included, so a `**` inside a snippet stays part of the snippet — a run of N backticks opens code and only a run of exactly N closes it, which is what lets ``a span with a ` in it`` and a ````-fence holding a ```-fence work. One or two unmatched backticks are literal text, as they are in Markdown. A run that has code inside it is still one run — ``**the `fs.read` tool**`` is bold around a snippet, not four literal asterisks — because the whole reply is rewritten at once, with the code held out of the rewrite rather than cut out of the text. Anything whose spelling already matches — lists, block quotes, inline code — is untouched. A marker still being streamed stays literal until its closing half arrives.
8
8
  - **Mention to start, then it stays in the thread.** A mention opens a conversation; every reply in that thread reaches the agent without tagging it again, and free-form DMs are unchanged. Who an untagged reply is for is decided by [Who a message is for](#who-a-message-is-for). Inbound mentions of other users are humanized to `@Display Name` for the model; redeliveries are deduped so a slow turn never runs twice.
9
9
  - **Approval buttons** for the gateway's `remote` permission mode: a gated tool call parks the turn and asks in its thread with **Allow once** / **Always allow** / **Deny**. See [Approving tool calls](#approving-tool-calls) — clicks are authorized by *who clicked*, never by who can see the message, and **Always allow** is offered only where the daemon would actually remember it.
10
10
  - **Tokens are gateway infrastructure secrets**, stored in the `channels` namespace of `~/.stratus/credentials.json` — never in an agent's credential allowlist:
@@ -44,20 +44,104 @@ they are the whole model:
44
44
  straight after it reaches the agent you just named, and the agent that
45
45
  *had* the thread stands down at the same instant rather than whenever its
46
46
  own app next catches up.
47
+ 5. **How an agent listens is the soul's to say.** Rules 3 and 4 are the
48
+ `thread` mode, the default. A soul with `listens: mentions` takes only
49
+ messages that name it, and hears the rest. One with `listens: judge`
50
+ hears everything and, while attentive — eight messages or fifteen
51
+ minutes after it last answered a message that named it, whichever ends
52
+ first — runs a turn nobody asked for on each untagged message, told it
53
+ may answer with nothing; past the window a message is heard with no
54
+ turn at all, and a mention re-arms it. A reply it chose to give on its
55
+ own does not: it takes the thread for the thread rule, and counts
56
+ against its own window like any other message. A turn that decides on silence posts nothing: its
57
+ placeholder is opened only on the first text it streams, never on a
58
+ tool line, and a failed one posts no error note — one the daemon died
59
+ inside, failed at the next start, included, a line it had already
60
+ streamed staying as it was. A line an attempt the
61
+ provider abandoned had already posted is taken back when the retry
62
+ decides on silence. Neither mode takes
63
+ part in the holder rule — that rule answers whose an untagged reply is,
64
+ and for these two the soul already has — so a thread-rule agent stands
65
+ down for a judging or mentions-only one that spoke last: the judging
66
+ one decides for itself, and the mentions-only one is being talked to
67
+ and waits to be named. A judging agent that chooses to speak takes the
68
+ thread the way any speaker does, recorded once its reply has landed —
69
+ so a message typed while it was still deciding may be answered by both
70
+ it and a thread-rule colleague that held the thread before. "Spoke
71
+ last" is read off the thread, not the clock: the holder is whichever
72
+ agent's reply sits lowest, since that is the one a reader answers, and
73
+ a placeholder posted early and filled in slowly sits above a reply
74
+ posted while it was being written. A colleague's reply is never
75
+ judged, only heard.
76
+ The window also counts messages this daemon has dispatched that the
77
+ session does not hold yet, so a burst typed inside one turn is judged
78
+ up to the window and heard past it.
79
+ 6. **Standing down is not leaving.** An agent in a thread hears what is
80
+ said to the other agent in it — the question that named its colleague,
81
+ the untagged replies that were the colleague's to answer, and what the
82
+ colleague *replied* — into its own session, with no turn run and
83
+ nothing posted. The next time it is asked, it answers as someone who
84
+ followed the conversation rather than one who stepped out of the room.
85
+ What it hears is marked as said to somebody else, so a stranger's words
86
+ in a shared thread never read to it as an instruction; and a stranger
87
+ overheard lowers its session's trust label exactly as one who addressed
88
+ it would, since their text is in the transcript either way. A
89
+ colleague's reply is heard under the colleague's own label, never as a
90
+ person's: an agent's word is at most `agent`, and carries whatever its
91
+ session has been exposed to. Replies only — a turn that said nothing,
92
+ or failed, is nothing to hear — and only while the hearer's app is
93
+ still in the channel, which is asked of Slack per reply: a person's
94
+ message reaches an agent through its own socket, so an app removed
95
+ from a private channel stops hearing on its own, but a reply is
96
+ forwarded by the daemon and would otherwise keep arriving.
47
97
 
48
98
  Everyone in the thread is talking to the same agent — a reply from a second
49
99
  person is a follow-up like any other, and channel messages reach the model
50
100
  prefixed with the speaker's display name so it knows who said what. A reply
51
101
  carrying a file, or one the author also broadcast to the channel, is a
52
102
  follow-up too; what Slack marks as bookkeeping — an edit, a deletion, a
53
- join — is not, and nothing answers it. **Attachment contents are not
54
- readable** — the app does not ask for `files:read` — so a message with files
55
- reaches the agent naming them and saying they cannot be opened, which is
56
- what lets it answer honestly instead of as though it had read the log. A
57
- file dropped in with nothing said is not a question, and gets no reply. What is
58
- *not* shared is history: sessions are per agent, so an agent tagged into a
59
- thread halfway through starts from what it is told then, not from what the
60
- other agent was told. Bring it up to speed in the message that tags it.
103
+ join — is not, and nothing answers it. **Images are shown to the model.**
104
+ A PNG, JPEG, GIF, or WebP attached to a message is downloaded with the
105
+ bot token and sent with the message — a screenshot alone, with nothing
106
+ typed, is a question in its own right and gets an answer. That needs the
107
+ `files:read` scope, which the shipped manifest asks for; an app installed
108
+ without it should have the scope added under **OAuth & Permissions** and be
109
+ reinstalled once, and until then `stratus serve` warns, naming the scope,
110
+ each time an image arrives. An image over 5 MB or 8000 pixels a side (the
111
+ model API's limits) is not kept, and one message's images stop at 20 MB
112
+ together — a request has a size limit too, and images past it are named as
113
+ unreadable; send them in a message of their own. The same 20 MB — and 20
114
+ images — is what a whole thread's images may take on one request, spent
115
+ newest first — and the request as a whole has a limit too, so when the
116
+ rest of the conversation needs the room the oldest images give way to it.
117
+ Once a thread's images pass a limit the oldest are let
118
+ go of — the model is told an image was there and what it was called, and
119
+ the session keeps that record rather than the pixels, so a busy thread's
120
+ row stays bounded. A message's downloads share one 30-second deadline,
121
+ after which whatever has not arrived is named as unreadable, so a slow
122
+ link cannot hold the thread. And an image the model API itself refuses —
123
+ the adapter checks that a file opens and closes like the image it claims
124
+ to be, but that is not a decode — is dropped from the session and the turn
125
+ retried without it, so one bad file cannot fail every later turn of a
126
+ thread.
127
+ An OpenAI-compatible model that takes only text needs `"vision": false` in
128
+ config, which turns every image into that note; see the
129
+ [Slack guide](../../docs/guides/slack.md#sending-an-image). Whether the model actually *sees* the image depends on the
130
+ agent's runtime: the Anthropic API and OpenAI-compatible providers send it
131
+ as image content; the Claude Code and Codex harnesses take a text prompt,
132
+ so there the agent is told the image's name and that it cannot see it.
133
+ **Every other attachment is unreadable** — a log, a PDF, an image that was
134
+ too large — so the message reaches the agent naming those files and saying
135
+ they cannot be opened, which is what lets it answer honestly instead of as
136
+ though it had read the log. Such a file dropped in with nothing said is not
137
+ a question, and gets no reply.
138
+ Sessions are still per agent: an agent hears a thread from the mention
139
+ that brought it in, and what was said before that — to the other agent, or
140
+ by it — is not backfilled ([#147](https://github.com/stratuslabs/agent/issues/147)).
141
+ Bring it up to speed in the message that tags it. A colleague's reply is
142
+ heard by the daemon that posted it, not through Slack — an agent served by
143
+ another daemon is a stranger's bot to this one, and its replies are not
144
+ heard at all.
61
145
 
62
146
  Three edges worth knowing. An agent whose app was installed before the
63
147
  history scopes below is told about mentions only, and behaves exactly as it
@@ -75,10 +159,10 @@ always given when an app is down.
75
159
  And the rules above are mechanical, which shows in a thread where people are
76
160
  mostly talking to *each other*: an agent invited into one answers every
77
161
  untagged reply in it, including the ones meant for somebody else. Give the
78
- side conversation its own thread. Teaching an agent to read the room
79
- instead — to follow a thread whether or not it is being spoken to, and
80
- answer only when it has something to add — is
81
- [roadmap step 31](../../docs/roadmap/31-reading-the-room.md).
162
+ side conversation its own thread. It can now *hear* a thread it is not
163
+ answering; teaching it to choose — to answer only when it has something to
164
+ add, and to let "thanks, we've got it" be a sentence it read — is the rest
165
+ of [roadmap step 31](../../docs/roadmap/31-reading-the-room.md).
82
166
 
83
167
  ## Installing
84
168
 
@@ -104,7 +188,7 @@ editing `credentials.json` by hand.
104
188
  The manual equivalent, if you prefer:
105
189
 
106
190
  1. https://api.slack.com/apps → **Create New App → From a manifest** → paste `manifest/stratus-agent.manifest.json` with `NAME` replaced by the agent's name.
107
- The manifest asks for the `channels:history` / `groups:history` / `mpim:history` scopes and the matching `message.*` events, which is what lets an agent [stay in a thread](#who-a-message-is-for) instead of needing a mention every time. An app created before those shipped needs them added under **OAuth & Permissions** and **Event Subscriptions** and reinstalled once; leave them off and it answers mentions and DMs, exactly as it did before.
191
+ The manifest asks for the `channels:history` / `groups:history` / `mpim:history` scopes and the matching `message.*` events, which is what lets an agent [stay in a thread](#who-a-message-is-for) instead of needing a mention every time, and for `files:read`, which is what lets it [see an attached image](#who-a-message-is-for). An app created before those shipped needs them added under **OAuth & Permissions** and **Event Subscriptions** and reinstalled once; leave the history scopes off and it answers mentions and DMs, exactly as it did before; leave `files:read` off and it hears about attachments by name only.
108
192
  2. **Basic Information → App-Level Tokens** → generate a token with `connections:write` (that's the `appToken`, `xapp-…`).
109
193
  3. **Install App** to the workspace → copy the **Bot User OAuth Token** (that's the `botToken`, `xoxb-…`).
110
194
  4. Upload the agent's avatar under **Display Information**.
@@ -128,12 +212,20 @@ the parser cannot reduce to a scope. The prompt says so instead, because a
128
212
  button that does exactly what **Allow once** does, under a label promising a
129
213
  standing grant, is worse than no button.
130
214
 
215
+ Where it is offered, a line beside the buttons says what it would do,
216
+ because that differs by tool and is the half of the question the button
217
+ widens: for most gated tools it is a **standing grant** to the agent that
218
+ lasts until an operator revokes it (`stratus grants <agent>` lists and
219
+ revokes; see [Approvals](../../docs/guides/approvals.md)); for a shell
220
+ command it is that command's scope; for a browser action it is the site;
221
+ and for a send outside a schedule it is the rest of this session.
222
+
131
223
  The resolved message describes what the daemon did rather than which button
132
224
  was pressed — `POST /approvals` accepts `always` whatever this channel
133
- rendered, so the answer can arrive from somewhere else. It states the floor
134
- rather than the ceiling: a remembered answer lasts *at least* the session,
135
- and whether a scoped one also survives a restart depends on the daemon
136
- writing the whitelist, which is not known when the message is sent.
225
+ rendered, so the answer can arrive from somewhere else — and names the grant
226
+ that was made, since the request said in advance which kind it would be.
227
+ The one thing it cannot promise is the disk write: a daemon that could not
228
+ read the whitelist does not write over it, and by then the message is sent.
137
229
  `stratus logs` has the exact line.
138
230
 
139
231
  Who may answer is configured per agent, in `~/.stratus/config.json`:
@@ -225,12 +317,62 @@ With no list at all, every Slack sender is `unknown`, and `stratus serve`
225
317
  says so at startup. An agent's own entry replaces the shared list;
226
318
  `"slackUsers": []` excludes an agent from it.
227
319
 
320
+ The list also decides how a speaker is *named* in a channel turn, which
321
+ the model reads as `Name: text`. A principal is their display name — one
322
+ line, at most 80 characters, control characters and Unicode bidi controls
323
+ spelled out — because
324
+ their profile is trusted the way their messages are. With a list in force,
325
+ everyone else is their user id, exactly as an `@mention` of them already
326
+ is: a display name is text its owner typed, and the speaker position of a
327
+ user turn is not a place a stranger gets to put a sentence. With no list
328
+ at all there is nobody to prefer, so every author keeps their name — and
329
+ "no list" is the key being absent; `"slackUsers": []` is a list that
330
+ prefers nobody, so under it every author is an id.
331
+
228
332
  The sender is judged on **every message**, not once per thread. A thread
229
333
  keys one session for everyone in it, so an authorized member can open one
230
334
  and a stranger can mention the agent inside it afterwards — the stranger's
231
335
  turn lowers the session, and the authorized member's next turn does not
232
336
  raise it back.
233
337
 
338
+ The list is a **label** by default: an unlisted sender still gets a turn,
339
+ marked `unknown`. For an agent that holds tools that is not enough — a
340
+ stranger's message is not merely uncertain, it is a prompt they chose, and
341
+ `unknown` does not stop `shell.run` from running on it once a listed
342
+ approver clicks. `admit` makes the list a **door**:
343
+
344
+ ```jsonc
345
+ {
346
+ "principals": {
347
+ "slackUsers": ["U01DYLAN"],
348
+ "admit": "principals",
349
+ "agents": {
350
+ "bea": { "admit": "anyone" }
351
+ }
352
+ }
353
+ }
354
+ ```
355
+
356
+ Under `"admit": "principals"` a sender not in `slackUsers` gets no turn:
357
+ the message is refused before it takes a place in the queue, the agent
358
+ does not overhear it into a thread's transcript either, and nothing is
359
+ posted back — a reply is a conversation the operator chose not to have.
360
+ The refusal is one line in the daemon log naming the agent and the user
361
+ id — one per message, however many times Slack delivers it, and only for a
362
+ message the agent would have taken up: a mention, or a reply in a thread
363
+ the agent holds, whether the daemon remembers that or its sessions do. A
364
+ stranger's reply in a thread the agent was never part of is dropped
365
+ without a line. `"anyone"` is
366
+ the default and today's behavior; the key inherits per agent like
367
+ `slackUsers`, so Bea above stays open while everyone else is closed. A value
368
+ that is neither word is a config error, never `anyone`: this is the one
369
+ setting here whose misspelling would open the door. So is a `slackUsers`
370
+ that is not a list of user ids, and so is a block or a per-agent entry that
371
+ is not an object: dropped for its shape, a per-agent list would fall back to
372
+ the shared one, the broader list it existed to narrow, and the block itself
373
+ would fall back to `anyone`.
374
+ `stratus serve` says at startup which agents refuse unlisted senders.
375
+
234
376
  Like `approvals`, this block is read only from a config you chose —
235
377
  `--config`, `STRATUS_CONFIG`, or the global `~/.stratus/config.json`. A
236
378
  project-local `stratus.config.json` cannot appoint itself the principal.
package/dist/index.d.ts CHANGED
@@ -31,6 +31,14 @@ export interface SlackAgentConfig {
31
31
  * nobody is, and every sender is `unknown`.
32
32
  */
33
33
  principals?: string[];
34
+ /**
35
+ * Whether an unlisted sender gets a turn at all. `anyone` (the default)
36
+ * admits them as `unknown`; `principals` refuses them before a turn
37
+ * starts and does not let the agent overhear them either — the text
38
+ * would be in the transcript, which is what this mode exists to keep
39
+ * out. The label above is provenance; this is authorization.
40
+ */
41
+ admit?: 'anyone' | 'principals';
34
42
  }
35
43
  export interface SlackSocketEventArgs {
36
44
  ack: (response?: unknown) => Promise<void>;
@@ -81,19 +89,77 @@ export interface SlackInboundEvent {
81
89
  /**
82
90
  * Attachments on a `file_share` message. Slack puts the file metadata in
83
91
  * the event and the bytes behind an authenticated URL that needs
84
- * `files:read`, which this app does not ask for — so a turn learns that a
85
- * file arrived and what it is called, never what is in it.
92
+ * `files:read`. An image the model can take is downloaded and sent with
93
+ * the message (see `readImageAttachments`); anything else — a log, a
94
+ * PDF, an image too large or in a format the API refuses — reaches the
95
+ * turn by name only, told that it cannot be opened.
86
96
  */
87
- files?: Array<{
88
- name?: string;
89
- title?: string;
90
- }>;
97
+ files?: SlackInboundFile[];
98
+ }
99
+ /** The slice of Slack's file object the adapter reads. */
100
+ export interface SlackInboundFile {
101
+ id?: string;
102
+ name?: string;
103
+ title?: string;
104
+ /** Slack's own `image/png`-style type, decided by the uploader's client. */
105
+ mimetype?: string;
106
+ /** Bytes, as Slack reports them; checked before anything is downloaded. */
107
+ size?: number;
108
+ url_private?: string;
109
+ url_private_download?: string;
91
110
  }
111
+ /**
112
+ * What came back from fetching a file's private URL. Slack answers a
113
+ * request the app is not allowed to make — a token without `files:read` —
114
+ * with a **200 and a sign-in page**, never an error status, which is why
115
+ * the content type travels: it is the only thing that tells an image from
116
+ * an HTML apology for one.
117
+ */
118
+ export interface SlackFileDownload {
119
+ status: number;
120
+ contentType?: string;
121
+ body: Buffer;
122
+ /**
123
+ * Set when the body was cut off at `maxBytes`: what is in `body` is not
124
+ * the file, only proof that the file was bigger than the caller would
125
+ * take.
126
+ */
127
+ truncated?: boolean;
128
+ }
129
+ /**
130
+ * Fetches a file's private URL with the bot token. `signal` is the
131
+ * download's deadline, and a fetcher MUST stop on it — headers and body
132
+ * both: the download runs inside the session's intake chain, so one that
133
+ * stalls holds every later message in the thread, and `stop()` waits on
134
+ * the handler too. `maxBytes` is the most body a fetcher may buffer; one
135
+ * that reads past it MUST stop there and report `truncated`, because
136
+ * Slack's reported size is advisory and a response is not.
137
+ */
138
+ export type SlackFileFetcher = (url: string, botToken: string, signal: AbortSignal, maxBytes: number) => Promise<SlackFileDownload>;
92
139
  export interface SlackSocketLike {
93
140
  on(eventName: string, listener: (args: SlackSocketEventArgs) => void): void;
94
141
  start(): Promise<unknown>;
95
142
  disconnect(): Promise<void>;
96
143
  }
144
+ /**
145
+ * What `files.uploadV2` answers with, as much of it as is read here: the
146
+ * file's shares, each carrying the ts of the message the file became in a
147
+ * channel — where it sits in the thread. Whether a just-completed upload's
148
+ * shares are filled in yet is Slack's business, so every part is optional
149
+ * and a reader without one falls back (see `ReplyRenderer.queueUpload`).
150
+ */
151
+ export interface SlackUploadResult {
152
+ files?: Array<{
153
+ shares?: {
154
+ public?: Record<string, Array<{
155
+ ts?: string;
156
+ }>>;
157
+ private?: Record<string, Array<{
158
+ ts?: string;
159
+ }>>;
160
+ };
161
+ }>;
162
+ }
97
163
  export interface SlackWebLike {
98
164
  auth: {
99
165
  test(): Promise<{
@@ -117,6 +183,15 @@ export interface SlackWebLike {
117
183
  text: string;
118
184
  blocks?: SlackBlock[];
119
185
  }): Promise<unknown>;
186
+ /**
187
+ * Take a message back. The one caller is a turn nobody asked for that
188
+ * opened its placeholder for an attempt the provider then abandoned and
189
+ * decided on silence after all — see `ReplyRenderer.retract`.
190
+ */
191
+ delete(args: {
192
+ channel: string;
193
+ ts: string;
194
+ }): Promise<unknown>;
120
195
  /**
121
196
  * Visible only to `user`. Refusing a click needs to reach the person
122
197
  * who clicked without announcing to the channel that they tried.
@@ -135,7 +210,7 @@ export interface SlackWebLike {
135
210
  filename?: string;
136
211
  title?: string;
137
212
  thread_ts?: string;
138
- }): Promise<unknown>;
213
+ }): Promise<SlackUploadResult>;
139
214
  };
140
215
  conversations: {
141
216
  /**
@@ -178,7 +253,38 @@ export interface SlackAdapterOptions {
178
253
  createSocketClient?: (appToken: string) => SlackSocketLike;
179
254
  /** Test injection: build a Web API client for a bot token. */
180
255
  createWebClient?: (botToken: string) => SlackWebLike;
256
+ /**
257
+ * Test injection: fetch a file's private URL with the bot token. The
258
+ * Web API client has no call for this — file bytes are served from a
259
+ * plain authenticated URL — so it is its own seam.
260
+ */
261
+ fetchFile?: SlackFileFetcher;
262
+ /**
263
+ * How long one message's attachment downloads may take, all of them
264
+ * together, before the turn goes on without whatever has not arrived.
265
+ * Defaults to `DEFAULT_FILE_DOWNLOAD_TIMEOUT_MS`.
266
+ */
267
+ fileDownloadTimeoutMs?: number;
268
+ /**
269
+ * Test injection: the clock, in milliseconds, that an upload's place in
270
+ * the thread is read from (`ReplyOutcome.spokeAt`) when the upload's
271
+ * answer carries no share ts. Defaults to `Date.now`.
272
+ */
273
+ now?: () => number;
181
274
  }
275
+ /**
276
+ * The default fetcher over a `fetch`. Exported as a factory so the bounded
277
+ * read can be tested against a fake `fetch` with a body that never ends;
278
+ * the adapter uses it over the global one.
279
+ *
280
+ * The body is read a chunk at a time and abandoned the moment it passes
281
+ * `maxBytes`: `arrayBuffer()` would buffer the whole response first, and
282
+ * the size the adapter checked before asking came from Slack's metadata,
283
+ * which an oversized response is under no obligation to match. A
284
+ * `content-length` that already says too much is refused before a byte is
285
+ * read.
286
+ */
287
+ export declare const createSlackFileFetcher: (fetchImpl: typeof fetch) => SlackFileFetcher;
182
288
  /**
183
289
  * The Slack channel adapter. One Slack app per agent — Slack has no way to
184
290
  * give a single bot several identities with real avatars, presence, and
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}