@stratusagent/channel-slack 0.11.10 → 0.12.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Slack adapter for Stratus agents. **One Slack app per agent** — Slack has no way to give a single bot several identities with real avatars, presence, and DMs, so each agent gets its own app, and the adapter runs one Socket Mode connection per agent (no public ingress needed; Mac Minis behind NAT are fine).
4
4
 
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 a turn that was still running is continued from where it got to (unless the agent runs on a harness — `codex` or a Claude subscription — where it is failed instead, since the harness may already have acted on it); either way, 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.
5
+ - **Resumable conversations**: session keys are `slack:<agent>:<team>:<channel>:<thread_ts ?? ts>` (a DM's top level: the DM channel id alone; a thread in a DM adds its `thread_ts`) — 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 a turn that was still running is continued from where it got to (unless the agent runs on a harness — `codex` or a Claude subscription — where it is failed instead, since the harness may already have acted on it); either way, 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
  - **Replies post when they are ready**: by default each message is posted once, finished: what the agent writes before starting a tool is posted as the tool starts, and the reply follows, with Slack's loading status (`assistant.threads.setStatus` — "is thinking…", "is running shell.run…") shown while the turn works, so the notification carries the answer. The `stream` reply mode (`slack.replies` in the [config](../../docs/guides/slack.md#how-replies-appear)) keeps the placeholder-and-edit rendering instead: post `…`, edit as deltas arrive (throttled for `chat.update` limits), show `⚙ tool…` status lines, finalize with the full reply. Either way a reply that outgrows one message is split across several.
7
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. A bare address is sent as `<https://…>`, Slack's own link markup, so emphasis around it (`**https://…**`) cannot be read as part of it, and a `*`, `_`, or `~` inside one is part of the address rather than a marker. Trailing punctuation (`.`, `,`, `**`) and a `)` the address never opened belong to the sentence, as in GFM autolinks, so `…/wiki/Function_(mathematics)` stays whole. A bare address is sent as `<https://…>`, Slack's own link markup, so emphasis around it (`**https://…**`) cannot be read as part of it, and the `*`, `_`, or `~` inside one is part of the address rather than a marker; trailing punctuation (`.`, `,`, `**`) is the sentence's, as in GFM autolinks. The reply is read once into tokens and written back from those, so what counts as code is decided one time rather than by each rule in turn. Three things follow from that. **A code run is one token**: nothing reads or rewrites inside it, an unclosed fence included, and the newlines in a fence are not line breaks — so a `#` on a line of somebody's shell script is not a heading. 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 text, as they are in Markdown, but a pair is a span however far apart, because Slack pairs them too. **A delimiter is matched to its partner**, across whatever sits between — ``**the `fs.read` tool**`` is bold around a snippet, not four literal asterisks — though never across a line, so a marker still being streamed stays literal until its closing half arrives. A run is spent rather than matched whole, so the three asterisks ending `**bold and *italic***` close the italic with one and the bold with two; what no pair spends stays on the line as text, and what a run cannot buy — the third tilde of `~~~struck~~~`, a style that does not exist — is never charged for. **A wrapper is only written where its own character is not already loose** in what it wraps: Slack has one delimiter per style and no way to nest it, so a heading that already contains bold keeps the bold it has rather than gaining a second pair that would print instead of render, and `# glob *.ts` keeps an asterisk that was never a marker. A heading sheds its hashes and its trailing spaces from the prose that holds them, never from a snippet. Anything whose spelling already matches — lists, block quotes, inline code — is untouched. **A pipe table**, which mrkdwn has no syntax for, becomes a code block with its columns padded to line up (alignment colons respected, `**`/`` ` `` markers dropped from cells, since nothing renders inside code); a grid wider than 60 characters — past what a phone shows unwrapped — or one with a link in it (a link inside a code block is not clickable) becomes one line per row, `*Header*: value · *Header*: value`. A one-column table has nothing to line up and becomes a bulleted list under its bold header. A table inside a fence is somebody's text and stays as written, and a grid longer than one message is split like any code block, closed at the cut and reopened in the next, with its header and rule repeated so every part names its columns.
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. A mention partway into an existing thread opens the conversation with what was said before it: the thread's parent and its newest 39 earlier messages. A mention at the top of a channel opens with the channel's 20 most recent messages instead, which is where a channel per client or per project keeps its context. Either way each message keeps its own speaker and its own trust, so a principal's mention does not carry a stranger's earlier words in under the principal's label (other bots count as `unknown`, and under `admit: "principals"` unlisted senders are left out). This needs the history scopes below; without them the agent sees only the message that named it, and the adapter logs a warning. 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 — in memory while the process remembers them, and after a restart by the gateway: each message is dispatched with its own key (`idempotencyKey`), a repeat runs no second turn, and the adapter posts that message's own reply and files (a file changed since that turn is skipped, since the path may now hold another file), or its failure if the turn failed — once more, if the process died after posting it, which beats a reply never posted.
@@ -146,8 +146,17 @@ message after what was said, between `[Attached: plan.md. Its contents
146
146
  follow.]` and `[End of plan.md]`. A text file dropped in alone is a
147
147
  question, like a screenshot. One file may be 100 KB and one message's text
148
148
  files 200 KB together, since read text joins the conversation and is sent
149
- again every turn; a larger one is never downloaded, and `stratus serve`
150
- warns with the file's name. A file that is not UTF-8, or that comes back as
149
+ again every turn. A larger one, up to 10 MB, is saved under the agent's
150
+ workspace (`attachments/slack/<stamp>-<file id>-<name>`, `0600`) when the
151
+ host passes `attachmentStore`, and the message says `[Attached: spec.md,
152
+ 251 KB, too large to include here. It is saved in full at <path>. …]`. The
153
+ path is recorded in the tainted-write ledger at the sender's label before
154
+ the bytes are written, so `fs.read` of it carries that label. One agent's
155
+ saved attachments are bounded together (`attachmentStore.maxBytes`, 256 MB
156
+ by default, and 1,000 files): past it a save is refused, the file reaches the turn as a
157
+ name, and the warning names the directory to clear. Nothing is deleted. `stratus
158
+ serve` passes it; a host that does not, or a file over 10 MB, gets the name
159
+ only, and `stratus serve` warns with the file's name. A file that is not UTF-8, or that comes back as
151
160
  Slack's sign-in page because the scope is missing, is treated as unreadable
152
161
  and the warning names the scope. Text files share the message's 30-second
153
162
  download deadline with its images.
@@ -187,7 +196,7 @@ untagged reply in it, including the ones meant for somebody else. Give the
187
196
  side conversation its own thread. It can now *hear* a thread it is not
188
197
  answering; teaching it to choose — to answer only when it has something to
189
198
  add, and to let "thanks, we've got it" be a sentence it read — is the rest
190
- of [roadmap step 31](../../docs/roadmap/31-reading-the-room.md).
199
+ of the reading-the-room work.
191
200
 
192
201
  ### What the agent is told about the room
193
202
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type AgentWorkspaces, type TrustLevel } from '@stratusagent/core';
1
2
  import { type AdmitPolicy, type ChannelAdapter } from '@stratusagent/channels';
2
3
  /** One agent's Slack identity: its own app (avatar, presence) and tokens. */
3
4
  export interface SlackAgentConfig {
@@ -45,6 +46,12 @@ export interface SlackAgentConfig {
45
46
  * a `…` placeholder at once and edits it as the reply is written.
46
47
  */
47
48
  replies?: 'final' | 'stream';
49
+ /**
50
+ * `false` posts this agent's messages without link and media previews
51
+ * (Slack's `unfurl_links`/`unfurl_media`). Absent or `true` leaves
52
+ * Slack's default, which previews them.
53
+ */
54
+ linkPreviews?: boolean;
48
55
  /**
49
56
  * Channel ids where a new top-level message from an admitted sender is
50
57
  * this agent's to answer without a mention: the channel people come to
@@ -255,6 +262,8 @@ export interface SlackWebLike {
255
262
  text: string;
256
263
  thread_ts?: string;
257
264
  blocks?: SlackBlock[];
265
+ unfurl_links?: boolean;
266
+ unfurl_media?: boolean;
258
267
  }): Promise<{
259
268
  ts?: string;
260
269
  channel?: string;
@@ -409,6 +418,31 @@ export interface SlackAdapterOptions {
409
418
  * Defaults to `DEFAULT_FILE_DOWNLOAD_TIMEOUT_MS`.
410
419
  */
411
420
  fileDownloadTimeoutMs?: number;
421
+ /**
422
+ * Where a text attachment too large to read into the message is saved
423
+ * instead, so the turn can be told a path rather than only a name.
424
+ *
425
+ * Both halves or neither: the file holds what a Slack member sent, and
426
+ * read back through `fs.read` without a ledger record it would arrive as
427
+ * the agent's own notes — the laundering the ledger exists to stop. So the
428
+ * path is recorded at the sender's label before the bytes land, as
429
+ * `plugin-mcp` does for a server's files. Without this option such a file
430
+ * reaches the turn as a name only, as before.
431
+ */
432
+ attachmentStore?: {
433
+ workspaces: AgentWorkspaces;
434
+ /** The host's tainted-write ledger, the one `tool-fs` reads. */
435
+ ledger: {
436
+ recordWrite(agentId: string, absolutePath: string, trust: TrustLevel): Promise<void>;
437
+ };
438
+ /**
439
+ * The most one agent's saved attachments may weigh together, so anyone
440
+ * admitted to talk to the agent cannot fill the host's disk. A save that
441
+ * would pass it is refused (the file is a name-only note and the log
442
+ * says where to make room); nothing is deleted. Defaults to 256 MB.
443
+ */
444
+ maxBytes?: number;
445
+ };
412
446
  /**
413
447
  * Test injection: the clock, in milliseconds, that an upload's place in
414
448
  * the thread is read from (`ReplyOutcome.spokeAt`) when the upload's
@@ -416,6 +450,13 @@ export interface SlackAdapterOptions {
416
450
  */
417
451
  now?: () => number;
418
452
  }
453
+ /**
454
+ * A client whose every post goes out without link or media previews, for
455
+ * an agent configured with `linkPreviews: false`. Applied once around the
456
+ * client, so no post site can forget it. `chat.update` takes no unfurl
457
+ * flags; an edited placeholder keeps the post's own setting.
458
+ */
459
+ export declare const withoutLinkPreviews: (web: SlackWebLike) => SlackWebLike;
419
460
  /**
420
461
  * The default fetcher over a `fetch`. Exported as a factory so the bounded
421
462
  * read can be tested against a fake `fetch` with a body that never ends;
@@ -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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"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { lstat, open, readFile } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, mkdir, open, readdir, readFile, realpath } from 'node:fs/promises';
3
4
  import { createRequire } from 'node:module';
4
5
  import path from 'node:path';
5
6
  import { filePathsOf, isLatestTurn, latestTurnReply, MAX_IDEMPOTENCY_KEY_LENGTH, turnFailureFor, turnFilesFor, turnReplyFor, CONVERSATION_METADATA_KEY, SENDER_TRUST_METADATA_KEY, sessionWriteTrust, imageDimensions, isImageAttachmentMediaType, IMAGE_ATTACHMENT_MAX_BYTES, IMAGE_ATTACHMENT_MAX_DIMENSION, IMAGE_ATTACHMENTS_MAX_TOTAL_BYTES, escapeControlCharacters, leastTrusted, sessionTrustOf, SESSION_TRUST_METADATA_KEY, } from '@stratusagent/core';
@@ -138,6 +139,21 @@ const checkBotScopes = (agentId, granted, warn) => {
138
139
  + 'Without them, some features (thread follow-through, image viewing, '
139
140
  + 'channel history) will silently fail.');
140
141
  };
142
+ /**
143
+ * A client whose every post goes out without link or media previews, for
144
+ * an agent configured with `linkPreviews: false`. Applied once around the
145
+ * client, so no post site can forget it. `chat.update` takes no unfurl
146
+ * flags; an edited placeholder keeps the post's own setting.
147
+ */
148
+ export const withoutLinkPreviews = (web) => {
149
+ // Delegating objects, not copies: everything else on the client (and a
150
+ // method replaced on it later) is still reached through the original.
151
+ const chat = Object.create(web.chat);
152
+ chat.postMessage = (args) => web.chat.postMessage({ ...args, unfurl_links: false, unfurl_media: false });
153
+ const wrapped = Object.create(web);
154
+ wrapped.chat = chat;
155
+ return wrapped;
156
+ };
141
157
  const defaultWebClient = (botToken) => {
142
158
  const { WebClient } = requireModule('@slack/web-api');
143
159
  return new WebClient(botToken);
@@ -1631,6 +1647,136 @@ const TEXT_ATTACHMENT_MAX_BYTES = 100 * 1024;
1631
1647
  * is a cost decision as much as a context one.
1632
1648
  */
1633
1649
  const TEXT_ATTACHMENTS_MAX_TOTAL_BYTES = 200 * 1024;
1650
+ /**
1651
+ * The most a text attachment may weigh to be saved to the agent's
1652
+ * workspace when it is too large to read into the message. A 251 KB
1653
+ * Markdown spec reached an agent as a filename and it had no way to open
1654
+ * it; saved, the agent reads it with its file tools, in parts.
1655
+ */
1656
+ const TEXT_ATTACHMENT_SAVE_MAX_BYTES = 10 * 1024 * 1024;
1657
+ /** The default bound on one agent's saved attachments, all together. */
1658
+ const SAVED_ATTACHMENTS_MAX_BYTES = 256 * 1024 * 1024;
1659
+ /**
1660
+ * The most saved attachments one agent keeps, whatever they weigh. A file
1661
+ * crowded out of a full message is saved however small it is, and each save
1662
+ * is a ledger record, so the count is bounded as well as the bytes.
1663
+ */
1664
+ const SAVED_ATTACHMENTS_MAX_FILES = 1_000;
1665
+ /** Under the agent's workspace, where saved attachments go. */
1666
+ const SAVED_ATTACHMENTS_DIR = path.join('attachments', 'slack');
1667
+ /**
1668
+ * A file name safe to join under a directory: Slack's name is whatever the
1669
+ * uploader's client sent, so separators, `..`, and control characters are
1670
+ * replaced rather than trusted. Stamped, so the same file shared twice is
1671
+ * two saves rather than a name already taken.
1672
+ */
1673
+ const savedAttachmentName = (file, now) => {
1674
+ const base = ((file.name ?? file.title ?? '').split(/[\\/]/).pop() ?? '').replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^[._]+/, '').slice(0, 120);
1675
+ const id = (file.id ?? '').replace(/[^A-Za-z0-9]+/g, '').slice(0, 32);
1676
+ return [now.toString(36), ...(id.length > 0 ? [id] : []), base.length > 0 ? base : 'attachment'].join('-');
1677
+ };
1678
+ /**
1679
+ * Refuses a save that would take the directory's regular files past
1680
+ * `maxBytes` together, or its entries past `SAVED_ATTACHMENTS_MAX_FILES`. Refused rather than making room: deleting on a
1681
+ * Slack member's say is a worse failure than a full directory, and with
1682
+ * nothing removed every ledger record still names a file that exists.
1683
+ */
1684
+ const ensureRoom = async (dir, incoming, maxBytes) => {
1685
+ let total = 0;
1686
+ const names = await readdir(dir);
1687
+ for (const name of names) {
1688
+ const info = await lstat(path.join(dir, name)).catch(() => undefined);
1689
+ total += info?.isFile() ? info.size : 0;
1690
+ }
1691
+ if (names.length >= SAVED_ATTACHMENTS_MAX_FILES) {
1692
+ throw new Error(`${dir} already holds ${SAVED_ATTACHMENTS_MAX_FILES} saved attachments; remove files there to save more`);
1693
+ }
1694
+ if (total + incoming > maxBytes) {
1695
+ throw new Error(`saved attachments in ${dir} would pass their ${maxBytes}-byte bound; remove files there to save more`);
1696
+ }
1697
+ };
1698
+ /**
1699
+ * One save at a time per directory, across messages and sessions: two
1700
+ * concurrent saves would each measure the same free space and both write.
1701
+ */
1702
+ const saveChains = new Map();
1703
+ const serialized = (key, run) => {
1704
+ const previous = saveChains.get(key) ?? Promise.resolve();
1705
+ const next = previous.catch(() => undefined).then(run);
1706
+ const settled = next.catch(() => undefined);
1707
+ saveChains.set(key, settled);
1708
+ void settled.then(() => {
1709
+ if (saveChains.get(key) === settled) {
1710
+ saveChains.delete(key);
1711
+ }
1712
+ });
1713
+ return next;
1714
+ };
1715
+ /**
1716
+ * The saver for one agent and one sender. The directory is canonical
1717
+ * because the ledger is keyed the way `fs.read` looks a path up, through
1718
+ * `realpath`. The path is recorded before the bytes land, so a crash
1719
+ * between the two leaves a labelled path with no file, never a file with
1720
+ * no label; and the file is created exclusively, so a link planted at the
1721
+ * name fails the save rather than carrying the bytes somewhere the ledger
1722
+ * never saw.
1723
+ */
1724
+ const createAttachmentSaver = (store, agentId, trust, now) => async (file, body) => {
1725
+ const workspace = store.workspaces.prepare(agentId);
1726
+ const lexical = path.join(workspace, SAVED_ATTACHMENTS_DIR);
1727
+ // One component at a time, never through a link: a recursive mkdir would
1728
+ // follow a link planted at `attachments` and create `slack` wherever it
1729
+ // points before the check below could refuse it.
1730
+ await mkdir(workspace, { recursive: true, mode: 0o700 });
1731
+ let at = workspace;
1732
+ for (const part of SAVED_ATTACHMENTS_DIR.split(path.sep)) {
1733
+ at = path.join(at, part);
1734
+ await mkdir(at, { mode: 0o700 }).catch((error) => {
1735
+ if (error.code !== 'EEXIST') {
1736
+ throw error;
1737
+ }
1738
+ });
1739
+ const info = await lstat(at);
1740
+ if (!info.isDirectory()) {
1741
+ throw new Error(`${at} is not a directory of the workspace's own`);
1742
+ }
1743
+ }
1744
+ // The directory must be exactly where the workspace says, through no link
1745
+ // of its own: one already planted at `attachments` or `attachments/slack`
1746
+ // would put the file outside the workspace, where the agent's file tools
1747
+ // cannot read it and nothing should be written on a Slack member's say.
1748
+ const dir = await realpath(lexical);
1749
+ if (dir !== path.join(await realpath(workspace), SAVED_ATTACHMENTS_DIR)) {
1750
+ throw new Error(`${lexical} is a link, not the workspace's own directory`);
1751
+ }
1752
+ return serialized(dir, async () => {
1753
+ await ensureRoom(dir, body.length, store.maxBytes ?? SAVED_ATTACHMENTS_MAX_BYTES);
1754
+ const target = path.join(dir, savedAttachmentName(file, now()));
1755
+ await store.ledger.recordWrite(agentId, target, trust);
1756
+ // O_EXCL and O_NOFOLLOW guard only the last component. A directory on the
1757
+ // way swapped for a link after the record would send the create somewhere
1758
+ // else, so the open file is checked against the recorded name before a
1759
+ // byte is written (the same check as the plugins package's
1760
+ // `nameIdentifiesHandle`). A refused save leaves the empty file rather
1761
+ // than unlinking through a name that just proved it can move.
1762
+ const handle = await open(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
1763
+ try {
1764
+ const [canonical, atName, held] = await Promise.all([
1765
+ realpath(target).catch(() => undefined),
1766
+ lstat(target).catch(() => undefined),
1767
+ handle.stat(),
1768
+ ]);
1769
+ if (canonical !== target || atName === undefined || atName.dev !== held.dev || atName.ino !== held.ino) {
1770
+ throw new Error(`${target} moved between its provenance record and its write`);
1771
+ }
1772
+ await handle.writeFile(body);
1773
+ }
1774
+ finally {
1775
+ await handle.close();
1776
+ }
1777
+ return target;
1778
+ });
1779
+ };
1634
1780
  const TEXT_APPLICATION_TYPES = new Set([
1635
1781
  'application/json',
1636
1782
  'application/xml',
@@ -1668,8 +1814,9 @@ const isTextAttachment = (file) => {
1668
1814
  * caps, or comes back as a page stays in `unread` and is named in the
1669
1815
  * note, exactly as before; the warning says why.
1670
1816
  */
1671
- const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadline, warn, reasons) => {
1817
+ const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadline, warn, reasons, save) => {
1672
1818
  const texts = [];
1819
+ const saved = [];
1673
1820
  const unread = [];
1674
1821
  const skip = (file, reason) => {
1675
1822
  unread.push(file);
@@ -1691,8 +1838,13 @@ const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadli
1691
1838
  continue;
1692
1839
  }
1693
1840
  const maxBytes = Math.min(TEXT_ATTACHMENT_MAX_BYTES, TEXT_ATTACHMENTS_MAX_TOTAL_BYTES - total);
1694
- if (file.size !== undefined && file.size > maxBytes) {
1695
- warn(`slack: ${fileLabel(file)} is ${file.size} bytes, over the ${maxBytes} bytes of text this message could still take; the turn is told it cannot be read. Paste the part that matters, or split the file.`);
1841
+ // Over the inline cap, a file is still downloaded when it can be saved
1842
+ // to the workspace instead; only over that cap too is it a name.
1843
+ const limit = save === undefined ? maxBytes : Math.max(maxBytes, TEXT_ATTACHMENT_SAVE_MAX_BYTES);
1844
+ if (file.size !== undefined && file.size > limit) {
1845
+ warn(save === undefined
1846
+ ? `slack: ${fileLabel(file)} is ${file.size} bytes, over the ${maxBytes} bytes of text this message could still take; the turn is told it cannot be read. Paste the part that matters, or split the file.`
1847
+ : `slack: ${fileLabel(file)} is ${file.size} bytes, over the ${limit} bytes a text attachment may be saved at; the turn is told it cannot be read. Paste the part that matters, or split the file.`);
1696
1848
  skip(file, 'too-large');
1697
1849
  continue;
1698
1850
  }
@@ -1703,7 +1855,7 @@ const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadli
1703
1855
  }
1704
1856
  let download;
1705
1857
  try {
1706
- download = await fetchFile(url, botToken, deadline, maxBytes);
1858
+ download = await fetchFile(url, botToken, deadline, limit);
1707
1859
  }
1708
1860
  catch (error) {
1709
1861
  const reason = error instanceof Error && error.name === 'TimeoutError'
@@ -1722,8 +1874,8 @@ const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadli
1722
1874
  skip(file, 'failed');
1723
1875
  continue;
1724
1876
  }
1725
- if (download.truncated === true || download.body.length > maxBytes) {
1726
- warn(`slack: ${fileLabel(file)} is larger than the ${maxBytes} bytes of text this message could still take; the download was abandoned and the turn is told it cannot be read.`);
1877
+ if (download.truncated === true || download.body.length > limit) {
1878
+ warn(`slack: ${fileLabel(file)} is larger than the ${limit} bytes ${save === undefined ? 'of text this message could still take' : 'a text attachment may be saved at'}; the download was abandoned and the turn is told it cannot be read.`);
1727
1879
  skip(file, 'too-large');
1728
1880
  continue;
1729
1881
  }
@@ -1736,11 +1888,24 @@ const readTextAttachments = async (files, botToken, fetchFile, timeoutMs, deadli
1736
1888
  skip(file, 'unreadable');
1737
1889
  continue;
1738
1890
  }
1891
+ if (download.body.length > maxBytes) {
1892
+ // Only reachable with a saver: `limit` is `maxBytes` without one.
1893
+ try {
1894
+ saved.push({ name: fileLabel(file), path: await save(file, download.body), bytes: download.body.length });
1895
+ }
1896
+ catch (error) {
1897
+ warn(`slack: could not save ${fileLabel(file)} to the agent's workspace: ${error instanceof Error ? error.message : String(error)}. The turn is told it cannot be read.`);
1898
+ skip(file, 'too-large');
1899
+ }
1900
+ continue;
1901
+ }
1739
1902
  total += download.body.length;
1740
1903
  texts.push({ name: fileLabel(file), text });
1741
1904
  }
1742
- return { texts, unread };
1905
+ return { texts, saved, unread };
1743
1906
  };
1907
+ /** Saved attachments, named with where each is, after what was said. */
1908
+ const savedAttachmentBlocks = (saved) => saved.map(({ name, path: at, bytes }) => `\n[Attached: ${name}, ${Math.ceil(bytes / 1024)} KB, too large to include here. It is saved in full at ${at}. Read it with your file tools, in parts if it is long.]`).join('');
1744
1909
  /** Read text attachments, set into the message after what was said. */
1745
1910
  const textAttachmentBlocks = (texts) => texts.map(({ name, text }) => `\n[Attached: ${name}. Its contents follow.]\n${text.replace(/\s+$/, '')}\n[End of ${name}]`).join('');
1746
1911
  /**
@@ -2065,6 +2230,12 @@ export const createSlackChannelAdapter = (options) => {
2065
2230
  const botIdentities = new Map();
2066
2231
  // Rendered approval requests, keyed by the gateway's request id — the
2067
2232
  // same id the buttons carry back.
2233
+ //
2234
+ // In memory only, and that is an open gap: a prompt posted by a process
2235
+ // that died is never retracted unless someone clicks it (a click on a
2236
+ // prompt this process never posted rewrites it from the click's own
2237
+ // coordinates). Retracting the ones nobody clicks needs a durable index
2238
+ // of these posts that survives the process that made them.
2068
2239
  const approvalPosts = new Map();
2069
2240
  // Credential requests whose post is in flight: Slack can show the message,
2070
2241
  // and take a click on it, before postMessage resolves here. The same
@@ -2953,7 +3124,16 @@ export const createSlackChannelAdapter = (options) => {
2953
3124
  if (!gateway) {
2954
3125
  return;
2955
3126
  }
2956
- const connection = connectionFor(event.agentId);
3127
+ // A delegated call is asked where the work was asked for: the
3128
+ // delegating conversation, through the app and approvers of the agent
3129
+ // at the root of the chain. The agent doing the work may have no
3130
+ // connection here at all, and its sub-session has no conversation of
3131
+ // its own — asked the old way, nobody saw it and the turn sat out the
3132
+ // whole timeout. The gateway makes such a call one-shot, so routing it
3133
+ // through another agent's approvers never grants anything standing.
3134
+ const origin = event.delegatedFrom;
3135
+ const viaOrigin = origin ? connectionFor(origin.agentId) : undefined;
3136
+ const connection = viaOrigin ?? connectionFor(event.agentId);
2957
3137
  if (!connection) {
2958
3138
  // Two different situations, and only one of them is this adapter's
2959
3139
  // to answer.
@@ -2968,14 +3148,14 @@ export const createSlackChannelAdapter = (options) => {
2968
3148
  // agent — denying here would let Slack refuse a question somebody
2969
3149
  // else was going to ask. `stratus serve` reports agents that no
2970
3150
  // channel can ask for, at startup, where the whole roster is visible.
2971
- if (configuredAgents.has(event.agentId)) {
2972
- warn(`slack: ${event.agentId} has no live connection; denying ${event.call.toolName}`);
3151
+ if (configuredAgents.has(event.agentId) || (origin && configuredAgents.has(origin.agentId))) {
3152
+ warn(`slack: ${origin?.agentId ?? event.agentId} has no live connection; denying ${event.call.toolName}`);
2973
3153
  gateway.resolveApproval({ requestId: event.requestId, answer: 'deny', reason: 'undeliverable' });
2974
3154
  }
2975
3155
  return;
2976
3156
  }
2977
3157
  const approvers = new Set((connection.config.approvers ?? []).filter((id) => id.length > 0));
2978
- const metadata = event.metadata ?? {};
3158
+ const metadata = (viaOrigin ? origin?.metadata : event.metadata) ?? {};
2979
3159
  const sameChannel = metadata.channel === 'slack' && typeof metadata.slackChannel === 'string'
2980
3160
  ? metadata.slackChannel
2981
3161
  : undefined;
@@ -2989,14 +3169,19 @@ export const createSlackChannelAdapter = (options) => {
2989
3169
  gateway.resolveApproval({ requestId: event.requestId, answer: 'deny', reason: 'undeliverable' });
2990
3170
  };
2991
3171
  if (approvers.size === 0) {
2992
- decline(`no approvers configured for ${event.agentId}`);
3172
+ decline(`no approvers configured for ${connection.config.agentId}`);
2993
3173
  return;
2994
3174
  }
2995
3175
  if (!channel) {
2996
- decline(`no conversation to ask in for ${event.agentId}`);
3176
+ decline(`no conversation to ask in for ${connection.config.agentId}`);
2997
3177
  return;
2998
3178
  }
2999
- const agentName = gateway.agents().find((agent) => agent.id === event.agentId)?.name ?? event.agentId;
3179
+ const nameOf = (agentId) => gateway.agents().find((agent) => agent.id === agentId)?.name ?? agentId;
3180
+ // "Quinn (for Nova)": the approver is being asked about Quinn's call,
3181
+ // by Nova's app, in Nova's conversation, and should be told both.
3182
+ const agentName = viaOrigin && origin && origin.agentId !== event.agentId
3183
+ ? `${nameOf(event.agentId)} (for ${nameOf(origin.agentId)})`
3184
+ : nameOf(event.agentId);
3000
3185
  // The notification preview is all some approvers see before deciding
3001
3186
  // whether to open the thread, so it carries the arguments too — just
3002
3187
  // the short form.
@@ -3277,7 +3462,8 @@ export const createSlackChannelAdapter = (options) => {
3277
3462
  // "Said nothing" as the session can tell it: no reply saved for the
3278
3463
  // turn. A line the old process had streamed into a placeholder and
3279
3464
  // never saved is invisible here — the session records what was
3280
- // said, not what Slack took (roadmap 31's open item) — so such a
3465
+ // said, not what Slack took (the open delivery-record item at the
3466
+ // gateway's lastSpokeAt) — so such a
3281
3467
  // turn is failed without a note under that line. Taken over the
3282
3468
  // alternative on purpose: a note for every judged turn a restart
3283
3469
  // caught turns a silent turn, the common case, into the
@@ -3345,9 +3531,13 @@ export const createSlackChannelAdapter = (options) => {
3345
3531
  return { said: routing.unaddressed === true, landed };
3346
3532
  }
3347
3533
  // A reply the thread's other agents hear like any other, taking their
3348
- // place for it before the post, as `handleInbound` does.
3534
+ // place for it before the post, as `handleInbound` does. A DM thread
3535
+ // has nobody else in it to hear, as on the live path.
3349
3536
  const published = postAheadOf(behind, connection, channel, thread, posting, uploads);
3350
- const heard = routing.reply !== undefined && thread !== undefined
3537
+ const conversation = metadata[CONVERSATION_METADATA_KEY];
3538
+ const isDm = typeof conversation === 'object' && conversation !== null && !Array.isArray(conversation)
3539
+ && conversation.kind === 'direct';
3540
+ const heard = routing.reply !== undefined && thread !== undefined && !isDm
3351
3541
  ? overhearReply(connection, channel, thread, routing.reply, routing, published)
3352
3542
  : undefined;
3353
3543
  const delivered = await published;
@@ -4046,9 +4236,10 @@ export const createSlackChannelAdapter = (options) => {
4046
4236
  const isDm = event.channel_type === 'im';
4047
4237
  const team = args.body?.team_id ?? connection.teamId;
4048
4238
  // A top-level mention has no thread_ts; its own ts roots the reply
4049
- // thread that becomes the conversation. DMs are one conversation per
4050
- // peer, unkeyed by thread.
4051
- const thread = isDm ? undefined : event.thread_ts ?? event.ts;
4239
+ // thread that becomes the conversation. A DM's top level is one
4240
+ // conversation per peer, unkeyed by thread; a thread inside the DM is a
4241
+ // conversation of its own, answered in that thread.
4242
+ const thread = isDm ? event.thread_ts : event.thread_ts ?? event.ts;
4052
4243
  // Threads only: a DM has one agent by construction.
4053
4244
  const threadKey = thread !== undefined && !isDm
4054
4245
  ? threadKeyFor({ team, conversation: event.channel, thread })
@@ -4371,20 +4562,22 @@ export const createSlackChannelAdapter = (options) => {
4371
4562
  const { images, unread: notImages } = overhear || judged
4372
4563
  ? { images: [], unread: event.files ?? [] }
4373
4564
  : await readImageAttachments(event.files ?? [], connection.config.botToken, fetchFile, fileDownloadTimeoutMs, warn, reasons, deadline);
4374
- const { texts, unread } = overhear || judged
4375
- ? { texts: [], unread: notImages }
4376
- : await readTextAttachments(notImages, connection.config.botToken, fetchFile, fileDownloadTimeoutMs, deadline, warn, reasons);
4565
+ const { texts, saved, unread } = overhear || judged
4566
+ ? { texts: [], saved: [], unread: notImages }
4567
+ : await readTextAttachments(notImages, connection.config.botToken, fetchFile, fileDownloadTimeoutMs, deadline, warn, reasons, options.attachmentStore
4568
+ ? createAttachmentSaver(options.attachmentStore, connection.config.agentId, senderTrustFor(connection.config, userId), options.now ?? Date.now)
4569
+ : undefined);
4377
4570
  // An image or a document with nothing said is still a question
4378
4571
  // ("what's this?"); a file the turn could not open with nothing said
4379
4572
  // is not one, and gets no reply.
4380
- if (cleaned.length === 0 && images.length === 0 && texts.length === 0) {
4573
+ if (cleaned.length === 0 && images.length === 0 && texts.length === 0 && saved.length === 0) {
4381
4574
  return undefined;
4382
4575
  }
4383
4576
  const author = await authorFor(connection, userId);
4384
4577
  // In shared channels the model should know who is speaking; a DM is
4385
4578
  // unambiguous. A bare image in a channel still says who sent it.
4386
4579
  const spoken = isDm ? cleaned : (cleaned.length > 0 ? `${author}: ${cleaned}` : `${author}:`);
4387
- const userMessage = `${spoken}${textAttachmentBlocks(texts)}${attachmentNote(unread, reasons)}`;
4580
+ const userMessage = `${spoken}${textAttachmentBlocks(texts)}${savedAttachmentBlocks(saved)}${attachmentNote(unread, reasons)}`;
4388
4581
  // Who sent this, judged against the operator's list — per message,
4389
4582
  // never remembered from the first one in the thread. A DM proves
4390
4583
  // nothing about who is typing, so a DM from an unlisted member is
@@ -4403,7 +4596,9 @@ export const createSlackChannelAdapter = (options) => {
4403
4596
  : {
4404
4597
  ...room,
4405
4598
  ...(room.kind === 'direct' && senderTrust === 'user' ? { with: author } : {}),
4406
- ...(thread !== undefined && room.kind !== 'direct' ? { thread: true } : {}),
4599
+ // A DM thread is a thread too, with its own session; core takes
4600
+ // no root for a direct room, since nothing posts to one by id.
4601
+ ...(thread !== undefined ? { thread: true, ...(room.kind !== 'direct' ? { threadRoot: thread } : {}) } : {}),
4407
4602
  // People outside this workspace read a Slack Connect channel, and
4408
4603
  // the event says so itself, even when the lookup failed.
4409
4604
  ...(room.kind !== 'direct' && args.body?.is_ext_shared_channel === true ? { shared: true } : {}),
@@ -4449,7 +4644,7 @@ export const createSlackChannelAdapter = (options) => {
4449
4644
  // said before this. Inside the chain, before the placeholder, so the
4450
4645
  // read cannot reorder this message against the next one.
4451
4646
  let earlier = [];
4452
- if (!isDm && thread !== undefined) {
4647
+ if (thread !== undefined) {
4453
4648
  // A top-level mention roots its own session, so it is new but for
4454
4649
  // a redelivery, which the gateway answers without reading this.
4455
4650
  let exists = false;
@@ -4483,7 +4678,8 @@ export const createSlackChannelAdapter = (options) => {
4483
4678
  // while the tool it preceded is still running. Under the session's
4484
4679
  // label as stored, lowered by anything that tainted it during this
4485
4680
  // turn, and the least trusted label where it cannot be read.
4486
- ...(thread !== undefined
4681
+ // A DM thread has nobody else in it to hear.
4682
+ ...(thread !== undefined && !isDm
4487
4683
  ? {
4488
4684
  onInterim: (heard, landed) => {
4489
4685
  // Tracked, so a shutdown drains it like any other reaction:
@@ -4664,7 +4860,7 @@ export const createSlackChannelAdapter = (options) => {
4664
4860
  replyLanded = outcome.published;
4665
4861
  return true;
4666
4862
  }, () => true);
4667
- const heard = thread !== undefined
4863
+ const heard = thread !== undefined && !isDm
4668
4864
  ? overhearReply(connection, event.channel, thread, () => renderer.spokenText(replyLanded ? reply ?? '' : ''), session, settled)
4669
4865
  : undefined;
4670
4866
  const { spoke, spokeAt } = await finalized;
@@ -4689,7 +4885,14 @@ export const createSlackChannelAdapter = (options) => {
4689
4885
  await heard;
4690
4886
  }
4691
4887
  else {
4692
- const { spoke, spokeAt } = await renderer.fail(failure instanceof Error ? failure.message : String(failure));
4888
+ // A paused daemon's refusal is its answer, said as it is: the
4889
+ // operator wrote that sentence for the person reading it, and it is
4890
+ // not a failure of the agent. Matched by name, as this package does
4891
+ // not import the gateway. A turn nobody asked for stays silent.
4892
+ const pausedReply = failure instanceof Error && failure.name === 'IntakePausedError';
4893
+ const { spoke, spokeAt } = pausedReply
4894
+ ? await renderer.finalize(renderer.lazy ? '' : failure.message)
4895
+ : await renderer.fail(failure instanceof Error ? failure.message : String(failure));
4693
4896
  if (spoke && threadKey !== undefined) {
4694
4897
  // A file it posted before breaking is still the last thing said.
4695
4898
  rememberAddressee(threadKey, connection.config.agentId, spokeAt ?? event.ts);
@@ -4831,7 +5034,8 @@ export const createSlackChannelAdapter = (options) => {
4831
5034
  continue;
4832
5035
  }
4833
5036
  try {
4834
- const web = createWeb(config.botToken);
5037
+ const created = createWeb(config.botToken);
5038
+ const web = config.linkPreviews === false ? withoutLinkPreviews(created) : created;
4835
5039
  const auth = await web.auth.test();
4836
5040
  const botUserId = auth.user_id ?? '';
4837
5041
  const teamId = auth.team_id ?? '';