@firedrill-tools/slack 0.1.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/LICENSE +201 -0
- package/README.md +369 -0
- package/firedrill/agent.target.json +17 -0
- package/firedrill/baseline.scenario.json +5 -0
- package/firedrill/bounds.scenario.json +112 -0
- package/firedrill/conformance.suite.json +21 -0
- package/firedrill/history-unavailable.scenario.json +11 -0
- package/firedrill/open-long-handles.scenario.json +263 -0
- package/firedrill/open-member-bound.scenario.json +24 -0
- package/firedrill/post-rate-limited.scenario.json +11 -0
- package/firedrill/response-budget.scenario.json +24 -0
- package/firedrill/slack-bounds.drill.json +428 -0
- package/firedrill/slack-denied.drill.json +74 -0
- package/firedrill/slack-fresh-install.drill.json +132 -0
- package/firedrill/slack-history-unavailable.drill.json +83 -0
- package/firedrill/slack-invalid-auth.drill.json +408 -0
- package/firedrill/slack-mcp-aliases.drill.json +219 -0
- package/firedrill/slack-open-long-handles.drill.json +82 -0
- package/firedrill/slack-open-member-bound.drill.json +135 -0
- package/firedrill/slack-post-rate-limited.drill.json +96 -0
- package/firedrill/slack-response-budget.drill.json +118 -0
- package/firedrill/slack-thread-many-repliers.drill.json +100 -0
- package/firedrill/slack-ts-sequence-full.drill.json +165 -0
- package/firedrill/slack-visibility.drill.json +110 -0
- package/firedrill/slack-web-api-flow.drill.json +896 -0
- package/firedrill/thread-many-repliers.scenario.json +79 -0
- package/firedrill/tools/slack/app/assets/ATTRIBUTION.md +40 -0
- package/firedrill/tools/slack/app/assets/fonts/OFL.txt +93 -0
- package/firedrill/tools/slack/app/assets/fonts/lato-latin-400.woff2 +0 -0
- package/firedrill/tools/slack/app/assets/fonts/lato-latin-700.woff2 +0 -0
- package/firedrill/tools/slack/app/assets/fonts/lato-latin-900.woff2 +0 -0
- package/firedrill/tools/slack/app/assets/slack-wordmark.svg +1 -0
- package/firedrill/tools/slack/app/assets/slack.svg +1 -0
- package/firedrill/tools/slack/app/site/app.js +2202 -0
- package/firedrill/tools/slack/app/site/assets/fonts/lato-latin-400.woff2 +0 -0
- package/firedrill/tools/slack/app/site/assets/fonts/lato-latin-700.woff2 +0 -0
- package/firedrill/tools/slack/app/site/assets/fonts/lato-latin-900.woff2 +0 -0
- package/firedrill/tools/slack/app/site/assets/slack-wordmark.svg +1 -0
- package/firedrill/tools/slack/app/site/assets/slack.svg +1 -0
- package/firedrill/tools/slack/app/site/chrome.css +73 -0
- package/firedrill/tools/slack/app/site/chrome.js +96 -0
- package/firedrill/tools/slack/app/site/icons.js +96 -0
- package/firedrill/tools/slack/app/site/index.html +253 -0
- package/firedrill/tools/slack/app/site/styles.css +2719 -0
- package/firedrill/tools/slack/app/site/ui.js +491 -0
- package/firedrill/tools/slack/behavior.mjs +1171 -0
- package/firedrill/tools/slack/lib/access.mjs +138 -0
- package/firedrill/tools/slack/lib/budget.mjs +67 -0
- package/firedrill/tools/slack/lib/ids.mjs +146 -0
- package/firedrill/tools/slack/lib/search.mjs +172 -0
- package/firedrill/tools/slack/lib/wire.mjs +144 -0
- package/firedrill/tools/slack/slack.tool.json +6114 -0
- package/firedrill/ts-sequence-full.scenario.json +38 -0
- package/firedrill/world.json +2008 -0
- package/firedrill.json +5 -0
- package/package.json +62 -0
- package/starter.json +1562 -0
- package/test/conformance.mjs +1166 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Reload Tech Inc.
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# @firedrill-tools/slack
|
|
2
|
+
|
|
3
|
+
A synthetic **Slack workspace** for [Firedrill](https://firedrill.run) drills. It simulates one workspace behind a
|
|
4
|
+
bounded subset of the **Slack Web API** (`/api/<method>`, the same paths, argument names, form encoding and response
|
|
5
|
+
bodies the official SDKs use) and exposes the eight tool names of the archived reference **Slack MCP server** as aliases.
|
|
6
|
+
It ships a **browser app that recreates the Slack desktop client** (see Browser app) over the same operations. Everything
|
|
7
|
+
lives in the Firedrill world: posting a message stores a row that other members of the same synthetic workspace can read.
|
|
8
|
+
No Slack service is ever contacted and no real workspace can be reached.
|
|
9
|
+
|
|
10
|
+
Tool id `slack` · package version `0.1.1` · engine `>=0.1.0 <0.2.0` · Apache-2.0.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
firedrill tool add @firedrill-tools/slack --install
|
|
16
|
+
firedrill serve
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`tool add --install` on a fresh project creates the `local-dev` actor with every operation granted and copies the 87
|
|
20
|
+
starter rows (one workspace). In an existing project it changes nothing silently: add the exact grants you want to the
|
|
21
|
+
actor that represents your agent and, optionally, the attribute that selects which workspace member it acts as:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"id": "agent",
|
|
26
|
+
"attributes": { "userId": "U01DANA0001", "teamId": "T01EXAMPLE0" },
|
|
27
|
+
"grants": [
|
|
28
|
+
{ "packageId": "slack", "operationId": "auth.test" },
|
|
29
|
+
{ "packageId": "slack", "operationId": "conversations.list" },
|
|
30
|
+
{ "packageId": "slack", "operationId": "conversations.history" },
|
|
31
|
+
{ "packageId": "slack", "operationId": "chat.post-message" }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Identities
|
|
37
|
+
|
|
38
|
+
The actor attribute `userId` selects the member the actor acts as and must be the id of a `users` row; an actor whose
|
|
39
|
+
`userId` matches no member fails every operation with `INVALID_AUTH` (HTTP 401, Slack `invalid_auth`), which is what a
|
|
40
|
+
token that resolves to no workspace member does. **Without the attribute the actor acts as the workspace's first active
|
|
41
|
+
human member in `users` row-id order** (deactivated and bot users are skipped; Dana Reyes, `U01DANA0001`, in the starter
|
|
42
|
+
data), so the grant-only `local-dev` actor that `tool add --install` creates is a member from its first call and needs
|
|
43
|
+
nothing added by hand; only a workspace without any active human member answers `invalid_auth` for such an actor. Set
|
|
44
|
+
`userId` explicitly to act as someone else. `teamId` is optional and must equal the workspace id when present. The 25
|
|
45
|
+
operation ids that can be granted are listed in the table below.
|
|
46
|
+
|
|
47
|
+
## Starting data
|
|
48
|
+
|
|
49
|
+
`starter.json` (and the author world in `firedrill/world.json`) describes the fictional workspace **Example Team**
|
|
50
|
+
(`T01EXAMPLE0`, `https://example-team.slack.com/`) at virtual time 2026-09-14T09:00Z:
|
|
51
|
+
|
|
52
|
+
- **6 members**: Dana Reyes (`U01DANA0001`, admin/owner), Sam Okafor, Priya Natarajan (contractor, member of `#general`
|
|
53
|
+
and `#random` only), Lee Marsh (deactivated), Ops Bot (`is_bot`, `B01OPSBOT01`) and Mira Chen (Singapore time zone,
|
|
54
|
+
status `:palm_tree:`). Addresses use `example.test`.
|
|
55
|
+
- **10 conversations**: `#general` (cannot be archived, one pinned welcome message), `#engineering` (15 messages: a
|
|
56
|
+
four-reply thread with one `thread_broadcast`, an edited message, `+1`/`eyes` reactions, a bot message with Block Kit
|
|
57
|
+
`blocks`; exactly three pages with `limit=5`), `#incidents` (private, a two-reply alert thread, one pinned runbook),
|
|
58
|
+
`#random`, `#launch-q3` (archived), `#design` (public, Dana is not a member), `#leadership` (private, invisible to Dana),
|
|
59
|
+
a Dana ↔ Sam IM, a Dana/Sam/Mira group DM and the empty `#onboarding`.
|
|
60
|
+
- **38 messages**, **30 memberships**, **2 pins**; the workspace `id_sequence` starts at 50, so generated ids
|
|
61
|
+
(`C000000001F`, `D000000001G`, …) never collide with authored rows. `message_sequence` is the six-digit `ts` fraction
|
|
62
|
+
and restarts in every virtual second (`message_second` records which second it belongs to; authored data may omit it),
|
|
63
|
+
so the first generated `ts` is `1789376400.000001`. An allocation skips any fraction an authored row already occupies.
|
|
64
|
+
|
|
65
|
+
Replace it with your own scenarios whenever you like — nothing in the behavior depends on these rows.
|
|
66
|
+
|
|
67
|
+
## Operations
|
|
68
|
+
|
|
69
|
+
Canonical MCP names are `slack.<operation>`; the alias column lists the reference Slack MCP server tool name exposed in
|
|
70
|
+
addition. Read methods exist as `GET` (query string) **and** `POST`, write methods as `POST`. A `POST` body is
|
|
71
|
+
`application/x-www-form-urlencoded` (what `@slack/web-api` and `slack_sdk` send; also assumed when no content type is
|
|
72
|
+
given) **or** an `application/json` object, as the Web API accepts with a bearer token: `blocks` may then be a real array
|
|
73
|
+
and booleans real booleans.
|
|
74
|
+
|
|
75
|
+
| operation | MCP alias | HTTP route | notes |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `auth.test` | — | `GET`/`POST /api/auth.test` | `url`, `team`, `user`, `team_id`, `user_id`, `bot_id` for bot users |
|
|
78
|
+
| `users.list` | `slack_get_users` | `GET`/`POST /api/users.list` | `limit` (≤ 1000), `cursor`; deactivated and bot users included |
|
|
79
|
+
| `users.info` | — | `GET`/`POST /api/users.info` | `user` |
|
|
80
|
+
| `users.profile.get` | `slack_get_user_profile` | `GET`/`POST /api/users.profile.get` | `user` optional (defaults to the acting member) |
|
|
81
|
+
| `conversations.list` | `slack_list_channels` | `GET`/`POST /api/conversations.list` | `types` (public_channel, private_channel, mpim, im), `exclude_archived`, `limit`, `cursor` |
|
|
82
|
+
| `conversations.info` | — | `GET`/`POST /api/conversations.info` | `include_num_members`; `is_member` always computed |
|
|
83
|
+
| `conversations.members` | — | `GET`/`POST /api/conversations.members` | `limit`, `cursor` |
|
|
84
|
+
| `conversations.create` | — | `POST /api/conversations.create` | `name` (leading `#` stripped, lower-cased), `is_private`; emits `channel.created` |
|
|
85
|
+
| `conversations.join` | — | `POST /api/conversations.join` | public channels only; repeat join returns `warning: already_in_channel` |
|
|
86
|
+
| `conversations.invite` | — | `POST /api/conversations.invite` | `users` comma list; all-or-nothing; one `channel_join` message per invitee |
|
|
87
|
+
| `conversations.open` | — | `POST /api/conversations.open` | `users` (1 → IM, 2–8 → group DM) or `channel`; `return_im` |
|
|
88
|
+
| `conversations.archive` | — | `POST /api/conversations.archive` | not `#general`, not IMs; no unarchive |
|
|
89
|
+
| `conversations.set-topic` | — | `POST /api/conversations.setTopic` | `topic` ≤ 250 characters |
|
|
90
|
+
| `conversations.history` | `slack_get_channel_history` | `GET`/`POST /api/conversations.history` | newest first, `limit` (≤ 999), `cursor`, `oldest`/`latest`/`inclusive`, `has_more`, `pin_count` |
|
|
91
|
+
| `conversations.replies` | `slack_get_thread_replies` | `GET`/`POST /api/conversations.replies` | parent first then replies oldest first; `ts`; `limit`, `cursor`, `oldest`/`latest` |
|
|
92
|
+
| `chat.post-message` | `slack_post_message` | `POST /api/chat.postMessage` | `text` and/or `blocks` (JSON string in a form body, real array in a JSON body), `thread_ts`, `reply_broadcast`; emits `message.posted` |
|
|
93
|
+
| `chat.reply` | `slack_reply_to_thread` | — (use `chat.postMessage` with `thread_ts`) | the reference server's threaded-reply contract |
|
|
94
|
+
| `chat.update` | — | `POST /api/chat.update` | own messages only; sets `edited` |
|
|
95
|
+
| `chat.delete` | — | `POST /api/chat.delete` | own messages, or anyone's for `is_admin`; a parent with replies becomes a `tombstone`; emits `message.deleted` |
|
|
96
|
+
| `reactions.add` | `slack_add_reaction` | `POST /api/reactions.add` | `name` with or without colons; 50 distinct reactions per message; emits `reaction.added` |
|
|
97
|
+
| `reactions.remove` | — | `POST /api/reactions.remove` | |
|
|
98
|
+
| `pins.add` / `pins.remove` | — | `POST /api/pins.add`, `…/pins.remove` | keeps `pinned_to` on the message in sync; `pins.add` refuses the 101st pin of a conversation with `too_many_rows` (Slack's limit is 100 pins) |
|
|
99
|
+
| `pins.list` | — | `GET`/`POST /api/pins.list` | newest pin first, with `permalink` and `pinned_to` |
|
|
100
|
+
| `search.messages` | — | `GET`/`POST /api/search.messages` | `query`, `count` (≤ 100), `page`, `sort` score/timestamp, `sort_dir` |
|
|
101
|
+
|
|
102
|
+
The MCP aliases accept the argument names of the reference server (`channel_id`, `user_id`, `thread_ts`, `timestamp`,
|
|
103
|
+
`reaction`, `limit`, `cursor`); the canonical operations accept both those spellings and the Web API ones (`channel`,
|
|
104
|
+
`user`, `ts`, `name`) and fail with `INVALID_ARGUMENTS` when both are given. Every success body carries `"ok": true` and is
|
|
105
|
+
returned verbatim over HTTP; MCP structured content is the same object.
|
|
106
|
+
|
|
107
|
+
### Access model (bot-token shaped)
|
|
108
|
+
|
|
109
|
+
- Public channels are visible to every member (`list`, `info`, `members`); private channels, IMs and group DMs only to
|
|
110
|
+
their members — for anyone else they are `channel_not_found`.
|
|
111
|
+
- Reading history/replies/pins and every write requires membership (`not_in_channel`). `conversations.join` is the way
|
|
112
|
+
into a public channel; it is `method_not_supported_for_channel_type` for private channels, IMs and group DMs.
|
|
113
|
+
- Archived conversations stay readable and refuse every write (`is_archived`).
|
|
114
|
+
- `chat.update` needs authorship (`cant_update_message`); `chat.delete` needs authorship or `is_admin`
|
|
115
|
+
(`cant_delete_message`). Deactivated members cannot be invited or messaged (`user_not_found`).
|
|
116
|
+
- Framework grants authorize calling an operation; a grant-less actor gets `denied` (HTTP 403 with a Slack-shaped
|
|
117
|
+
`missing_scope` body naming the operation).
|
|
118
|
+
|
|
119
|
+
### Errors and status codes
|
|
120
|
+
|
|
121
|
+
Error bodies are Slack-shaped: `{ "ok": false, "error": "<snake_case>", "response_metadata": { "messages": ["…"] } }`
|
|
122
|
+
(the `messages` array only when the handler adds detail, e.g. `invalid_arguments`). **The HTTP status is 4xx/5xx**, not
|
|
123
|
+
Slack's usual 200 — see [Protocol compatibility](#protocol-compatibility). Mapping: 400 `invalid_arguments`,
|
|
124
|
+
`invalid_cursor`, `invalid_name`, `no_text`, `no_query`, `is_archived`, `already_archived`,
|
|
125
|
+
`method_not_supported_for_channel_type`, `cant_invite_self`, `too_many_reactions`, `invalid_blocks_format` (a `blocks`
|
|
126
|
+
form field that is not JSON), `invalid_blocks` (JSON that is not an array of at most 50 typed block objects, nests deeper
|
|
127
|
+
than 32 levels or encodes to more than 100 KB), `response_too_large` (this package's own error: one message, match or pin
|
|
128
|
+
set alone exceeds the response byte budget — see Limitations), `too_many_rows` (this package's own
|
|
129
|
+
error, not a Slack one: a bounded read found more rows than the workspace's bound — including the lookup a reply to a thread
|
|
130
|
+
with a full `reply_users` list makes — or a conversation is at its pin bound —
|
|
131
|
+
see Limitations) · 401 `invalid_auth` · 403 `not_in_channel`, `cant_update_message`, `cant_delete_message` · 404
|
|
132
|
+
`channel_not_found`, `user_not_found`, `message_not_found`, `thread_not_found`, `no_reaction`, `not_pinned` · 409
|
|
133
|
+
`name_taken`, `already_in_channel`, `already_reacted`, `already_pinned` · 429 `ratelimited` (+ `Retry-After: 30`) · 503
|
|
134
|
+
`service_unavailable`. Framework outcomes are rendered Slack-shaped too: schema violations → 400 `invalid_arguments`,
|
|
135
|
+
missing grant → 403 `missing_scope`, unknown method → 404 `framework.HTTP_ROUTE_NOT_FOUND` (the framework's own body),
|
|
136
|
+
`GET` on a write route → 405. A `POST` body that cannot be mapped to a call at all — JSON that does not parse
|
|
137
|
+
(Slack: `invalid_json`), a JSON value that is not an object (`json_not_object`), or a content type other than form or
|
|
138
|
+
JSON — answers the framework's 400 `framework.HTTP_REQUEST_MAPPING_FAILED` body, whose text names the Slack error; the
|
|
139
|
+
Tool cannot declare those as Slack-shaped errors because the request never reaches an operation.
|
|
140
|
+
|
|
141
|
+
### Search query subset
|
|
142
|
+
|
|
143
|
+
Whitespace-separated terms (case-insensitive substring of `text`, all must match), `"quoted phrases"`, `-negation`, and
|
|
144
|
+
the modifiers `in:#name` / `in:name` / `in:<#C…>` / `in:@handle` (IM), `from:@handle` / `from:<@U…>` / `from:me`,
|
|
145
|
+
`is:thread`, `has:pin`, `has:reaction`, `has::emoji:`, `before:` / `after:` / `on:` (`YYYY-MM-DD`, UTC) and
|
|
146
|
+
`during:YYYY-MM`. Only conversations the acting member belongs to are searched; `tombstone` and `channel_join` messages are
|
|
147
|
+
skipped. Score = 10 per matching term, +5 for a whole-word match, +1 for a top-level message; ties break by `ts`
|
|
148
|
+
descending. Any other modifier (`to:`, `has:link`, `has:file`, `is:saved`, `with:`, `during:` with a day, …) fails with
|
|
149
|
+
400 `invalid_arguments` — real Slack treats unknown modifiers as text. A `query` holding U+FFFD (the framework decodes
|
|
150
|
+
malformed percent-encoding such as `%E0%A4%A` in query strings and form bodies to U+FFFD; a correctly encoded
|
|
151
|
+
`%EF%BF%BD` is rejected the same way) fails with 400 `invalid_arguments` instead of an empty result; `%ZZ` stays literal
|
|
152
|
+
text, and non-ASCII searches (`café`, `漢字`, emoji) work. `conversations.list` `types` is an enumerated list, so a
|
|
153
|
+
mangled value is already `invalid_arguments`; `users.list` has no free-text filter. Pagination is page-based (`count`/`page`); a page
|
|
154
|
+
past the end returns an empty page.
|
|
155
|
+
|
|
156
|
+
## Protocol compatibility
|
|
157
|
+
|
|
158
|
+
- **HTTP**: paths, methods, argument names, form encoding, success bodies, cursors (`response_metadata.next_cursor`,
|
|
159
|
+
`""` on the last page) and error strings follow the public Slack Web API documentation for the subset above.
|
|
160
|
+
**Not verified against `@slack/web-api` or `slack_sdk`**; the manifest's `compatibility` list is empty. The
|
|
161
|
+
`slack-web-api` connection recipe maps `SLACK_API_URL` / `SLACK_BOT_TOKEN` to the world's HTTP URL and token for a
|
|
162
|
+
test process (`@slack/web-api`: `slackApiUrl = SLACK_API_URL + "/api/"`; `slack_sdk`: `base_url`).
|
|
163
|
+
- **MCP**: the eight tool names and argument names follow the README of the archived reference Slack MCP server
|
|
164
|
+
(`modelcontextprotocol/servers-archived`, `src/slack`) as read on 2026-09-14; outputs are the Slack bodies it returned
|
|
165
|
+
verbatim. **Not verified against that server or against Slack's hosted MCP server**; the conformance suite drives the
|
|
166
|
+
aliases with raw JSON-RPC over Streamable HTTP.
|
|
167
|
+
|
|
168
|
+
### Error envelope: HTTP status versus Slack's `ok: false`
|
|
169
|
+
|
|
170
|
+
Real Slack answers **HTTP 200** with `{"ok":false,"error":"…"}` for every platform error except rate limits (HTTP 429)
|
|
171
|
+
and outages (5xx). This package cannot reproduce that. The framework's manifest contract for HTTP routes
|
|
172
|
+
(`@firedrill-run/contracts`, `HttpRouteContractSchema`: `response.errors[].status` is `z.number().int().min(400).max(599)`)
|
|
173
|
+
requires every declared error of a route to map to a status between 400 and 599, and the manifest validator refuses a
|
|
174
|
+
route whose error map does not cover the operation's declared errors — so a 200 cannot be declared for an error and the
|
|
175
|
+
framework, which owns status codes, sends the mapped 4xx. What this package does instead: the **body** is Slack's exactly
|
|
176
|
+
(`{"ok":false,"error":"channel_not_found"}`, `response_metadata.messages` where Slack adds detail, `Retry-After` on 429)
|
|
177
|
+
while the status is the 4xx listed under "Errors and status codes". Effect on the official SDKs, read from their published
|
|
178
|
+
source and **not exercised here** (the `compatibility` list stays empty):
|
|
179
|
+
|
|
180
|
+
- **`@slack/web-api` (Node)**: `WebClient` treats a non-200 response that is not a 429 as an HTTP-level failure and rejects
|
|
181
|
+
with an error whose `code` is `slack_webapi_http_error` (`ErrorCode.HTTPError`, carrying `statusCode`, `statusMessage`,
|
|
182
|
+
`headers` and the parsed `body`), not the `slack_webapi_platform_error` (`ErrorCode.PlatformError`) whose `data.error`
|
|
183
|
+
holds the Slack error string. Code that branches on `error.code === ErrorCode.PlatformError` or reads `error.data.error`
|
|
184
|
+
therefore does not see `is_archived`, `channel_not_found` and friends; read `error.body.error` instead, and expect the
|
|
185
|
+
client's retry policy to treat such responses as it treats any HTTP error before rejecting. 429 (`ratelimited` +
|
|
186
|
+
`Retry-After`) and 503 (`service_unavailable`) behave as with Slack itself.
|
|
187
|
+
- **`slack_sdk` (Python)**: `WebClient` raises `SlackApiError` both for a non-200 status and for `ok: false`, so the
|
|
188
|
+
exception type is the same as with Slack and `e.response["error"]` still holds the Slack error string; only
|
|
189
|
+
`e.response.status_code` differs (4xx instead of 200).
|
|
190
|
+
- Agents and hand-written clients that branch on `body.ok`/`body.error` keep working; agents that treat any non-200 as a
|
|
191
|
+
transport failure do not — that is the deviation to plan for.
|
|
192
|
+
|
|
193
|
+
## Browser app
|
|
194
|
+
|
|
195
|
+
`firedrill serve` prints an app link (also **Tools → Open app** in the inspector). The app is a faithful recreation of the
|
|
196
|
+
Slack desktop web client in its default *Aubergine* theme, built so that a daily Slack user recognises every screen: the
|
|
197
|
+
44 px top bar with back/forward/history controls, the centred search field ("Search Example Team", `Ctrl/⌘ K`) and help;
|
|
198
|
+
the 70 px workspace switcher rail (workspace tile, Home / DMs / Activity / More, the round **+** and your avatar with a
|
|
199
|
+
presence dot); the 260 px sidebar with the workspace name menu, filter and compose buttons, collapsible **Channels**
|
|
200
|
+
(`#` / lock glyphs, bold for conversations with activity since you last opened them, "Add channels" → create or browse)
|
|
201
|
+
and **Direct messages** (avatars with presence, group DMs with a member-count tile, "New message"); the white
|
|
202
|
+
conversation pane with the `# channel ▾` header (opens channel details), topic, member pill with stacked avatars,
|
|
203
|
+
**Messages / Pins** tabs, day-divider pills ("Today", "Yesterday", weekdays, "Thu, Aug 20th"), 36 px rounded-square letter
|
|
204
|
+
avatars, bold sender names with an **APP** badge for bots, hover times on consecutive messages, "(edited)" markers,
|
|
205
|
+
"replied to a thread:" preambles for broadcast replies, grey system lines for joins, italic tombstones, reaction pills
|
|
206
|
+
(your own in blue), "N replies · Last reply …" thread footers with reply avatars, the hover toolbar (✅ 👀 🙌 quick
|
|
207
|
+
reactions, add reaction, reply in thread, more → pin/unpin, copy link, edit, delete) and Slack's channel intro card;
|
|
208
|
+
the composer with the formatting toolbar (bold, italic, strike, link, lists, quote, code, code block as mrkdwn
|
|
209
|
+
markers), `+`, formatting toggle, emoji picker, `@` mention menu and the green send button (Enter sends, Shift+Enter
|
|
210
|
+
newline, `↑` edits your last message, Esc cancels); the 400 px **Thread** panel with the "N replies" divider and a
|
|
211
|
+
reply composer with "Also send to #channel"; the right-hand **Profile** panel (large avatar, title, status, local time,
|
|
212
|
+
Message button, contact information); the channel **details modal** (About with editable topic, Members with search and
|
|
213
|
+
"Add people", Settings with "Archive channel"); **Create a channel** (name with `#` prefix and 80-character counter,
|
|
214
|
+
Public/Private), **Edit topic**, **Add people** / **New message** pickers with chips, blue-highlight context menus and
|
|
215
|
+
Slack-red confirmations for delete and archive; the **Search** results view (query heading, Messages tab with count,
|
|
216
|
+
In / From / Pinned filter chips, Most relevant / Most recent sort, highlighted terms, `1–20 of N` pager, click to jump
|
|
217
|
+
and flash the message); **Browse channels** (All channels, search, member counts, Join / Joined ✓, Archived badge);
|
|
218
|
+
a **People** directory; and the "About this workspace" panel showing the official Slack mark and wordmark. Routes
|
|
219
|
+
mirror the client's conversation ids (`#C…`, `#C…/thread/<ts>`, `#search/<query>`, `#browse`, `#people`, `#pins`).
|
|
220
|
+
Below 768 px the rail hides behind a hamburger, the sidebar becomes a drawer and the thread/profile panel a full-width
|
|
221
|
+
sheet, so nothing scrolls horizontally at 375 px.
|
|
222
|
+
|
|
223
|
+
Every control calls the Tool's own operations through `/_firedrill/client.js`: history, replies, members and users
|
|
224
|
+
paginate with the Web API cursors ("Load older messages"), search sends the typed query verbatim (an unsupported
|
|
225
|
+
modifier surfaces the operation's `invalid_arguments` inline), every mutation carries a fresh idempotency key that is
|
|
226
|
+
reused only when the same uncertain action is retried, the page polls `getContext().revision` every 2 s and refreshes
|
|
227
|
+
the sidebar, the open conversation, thread or search without discarding composer text, and loading skeletons, empty
|
|
228
|
+
states (channel intro, "No pinned items yet", "No results"), errors (503 fault card with Retry, 429 fault note under the
|
|
229
|
+
composer that keeps your text), join bars for public channels you are not in, archived banners, `invalid_auth` and
|
|
230
|
+
permission-denied (`missing_scope`) states are rendered. All record text goes through `textContent`; mrkdwn markers,
|
|
231
|
+
`<@U…>` mentions, `<#C…>` channel links, `<url|label>` links and `:shortcodes:` are rendered from text nodes and styled
|
|
232
|
+
spans only. Presence is not modelled by the backend, so other members show the hollow "away" dot and only you show
|
|
233
|
+
active; "unread" bold is a per-tab convenience (activity since you last opened the conversation in this tab), not a
|
|
234
|
+
server-side `last_read`. A UI write is visible over HTTP/MCP and vice versa because both hit the same operations; reset
|
|
235
|
+
restores both.
|
|
236
|
+
|
|
237
|
+
The app is served from `firedrill/tools/slack/app/site/` (`ui.root`), which bundles Lato 400/700/900 (SIL OFL 1.1) and
|
|
238
|
+
the official Slack mark and wordmark; `firedrill/tools/slack/app/assets/ATTRIBUTION.md` records every asset's source
|
|
239
|
+
URL, hash and licence (the framework serves only html/css/js/json/image/font files, so the Markdown record and the
|
|
240
|
+
licence text sit beside, not inside, the served root). Screenshots live in the author repository (`specs/slack/*.png`),
|
|
241
|
+
not in the package.
|
|
242
|
+
|
|
243
|
+
## Faults and events
|
|
244
|
+
|
|
245
|
+
| id | kind | effect |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| `post-rate-limited` | fault on `chat.post-message`, `chat.reply`, `chat.update` | 429 `ratelimited`, `Retry-After: 30`; nothing is stored, no event |
|
|
248
|
+
| `history-unavailable` | fault on `conversations.history`, `search.messages` | 503 `service_unavailable`; replies, info, pins keep working |
|
|
249
|
+
| `message.posted` | event | `{ channel, ts, user, thread_ts?, subtype?, text }` from `chat.post-message` / `chat.reply` (not for `channel_join` system messages) |
|
|
250
|
+
| `message.deleted` | event | `{ channel, ts, user, tombstoned }` |
|
|
251
|
+
| `reaction.added` | event | `{ channel, ts, user, reaction }` |
|
|
252
|
+
| `channel.created` | event | `{ channel, name, creator, is_private }` (from `conversations.create`, not from `conversations.open`) |
|
|
253
|
+
|
|
254
|
+
Activate a fault in a scenario with `"faults": [{ "packageId": "slack", "faultId": "post-rate-limited" }]`.
|
|
255
|
+
|
|
256
|
+
## Conformance
|
|
257
|
+
|
|
258
|
+
`firedrill tool test slack` runs fourteen drills twice (Web API flow over GET and POST routes with form and JSON bodies, MCP aliases, member
|
|
259
|
+
visibility, denied actor, `invalid_auth` actor, both faults, a fresh-install actor without identity attributes, the
|
|
260
|
+
workspace bounds — scenario `bounds` lowers every bound of the `workspace` row below the authored rows — the member
|
|
261
|
+
bound of `conversations.open` — scenario `open-member-bound` sets `limits.members` to 2 — and group DMs of members with
|
|
262
|
+
21-character handles — scenario `open-long-handles` — a thread that already has 50 distinct repliers — scenario
|
|
263
|
+
`thread-many-repliers` — and a 4,000-byte response budget — scenario `response-budget` sets `limits.response_bytes`, and
|
|
264
|
+
the drill pages users, conversations, history, replies and search with multi-byte text, checks every page stays within
|
|
265
|
+
the budget and returns every item exactly once, then gets `response_too_large` from an oversized message and pin set —
|
|
266
|
+
and a full `ts` second — scenario `ts-sequence-full` seeds `message_sequence 999997` in the frozen virtual second with an
|
|
267
|
+
authored message at `.999998`; the drill posts the last `.999999` and then gets `too_many_rows` from posting, replying,
|
|
268
|
+
joining and inviting with nothing written)
|
|
269
|
+
from `firedrill/`
|
|
270
|
+
with the Node-only target
|
|
271
|
+
`test/conformance.mjs`, checks that every operation, declared error, event and fault was observed, and compares the two
|
|
272
|
+
passes. Author-written checks are not certification of Slack fidelity.
|
|
273
|
+
|
|
274
|
+
## Limitations
|
|
275
|
+
|
|
276
|
+
- A bounded subset: 25 operations, 24 Slack methods, 35 HTTP routes. Every other method (`conversations.leave/rename/
|
|
277
|
+
setPurpose/kick/unarchive/mark/close`, `chat.postEphemeral/scheduleMessage/getPermalink/meMessage`, `reactions.get/
|
|
278
|
+
list`, `files.*`, `bookmarks.*`, `canvases.*`, `usergroups.*`, `users.lookupByEmail/setPresence/getPresence/profile.set`,
|
|
279
|
+
`team.info`, `emoji.list`, `views.*`, `apps.*`, `oauth.*`, `admin.*`, `search.all/files`, `rtm.*`) returns the framework
|
|
280
|
+
404; a `GET` on a declared write path returns 405. Events API, Socket Mode, interactivity payloads, Block Kit
|
|
281
|
+
rendering, Slack Connect and Enterprise Grid are not emulated.
|
|
282
|
+
- **Errors use HTTP 4xx/5xx status codes** with Slack-shaped `{ ok: false, error }` bodies. Real Slack answers HTTP 200 for
|
|
283
|
+
`ok: false` (except 429 and 5xx); the framework's route contract only allows 400–599 for declared errors, so this cannot
|
|
284
|
+
be reproduced — see "Error envelope" under Protocol compatibility for the exact constraint and what each official SDK
|
|
285
|
+
does with it.
|
|
286
|
+
- `POST` bodies are form-encoded or JSON objects only; `multipart/form-data` (Slack accepts it) is refused with the
|
|
287
|
+
framework 400 described above. The legacy `token` form/query parameter is not accepted; the bearer token is the
|
|
288
|
+
isolated world token. OAuth scopes, `xoxb`/`xoxp` token types, app installation and per-method `missing_scope` are not
|
|
289
|
+
emulated — a grant-less actor gets `missing_scope` for everything.
|
|
290
|
+
- Not verified against `@slack/web-api`, `slack_sdk`, Slack's hosted MCP server or the archived reference MCP server.
|
|
291
|
+
- Access is bot-token shaped: membership is required to read history/replies/pins and to write; user-token nuances
|
|
292
|
+
(reading public channels without joining, admin visibility of private channels, `account_inactive` for deactivated
|
|
293
|
+
actors) are not modelled. A thread's `reply_users` lists at most its first 50 distinct repliers; replies from
|
|
294
|
+
further participants are still posted and counted in `reply_count` and `reply_users_count` (which counts every distinct
|
|
295
|
+
replier), they are just not added to the list.
|
|
296
|
+
- `ts` values are unique and ordered but the fractional part is a per-second sequence, not sub-second wall time; all
|
|
297
|
+
times are virtual. One virtual second holds at most **999,999** message timestamps; when the clock does not advance,
|
|
298
|
+
the next `chat.postMessage`, `chat.reply`, `conversations.join` or `conversations.invite` fails with 400
|
|
299
|
+
`too_many_rows` ("message ts limit reached …") and writes nothing. `edited.ts` is the virtual second with a `.000000`
|
|
300
|
+
fraction.
|
|
301
|
+
- Search is substring-based over the documented modifier subset; unsupported modifiers fail with 400 instead of being
|
|
302
|
+
treated as text; no ranking parity, stemming, `to:`, file/link filters, `search.all`, or cursor pagination.
|
|
303
|
+
- Messages store `text` and opaque `blocks`; no mrkdwn parsing, link unfurling, mention resolution on the server
|
|
304
|
+
(`<@U…>` is stored verbatim), attachments, files, edit history, message metadata or permalink resolution (permalinks
|
|
305
|
+
are string literals built from the workspace row and resolve nowhere).
|
|
306
|
+
- Tombstoning, `channel_join` system messages and reply counters follow Slack loosely; there are no `channel_leave`,
|
|
307
|
+
`channel_topic` or `channel_archive` system messages, no unread/`last_read` tracking, no typing or presence.
|
|
308
|
+
- **Row bounds, never silent truncation.** The workspace serves at most 1,000 users, 1,000 conversations, 1,000 members
|
|
309
|
+
per conversation, 10,000 messages per conversation and 100 pins per conversation (Slack's own pin limit); a world may
|
|
310
|
+
set its own with `limits: { users, conversations, members, messages, pins }` on the `workspace` row (each 1–10,000).
|
|
311
|
+
Every operation that has to read a whole set — `users.list`, `conversations.list`, `conversations.info` with
|
|
312
|
+
`include_num_members`, `conversations.members`, `conversations.create`, `conversations.join`, `conversations.invite`,
|
|
313
|
+
`conversations.open`, `conversations.set-topic` on an IM or group DM, `conversations.history`, `conversations.replies`,
|
|
314
|
+
`chat.delete` of a reply (it recounts the thread), a threaded `chat.postMessage` to a thread whose `reply_users` list is
|
|
315
|
+
already full (it looks for an earlier reply by the caller, before the new `ts` is allocated), `pins.add`, `pins.list` and `search.messages` — reads one row past
|
|
316
|
+
the bound and fails with 400 `too_many_rows` ("messages in C01GENERAL1 exceed the supported bound of N rows") when the
|
|
317
|
+
authored data exceeds it, instead of returning the first N rows; `pins.add` also refuses to exceed the pin bound.
|
|
318
|
+
`conversations.open` refuses to create an IM or group DM whose member count (the requested users plus the caller)
|
|
319
|
+
exceeds the member bound, before any id is allocated and whether or not `return_im` is set: 400 `too_many_rows`
|
|
320
|
+
("members of the requested conversation (3) exceed the supported bound of 2 rows (workspace.limits.members)"). A
|
|
321
|
+
refused call stores nothing and consumes no id, and no error names a conversation id the call would have created;
|
|
322
|
+
`conversations.create` and `conversations.open` build the returned channel from the rows they just wrote instead of
|
|
323
|
+
rescanning them. Reads
|
|
324
|
+
and writes by id (`auth.test`, `users.info`, `users.profile.get`, `conversations.info` without a member count,
|
|
325
|
+
`chat.postMessage` (other than the full-thread case above), `chat.update`, `reactions.*`, `pins.remove`, `conversations.archive`) never scan and keep working.
|
|
326
|
+
`too_many_rows` is this package's error string, not Slack's.
|
|
327
|
+
- **Response byte budget, never an oversized body.** Every list and read sizes its page by the UTF-8 bytes of the encoded
|
|
328
|
+
JSON body (computed from code points, so CJK and emoji text count 3–4 bytes) as well as by count: 900 KB by default,
|
|
329
|
+
lower when the `workspace` row sets `limits.response_bytes` (1,024–900,000). `users.list`, `conversations.list`,
|
|
330
|
+
`conversations.members`, `conversations.history` and `conversations.replies` stop filling a page before the budget and
|
|
331
|
+
return `has_more` / `response_metadata.next_cursor`, so a page can hold fewer items than `limit` and the cursor returns
|
|
332
|
+
every item exactly once. `search.messages` keeps page arithmetic uniform: `per_page` / `paging.count` is the requested
|
|
333
|
+
`count`, lowered only as far as needed for every page of the result set to fit, so `page` N+1 starts where page N ended.
|
|
334
|
+
`pins.list` has no pagination in the Web API, so a pin set whose body exceeds the budget answers 400
|
|
335
|
+
`response_too_large`; a single message or match that alone exceeds the budget does the same in history, replies and
|
|
336
|
+
search (users, conversations and member ids with `too_many_rows`; unreachable within the stored schema bounds). With
|
|
337
|
+
the default budget a single message exceeds it only in extreme cases (`text` is at most 40,000 characters and
|
|
338
|
+
`blocks` at most 100 KB, so it takes reaction lists naming thousands of users).
|
|
339
|
+
- **Group DM names stay within 80 characters.** A group DM is named `mpdm-<handle>--<handle>-1` from its members' sorted
|
|
340
|
+
handles, as in Slack. Handles may be 21 characters and a group DM may have nine members, so that form can exceed the
|
|
341
|
+
80-character conversation name; such names keep the first 69 characters of the plain form (trailing `-`/`.` removed)
|
|
342
|
+
and end with `-<8 hex digits>-1`, an FNV-1a digest of the full handle list
|
|
343
|
+
(`mpdm-dana.reyes--longhandle1xxxxxxxxxx--longhandle2xxxxxxxxxx--longha-98de6b61-1`). Different member sets keep
|
|
344
|
+
distinct names; Slack's own naming of very large group DMs is not reproduced.
|
|
345
|
+
- The browser app covers the 25 operations only: no huddles, canvases, files, bookmarks, "Later", saved items,
|
|
346
|
+
notifications/Activity feed, presence, typing indicators, server-side unread counts, message forwarding, custom
|
|
347
|
+
emoji, Block Kit rendering (blocks are stored opaque and the message `text` is shown), channel rename/leave/unarchive
|
|
348
|
+
or purpose editing (the backend has no such operations).
|
|
349
|
+
- **Client chrome that is rendered but not simulated.** These controls sit where Slack puts them, with Slack-style glyphs,
|
|
350
|
+
hover states and tooltips, and open a short "Not simulated by this Tool" panel instead of acting: the rail's **Later**
|
|
351
|
+
tab; sidebar **Threads**, **Huddles**, **Drafts & sent** and **Add apps**; the channel **Files** tab and **+** (add tab);
|
|
352
|
+
the **huddle** button and its caret; the message toolbar's **Forward** and **Save for later**; the composer's **video
|
|
353
|
+
clip**, **audio clip** and **shortcuts** (slash) buttons and the send button's **schedule** caret, in both the channel
|
|
354
|
+
composer and the thread reply composer (which carries the same formatting toolbar, +, format toggle, emoji and mention
|
|
355
|
+
controls as the channel composer); and the rail **Activity** tab, which has no activity or notification feed.
|
|
356
|
+
**Directories** opens a People / Channels menu (over `users.list` and `conversations.list`). The **+** attach menu's
|
|
357
|
+
"Attach files" row is visibly disabled because `files.*` is not part of this Tool.
|
|
358
|
+
Nothing in these panels calls an operation or invents workspace data.
|
|
359
|
+
- All data is fictional; the package contains no real workspace, member, token or copied content and must never be
|
|
360
|
+
pointed at a production Slack workspace.
|
|
361
|
+
|
|
362
|
+
## Trademarks
|
|
363
|
+
|
|
364
|
+
Slack and the Slack logo are trademarks of Slack Technologies, LLC, a Salesforce company. They are used here only to
|
|
365
|
+
identify the service this package simulates inside a test environment; this package is an independent Firedrill Tool and
|
|
366
|
+
is not affiliated with, sponsored by or endorsed by Slack or Salesforce. The logo files under
|
|
367
|
+
`firedrill/tools/slack/app/` are unmodified official assets whose sources are listed in
|
|
368
|
+
`firedrill/tools/slack/app/assets/ATTRIBUTION.md`; the bundled Lato font is licensed under the SIL Open Font License 1.1
|
|
369
|
+
(`firedrill/tools/slack/app/assets/fonts/OFL.txt`).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"target": {
|
|
4
|
+
"id": "conformance-agent",
|
|
5
|
+
"kind": "command",
|
|
6
|
+
"bindings": [
|
|
7
|
+
"http",
|
|
8
|
+
"mcp"
|
|
9
|
+
],
|
|
10
|
+
"executable": "node",
|
|
11
|
+
"arguments": [
|
|
12
|
+
"test/conformance.mjs"
|
|
13
|
+
],
|
|
14
|
+
"workingDirectory": ".",
|
|
15
|
+
"timeoutMs": 60000
|
|
16
|
+
}
|
|
17
|
+
}
|