@stratusagent/channel-slack 0.10.0 → 0.11.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 +85 -3
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1353 -109
- package/dist/index.js.map +1 -1
- package/manifest/stratus-agent.manifest.json +7 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
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.
|
|
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
|
-
- **
|
|
8
|
-
- **
|
|
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.
|
|
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
|
+
- **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.
|
|
9
10
|
- **Tokens are gateway infrastructure secrets**, stored in the `channels` namespace of `~/.stratus/credentials.json` — never in an agent's credential allowlist:
|
|
10
11
|
|
|
11
12
|
```json
|
|
@@ -18,6 +19,67 @@ Slack adapter for Stratus agents. **One Slack app per agent** — Slack has no w
|
|
|
18
19
|
}
|
|
19
20
|
```
|
|
20
21
|
|
|
22
|
+
## Who a message is for
|
|
23
|
+
|
|
24
|
+
A mention is how a conversation starts. After that, the thread is the
|
|
25
|
+
address — replying in it reaches the agent the way replying to a colleague
|
|
26
|
+
reaches them, with no `@` and no ceremony. Four rules decide the rest, and
|
|
27
|
+
they are the whole model:
|
|
28
|
+
|
|
29
|
+
1. **A mention is always for whoever is named.** In a thread or out of one,
|
|
30
|
+
naming an agent hands it the question.
|
|
31
|
+
2. **Inside a thread it is already in, an agent listens without being
|
|
32
|
+
named.** "Already in" means a session exists under that thread's key —
|
|
33
|
+
which happens only because somebody mentioned it there — so an agent
|
|
34
|
+
follows conversations it was brought into and nothing else. That record
|
|
35
|
+
is durable, so a daemon restart mid-thread forgets nothing.
|
|
36
|
+
3. **The room is not a conversation.** A channel message outside any thread
|
|
37
|
+
is never a follow-up, however much of the channel the app can see. An
|
|
38
|
+
agent that has not been spoken to does not join in.
|
|
39
|
+
4. **In a thread with several agents, an untagged reply goes to whoever
|
|
40
|
+
spoke last.** The same rule people use: you are answering the voice that
|
|
41
|
+
just answered you. Naming another agent moves the conversation to them —
|
|
42
|
+
so a handover is one `@`, and it takes effect the moment it is sent, not
|
|
43
|
+
whenever the turn it starts gets around to answering: an addendum typed
|
|
44
|
+
straight after it reaches the agent you just named, and the agent that
|
|
45
|
+
*had* the thread stands down at the same instant rather than whenever its
|
|
46
|
+
own app next catches up.
|
|
47
|
+
|
|
48
|
+
Everyone in the thread is talking to the same agent — a reply from a second
|
|
49
|
+
person is a follow-up like any other, and channel messages reach the model
|
|
50
|
+
prefixed with the speaker's display name so it knows who said what. A reply
|
|
51
|
+
carrying a file, or one the author also broadcast to the channel, is a
|
|
52
|
+
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.
|
|
61
|
+
|
|
62
|
+
Three edges worth knowing. An agent whose app was installed before the
|
|
63
|
+
history scopes below is told about mentions only, and behaves exactly as it
|
|
64
|
+
always did — the workspace's grant is the switch, per app. Where a thread's
|
|
65
|
+
several agents cannot be ordered — a host whose session routing carries no
|
|
66
|
+
timestamps — an untagged reply is left alone rather than answered twice;
|
|
67
|
+
mention the one you want.
|
|
68
|
+
|
|
69
|
+
A named agent whose app is down answers nothing, and nothing answers in its
|
|
70
|
+
place: being tagged is a decision about who is being asked, and an agent
|
|
71
|
+
that has been handed the question elsewhere does not take it back because
|
|
72
|
+
the other one is offline. That silence is the same signal a mention has
|
|
73
|
+
always given when an app is down.
|
|
74
|
+
|
|
75
|
+
And the rules above are mechanical, which shows in a thread where people are
|
|
76
|
+
mostly talking to *each other*: an agent invited into one answers every
|
|
77
|
+
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).
|
|
82
|
+
|
|
21
83
|
## Installing
|
|
22
84
|
|
|
23
85
|
This package is an **optional peer** of the CLI — `stratus` ships without any
|
|
@@ -42,6 +104,7 @@ editing `credentials.json` by hand.
|
|
|
42
104
|
The manual equivalent, if you prefer:
|
|
43
105
|
|
|
44
106
|
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.
|
|
45
108
|
2. **Basic Information → App-Level Tokens** → generate a token with `connections:write` (that's the `appToken`, `xapp-…`).
|
|
46
109
|
3. **Install App** to the workspace → copy the **Bot User OAuth Token** (that's the `botToken`, `xoxb-…`).
|
|
47
110
|
4. Upload the agent's avatar under **Display Information**.
|
|
@@ -54,6 +117,25 @@ parks the turn and the adapter asks here. The question goes to the thread the
|
|
|
54
117
|
turn is happening in; a turn with no Slack conversation of its own (scheduled
|
|
55
118
|
work, a delegate) asks in the configured `slackChannel`.
|
|
56
119
|
|
|
120
|
+
The request names the **site** for a call judged by one (`browser.act`),
|
|
121
|
+
because its arguments are a CSS selector and say nothing about where a click
|
|
122
|
+
lands — and that site is what **Always allow** widens.
|
|
123
|
+
|
|
124
|
+
**Always allow** is left off entirely where the daemon would remember
|
|
125
|
+
nothing: a `dangerous` tool, which asks every time whatever is answered; a
|
|
126
|
+
browser action whose conversation has no page to grant; and a shell command
|
|
127
|
+
the parser cannot reduce to a scope. The prompt says so instead, because a
|
|
128
|
+
button that does exactly what **Allow once** does, under a label promising a
|
|
129
|
+
standing grant, is worse than no button.
|
|
130
|
+
|
|
131
|
+
The resolved message describes what the daemon did rather than which button
|
|
132
|
+
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.
|
|
137
|
+
`stratus logs` has the exact line.
|
|
138
|
+
|
|
57
139
|
Who may answer is configured per agent, in `~/.stratus/config.json`:
|
|
58
140
|
|
|
59
141
|
```jsonc
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,16 @@ export interface SlackInboundEvent {
|
|
|
67
67
|
channel_type?: string;
|
|
68
68
|
bot_id?: string;
|
|
69
69
|
subtype?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Attachments on a `file_share` message. Slack puts the file metadata in
|
|
72
|
+
* the event and the bytes behind an authenticated URL that needs
|
|
73
|
+
* `files:read`, which this app does not ask for — so a turn learns that a
|
|
74
|
+
* file arrived and what it is called, never what is in it.
|
|
75
|
+
*/
|
|
76
|
+
files?: Array<{
|
|
77
|
+
name?: string;
|
|
78
|
+
title?: string;
|
|
79
|
+
}>;
|
|
70
80
|
}
|
|
71
81
|
export interface SlackSocketLike {
|
|
72
82
|
on(eventName: string, listener: (args: SlackSocketEventArgs) => void): void;
|
|
@@ -168,6 +178,14 @@ export interface SlackAdapterOptions {
|
|
|
168
178
|
* (DMs: the DM channel id alone), so threads are resumable conversations
|
|
169
179
|
* and two agents sharing a thread keep fully separate sessions. Replies
|
|
170
180
|
* stream via placeholder-then-edit, throttled for chat.update limits.
|
|
181
|
+
*
|
|
182
|
+
* A mention starts a thread conversation; inside one, the agent keeps
|
|
183
|
+
* listening without being tagged again — see `couldBeFollowUp` and
|
|
184
|
+
* `answersFollowUp` for exactly whose reply an untagged message is. That
|
|
185
|
+
* needs the app to be subscribed to `message.channels` / `message.groups`
|
|
186
|
+
* / `message.mpim` (the shipped manifest is); an app installed before
|
|
187
|
+
* those were in it receives only `app_mention` and behaves as it always
|
|
188
|
+
* did, which makes the workspace's own grant the switch.
|
|
171
189
|
*/
|
|
172
190
|
export declare const createSlackChannelAdapter: (options: SlackAdapterOptions) => ChannelAdapter;
|
|
173
191
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|