@stratusagent/channel-slack 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@ Slack adapter for Stratus agents. **One Slack app per agent** — Slack has no w
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.
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
7
  - **Mention-only in channels, free-form in DMs.** Inbound mentions of other users are humanized to `@Display Name` for the model; redeliveries are deduped so a slow turn never runs twice.
8
+ - **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.
8
9
  - **Tokens are gateway infrastructure secrets**, stored in the `channels` namespace of `~/.stratus/credentials.json` — never in an agent's credential allowlist:
9
10
 
10
11
  ```json
@@ -45,3 +46,68 @@ The manual equivalent, if you prefer:
45
46
  3. **Install App** to the workspace → copy the **Bot User OAuth Token** (that's the `botToken`, `xoxb-…`).
46
47
  4. Upload the agent's avatar under **Display Information**.
47
48
  5. Add both tokens under `channels.slack.<agentId>` in `~/.stratus/credentials.json` and restart `stratus serve` — the log will show `slack: <agentId> connected`.
49
+
50
+ ## Approving tool calls
51
+
52
+ When `stratus serve` runs with `approvals.mode: "remote"`, a gated tool call
53
+ parks the turn and the adapter asks here. The question goes to the thread the
54
+ turn is happening in; a turn with no Slack conversation of its own (scheduled
55
+ work, a delegate) asks in the configured `slackChannel`.
56
+
57
+ Who may answer is configured per agent, in `~/.stratus/config.json`:
58
+
59
+ ```jsonc
60
+ {
61
+ "approvals": {
62
+ "mode": "remote",
63
+ "slackChannel": "C07OPS",
64
+ "agents": {
65
+ "ava": { "slackApprovers": ["U01DYLAN", "U01OPS"] }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ That block is only read from a config you chose — `--config`,
72
+ `STRATUS_CONFIG`, or the global `~/.stratus/config.json`. A project-local
73
+ `stratus.config.json` cannot appoint approvers, since it can be checked into
74
+ any repository.
75
+
76
+ **Clicks are authorized by actor, not by delivery.** Everyone in a thread can
77
+ see the request; only the ids listed for that agent can decide it. Anyone
78
+ else's click is refused with a notice only they see, and the request stays
79
+ open for someone who may actually answer — which matters most for **Always
80
+ allow**, since that widens what the agent may do for the rest of the session.
81
+ An agent with no approvers listed denies every request on arrival rather than
82
+ leaving it hanging — set `"slackApprovers": []` on an agent to exclude it
83
+ from a shared default list. The same goes for an agent whose Slack app is
84
+ configured here but failed to connect. An agent this adapter was never given
85
+ is left alone: a request is a broadcast, and refusing one another channel was
86
+ about to answer is not the adapter's call — `stratus serve` reports agents no
87
+ channel can ask for, at startup. Those automatic denials are recorded as
88
+ `undeliverable` rather than `decided`, so the log never shows a refusal
89
+ nobody made as one somebody did.
90
+
91
+ The message shows the tool's arguments as well as its name — approving
92
+ `shell.run` without seeing what it would run is not approval. Arguments are
93
+ escaped, so a model-written argument cannot mention or broadcast to the
94
+ workspace through the prompt itself, and long ones are truncated with a
95
+ notice saying so.
96
+
97
+ Requests are also denied, visibly, when they expire, when the turn is
98
+ cancelled, and when the daemon shuts down. Every ending the daemon is alive
99
+ for retracts the buttons, so a message does not keep offering a decision that
100
+ has nowhere to land.
101
+
102
+ A crash is the ending it cannot be alive for, and the record of what was
103
+ posted lives only in memory — so a new process cannot find its predecessor's
104
+ prompts to retract them. Those correct themselves on the next click: it
105
+ answers that the request is no longer pending and rewrites the message, since
106
+ the click is the one thing that carries the message's location back. A prompt
107
+ nobody clicks stays as it was until someone does.
108
+
109
+ **Interactivity must be enabled on the app** or Slack delivers no clicks at
110
+ all. The shipped manifest turns it on; an app created before this shipped
111
+ needs `settings.interactivity.is_enabled` set to `true` once, under **App
112
+ Manifest**. No request URL is needed — the clicks arrive over the same Socket
113
+ Mode connection.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,22 @@ export interface SlackAgentConfig {
4
4
  agentId: string;
5
5
  appToken: string;
6
6
  botToken: string;
7
+ /**
8
+ * Slack user ids allowed to answer this agent's approval requests.
9
+ * Authorization is by ACTOR, never by delivery: a request posted into a
10
+ * shared thread is visible to everyone there, and one of them clicking
11
+ * **Always allow** would otherwise widen what the agent may do unattended
12
+ * for the rest of the session. Empty (or absent) means nobody can
13
+ * approve, so requests are declined on arrival rather than left hanging.
14
+ */
15
+ approvers?: string[];
16
+ /**
17
+ * Where to ask when the turn is not itself happening in Slack — a
18
+ * schedule-driven turn, a delegate, an HTTP dispatch. Defaults to the
19
+ * conversation the turn came from, which is where the person waiting on
20
+ * the answer already is.
21
+ */
22
+ approvalChannel?: string;
7
23
  }
8
24
  export interface SlackSocketEventArgs {
9
25
  ack: (response?: unknown) => Promise<void>;
@@ -11,10 +27,36 @@ export interface SlackSocketEventArgs {
11
27
  body?: {
12
28
  team_id?: string;
13
29
  event_id?: string;
30
+ /** block_actions only: who clicked. The one field authorization reads. */
31
+ user?: {
32
+ id?: string;
33
+ };
34
+ actions?: SlackBlockAction[];
35
+ channel?: {
36
+ id?: string;
37
+ };
38
+ /**
39
+ * block_actions only: the message the click came from, as Slack holds
40
+ * it at the moment it processes the interaction — so `blocks` is the
41
+ * message's real current state, not what the clicking client had
42
+ * rendered.
43
+ */
44
+ message?: {
45
+ ts?: string;
46
+ thread_ts?: string;
47
+ blocks?: SlackBlock[];
48
+ };
14
49
  };
15
50
  event?: SlackInboundEvent;
16
51
  retry_num?: number;
17
52
  }
53
+ export interface SlackBlockAction {
54
+ action_id?: string;
55
+ /** The request id the button carries. */
56
+ value?: string;
57
+ }
58
+ /** Block Kit is structural, not typed here beyond what the adapter emits. */
59
+ export type SlackBlock = Record<string, unknown>;
18
60
  export interface SlackInboundEvent {
19
61
  type: string;
20
62
  user?: string;
@@ -43,6 +85,7 @@ export interface SlackWebLike {
43
85
  channel: string;
44
86
  text: string;
45
87
  thread_ts?: string;
88
+ blocks?: SlackBlock[];
46
89
  }): Promise<{
47
90
  ts?: string;
48
91
  channel?: string;
@@ -51,6 +94,17 @@ export interface SlackWebLike {
51
94
  channel: string;
52
95
  ts: string;
53
96
  text: string;
97
+ blocks?: SlackBlock[];
98
+ }): Promise<unknown>;
99
+ /**
100
+ * Visible only to `user`. Refusing a click needs to reach the person
101
+ * who clicked without announcing to the channel that they tried.
102
+ */
103
+ postEphemeral(args: {
104
+ channel: string;
105
+ user: string;
106
+ text: string;
107
+ thread_ts?: string;
54
108
  }): Promise<unknown>;
55
109
  };
56
110
  files: {
@@ -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":"AAKA,OAAO,EAEL,KAAK,cAAc,EAGpB,MAAM,wBAAwB,CAAC;AAOhC,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,0EAA0E;QAC1E,IAAI,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACvB,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC7B,OAAO,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1B;;;;;WAKG;QACH,OAAO,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;SAAE,CAAC;KACtE,CAAC;IACF,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5E,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QAAE,IAAI,IAAI,OAAO,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAClE,IAAI,EAAE;QACJ,WAAW,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC5I,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACrG;;;WAGG;QACH,aAAa,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC5G,CAAC;IACF,KAAK,EAAE;QAGL,QAAQ,CAAC,IAAI,EAAE;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC/H,CAAC;IACF,KAAK,EAAE;QACL,IAAI,CAAC,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,OAAO,CAAC,EAAE;oBAAE,YAAY,CAAC,EAAE,MAAM,CAAC;oBAAC,SAAS,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAC9H,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,eAAe,CAAC;IAC3D,8DAA8D;IAC9D,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,YAAY,CAAC;CACtD;AAoaD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,mBAAmB,KAAG,cAqrBxE,CAAC"}