@assemblyline-agents/slack 5.0.10 → 6.0.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
@@ -2,12 +2,10 @@
2
2
 
3
3
  Official Assembly Line Slack channel plugin.
4
4
 
5
- It exports `defineSlackChannel(options)`, which receives Slack Events API callbacks over HTTP (with request-signature verification) and delivers agent replies through the Slack Web API, including file uploads and recent-activity context. The package also registers a Slack connection plugin (`defineSlackConnection`). A single installation requires `SLACK_SIGNING_SECRET`, `SLACK_BOT_TOKEN`, and the default bot scopes including `files:read` and `files:write`.
6
-
7
- Channel-aware authored tools may call the host-owned `slack_api` operation for
8
- the reviewed `chat.postMessage` and `conversations.open` methods. The runtime
9
- keeps the bot token out of tool input and returns only the destination and
10
- provider message identifiers; arbitrary Slack Web API methods are rejected.
5
+ The `slack` channel profile verifies Events API requests and delivers replies,
6
+ files, and recent-activity context through the Slack Web API. A single
7
+ installation requires `SLACK_SIGNING_SECRET`, `SLACK_BOT_TOKEN`, and the
8
+ documented bot scopes.
11
9
 
12
10
  Inbound message text is decoded from Slack's mrkdwn transport form before it
13
11
  reaches the agent: auto-linked URLs (`<https://…>`, `<https://…|label>`)
@@ -17,8 +15,56 @@ pairing packets arrives exactly as the user typed it. Mention and command
17
15
  sequences (`<@…>`, `<#…>`, `<!…>`) pass through unchanged.
18
16
 
19
17
  Inbound files preserve their Slack media type and attachment classification.
20
- Voice clips are therefore routed through any configured gateway media processor,
21
- such as `openRouterAudioTranscription()`, before the model receives the turn.
18
+ Voice clips use the Slack channel profile's default OpenRouter transcription
19
+ before the model receives the turn. Set `channels.slack.audio` to `openai` or
20
+ `disabled` only when the agent needs a different policy.
21
+
22
+ Selecting `channels: [slack]` also activates the plugin-owned
23
+ `slack_retrieve_file` tool. The tool retrieves a private Slack file by id into
24
+ `/workspace/slack-files` for analysis, root `deliver_artifact` delivery, or a
25
+ subagent `handoff_artifact`. It enforces a 50 MB limit and uses the credentials
26
+ for the event's workspace.
27
+
28
+ Authored tools can use two typed, host-owned channel operations without gaining
29
+ access to a raw Slack Web API method:
30
+
31
+ - `resolve_dm_by_email` looks up a workspace member and opens or resumes the
32
+ bot's DM with that person. It requires only `users:read`, `users:read.email`,
33
+ and `im:write`.
34
+ - `post_message` posts standard Markdown to a channel or DM. It requires only
35
+ `chat:write`.
36
+
37
+ Use `callSlackChannelOperation` to keep operation names, inputs, and results
38
+ typed:
39
+
40
+ ```ts
41
+ import { callSlackChannelOperation } from "@assemblyline-agents/slack";
42
+
43
+ const destination = await callSlackChannelOperation(ctx, "resolve_dm_by_email", {
44
+ email: ownerEmail,
45
+ teamId
46
+ });
47
+
48
+ await callSlackChannelOperation(ctx, "post_message", {
49
+ channel: destination.channelId,
50
+ teamId,
51
+ text: "## Review ready\n\nThe memo is ready for review.",
52
+ idempotencyKey: ctx.idempotencyKey(`memo-ready:${memoId}`),
53
+ unfurlLinks: false,
54
+ unfurlMedia: false
55
+ });
56
+ ```
57
+
58
+ Both operations select the existing single- or multi-workspace bot credential.
59
+ `post_message` uses the same 12,000-character Markdown splitting, broadcast
60
+ neutralization, retry policy, and phase-aware errors as normal Slack delivery.
61
+ Each chunk receives a stable Slack `client_msg_id` derived from the caller's
62
+ idempotency key, so a retry does not duplicate a successfully accepted chunk.
63
+ The operation allowlist and per-operation input validation prevent authored
64
+ tools from passing arbitrary Slack API methods or payload fields.
65
+
66
+ The optional Slack MCP connection remains an explicit `capabilities: [slack]`
67
+ selection.
22
68
 
23
69
  Outbound replies use Slack's standard Markdown, so headings, bold text, labeled
24
70
  links, lists, tables, and code from an agent render natively without
@@ -35,8 +81,10 @@ neutralizes broadcast commands: `<!here>`, `<!channel>`, and `<!everyone>`
35
81
  input therefore cannot ping the whole channel; links and user mentions in
36
82
  outbound text keep working.
37
83
 
38
- File delivery is explicit and durable. An agent calls `deliver_artifact` once
39
- for every completed `/workspace` file it needs to attach. The connector posts
84
+ File delivery is explicit and durable. A root agent calls `deliver_artifact`
85
+ once for every completed `/workspace` or `/files/handoffs` file it needs to
86
+ attach. A subagent calls `handoff_artifact` instead; the root then decides
87
+ whether to attach the returned file. The connector posts
40
88
  the final response together with all selected files and reports success only
41
89
  when Slack confirms every file id. Upload or completion failures leave the
42
90
  combined delivery unsent for that attempt and the durable queue retries it. If
@@ -83,9 +131,10 @@ callbacks may omit that ID, so only that callback shape is checked against the
83
131
  configured signing-secret set; team-identified events remain pinned to their
84
132
  mapped secret.
85
133
 
86
- Slack turns include a provider-scoped user principal by default. Map that
87
- sender to a canonical company identity before agent hooks run when roles,
88
- memory, or connection grants must follow the employee across channels:
134
+ Slack turns include a provider-scoped user principal by default. A custom local
135
+ channel plugin can wrap `defineSlackChannel` to map the sender to a canonical
136
+ company identity before hooks run. This callback is plugin code, not an
137
+ `agent.md` option:
89
138
 
90
139
  ```ts
91
140
  export default defineSlackChannel({
@@ -104,14 +153,14 @@ export default defineSlackChannel({
104
153
 
105
154
  Slack also reports each surface's privacy. DMs and assistant threads are
106
155
  private to the authenticated principal; channel threads are shared. When the
107
- agent opts into `audienceIsolation: true` in `agent.ts`, the runtime uses that
156
+ agent opts into `audienceIsolation: true` in `agent.md`, the runtime uses that
108
157
  boundary for memory, history, personal connections, subagents, schedules, and
109
158
  delivery, so private DM context is unavailable in a channel or another user's
110
159
  DM. Without the opt-in (the default), every surface is trusted and the signal
111
160
  has no effect.
112
161
 
113
- Under isolation, a personal Slack where every channel belongs to one trusted
114
- owner can still mark every surface private:
162
+ Under isolation, a custom Slack implementation for a personal workspace can
163
+ mark every surface private:
115
164
 
116
165
  ```ts
117
166
  export default defineSlackChannel({
@@ -121,24 +170,17 @@ export default defineSlackChannel({
121
170
 
122
171
  ## Install
123
172
 
124
- ```sh
125
- npm install @assemblyline-agents/slack
126
- ```
127
-
128
- With the Assembly Line CLI, add the channel instead:
129
-
130
173
  ```sh
131
174
  assembly-line add slack /path/to/agent
132
175
  ```
133
176
 
134
- This creates both `channels/slack.ts` and `slack-app-manifest.json`. The latter
135
- comes from the package's reusable
136
- [`templates/slack-app-manifest.json`](templates/slack-app-manifest.json) and
137
- contains the bot scopes and `app_mention`/`message.im` event subscriptions
138
- expected by the connector.
177
+ The command installs the package and selects `channels: [slack]` in `agent.md`.
178
+ It does not create a root channel file. The reusable
179
+ [`templates/slack-app-manifest.json`](templates/slack-app-manifest.json)
180
+ contains the expected bot scopes and event subscriptions.
139
181
 
140
- 1. Replace `https://your-agent.example.com/slack/events` in the generated
141
- manifest with the deployed agent URL.
182
+ 1. Copy the package template and replace
183
+ `https://your-agent.example.com/slack/events` with the deployed agent URL.
142
184
  2. In Slack's app dashboard, create an app **from an app manifest** and paste or
143
185
  upload the JSON.
144
186
  3. Install the app to the workspace.
@@ -146,10 +188,11 @@ expected by the connector.
146
188
  `SLACK_BOT_TOKEN` from **OAuth & Permissions** in the agent environment.
147
189
  5. Verify the installation with `assembly-line channels check /path/to/agent`.
148
190
 
149
- The loading indicator only needs `chat:write`. The default manifest also
150
- includes optional `groups:history` so private-channel thread context works
151
- without another reinstall. Remove `groups:history` if the bot will never be
152
- invited to private channels.
191
+ The loading indicator and `post_message` operation need `chat:write`.
192
+ `resolve_dm_by_email` needs `users:read`, `users:read.email`, and `im:write`.
193
+ The default manifest also includes optional `groups:history` so private-channel
194
+ thread context works without another reinstall. Remove `groups:history` if the
195
+ bot will never be invited to private channels.
153
196
 
154
197
  ## Part of Assembly Line
155
198
 
@@ -0,0 +1,43 @@
1
+ {
2
+ "kind": "slack",
3
+ "role": "connection",
4
+ "packageName": "@assemblyline-agents/slack",
5
+ "helper": "defineSlackConnection",
6
+ "protocol": "mcp",
7
+ "transport": "http",
8
+ "provider": "slack",
9
+ "description": "Slack workspace search, channels, messages, files, and users through Slack MCP.",
10
+ "defaultUrl": "https://mcp.slack.com/mcp",
11
+ "urlEnv": "SLACK_MCP_URL",
12
+ "tokenEnv": "SLACK_MCP_TOKEN",
13
+ "tokenRequired": true,
14
+ "capabilities": [
15
+ "mcp",
16
+ "live-tools",
17
+ "streamable-http"
18
+ ],
19
+ "scopes": [],
20
+ "subject": "workspace",
21
+ "readToolPatterns": [
22
+ "regex:(^|[-_])(search|read|list|fetch|get|retrieve|query|lookup|find|describe|details?|discover)([-_]|$)"
23
+ ],
24
+ "writeToolPatterns": [
25
+ "regex:(^|[-_])(add|append|archive|assign|book|buy|cancel|checkout|close|complete|create|delete|draft|duplicate|edit|execute|insert|invite|kick|link|merge|modify|move|mutate|open|pay|post|publish|purchase|push|react|reaction|remove|rename|reply|reserve|resolve|reopen|run|schedule|send|set|share|submit|transfer|transition|unassign|unlink|update|upload|write)([-_]|$)"
26
+ ],
27
+ "setup": [
28
+ {
29
+ "kind": "connection",
30
+ "name": "slack-developer-configuration",
31
+ "required": true,
32
+ "message": "Configure Slack credentials, OAuth application, or MCP bridge for this deployment."
33
+ }
34
+ ],
35
+ "requiredConfig": [],
36
+ "optionalConfig": [
37
+ "SLACK_MCP_URL"
38
+ ],
39
+ "requiredCredentials": [
40
+ "SLACK_MCP_TOKEN"
41
+ ],
42
+ "optionalCredentials": []
43
+ }
@@ -0,0 +1,94 @@
1
+ module.exports = {
2
+ "channels": {
3
+ "slack": {
4
+ "definition": "@assemblyline-agents/slack",
5
+ "factory": "defineSlackChannel",
6
+ "transport": "http",
7
+ "route": "/slack/events",
8
+ "methods": [
9
+ "POST"
10
+ ],
11
+ "requiredCredentials": [
12
+ "SLACK_SIGNING_SECRET",
13
+ "SLACK_BOT_TOKEN"
14
+ ],
15
+ "optionalConfig": [
16
+ "SLACK_BOT_USER_ID",
17
+ "SLACK_ASSISTANT_ENABLED"
18
+ ],
19
+ "optionalCredentials": [
20
+ "SLACK_WORKSPACE_CREDENTIALS_JSON"
21
+ ],
22
+ "defaultAudio": "openrouter",
23
+ "configSchema": {
24
+ "type": "object",
25
+ "additionalProperties": false
26
+ },
27
+ "metadata": {
28
+ "provider": "slack",
29
+ "ingress": {
30
+ "requiredSecretEnv": [
31
+ [
32
+ "SLACK_SIGNING_SECRET"
33
+ ]
34
+ ]
35
+ },
36
+ "installation": {
37
+ "requiredBotScopes": [
38
+ "app_mentions:read",
39
+ "channels:history",
40
+ "chat:write",
41
+ "files:read",
42
+ "files:write",
43
+ "im:history",
44
+ "im:write",
45
+ "users:read",
46
+ "users:read.email"
47
+ ],
48
+ "optionalBotScopes": [
49
+ "assistant:write",
50
+ "groups:history"
51
+ ],
52
+ "channelOperations": {
53
+ "resolve_dm_by_email": {
54
+ "requiredBotScopes": ["users:read", "users:read.email", "im:write"]
55
+ },
56
+ "post_message": {
57
+ "requiredBotScopes": ["chat:write"]
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ },
64
+ "connections": {
65
+ "slack": {
66
+ "contract": "./ai.assemblyline/connections/slack.json",
67
+ "definition": "@assemblyline-agents/slack",
68
+ "factory": "defineSlackConnection"
69
+ }
70
+ },
71
+ "tools": {
72
+ "slack_retrieve_file": {
73
+ "definition": "@assemblyline-agents/slack",
74
+ "factory": "defineSlackRetrieveFileTool",
75
+ "description": "Retrieve an existing private Slack file by id into the run sandbox.",
76
+ "inputSchema": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "properties": {
80
+ "fileId": { "type": "string" },
81
+ "filename": { "type": "string" },
82
+ "teamId": { "type": "string" }
83
+ },
84
+ "required": ["fileId"]
85
+ },
86
+ "capability": {
87
+ "visibility": "deferred",
88
+ "execution": "direct",
89
+ "namespace": "slack",
90
+ "tags": ["slack", "file", "attachment", "download"]
91
+ }
92
+ }
93
+ }
94
+ };
@@ -0,0 +1,51 @@
1
+ import type { JsonObject, ToolExecutionContext } from "@assemblyline-agents/core";
2
+ import type { ChannelContext, ChannelTurn } from "@assemblyline-agents/runtime";
3
+ export declare const SLACK_CHANNEL_OPERATIONS: readonly ["resolve_dm_by_email", "post_message"];
4
+ export type SlackChannelOperation = typeof SLACK_CHANNEL_OPERATIONS[number];
5
+ /** Exact bot-token scopes used by each semantic operation. */
6
+ export declare const SLACK_CHANNEL_OPERATION_REQUIRED_BOT_SCOPES: {
7
+ readonly resolve_dm_by_email: readonly ["users:read", "users:read.email", "im:write"];
8
+ readonly post_message: readonly ["chat:write"];
9
+ };
10
+ export interface SlackResolveDmByEmailInput extends JsonObject {
11
+ email: string;
12
+ teamId?: string;
13
+ }
14
+ export interface SlackResolveDmByEmailResult extends JsonObject {
15
+ ok: true;
16
+ userId: string;
17
+ channelId: string;
18
+ teamId?: string;
19
+ }
20
+ export interface SlackPostMessageInput extends JsonObject {
21
+ channel: string;
22
+ text: string;
23
+ idempotencyKey: string;
24
+ teamId?: string;
25
+ threadTs?: string;
26
+ unfurlLinks?: boolean;
27
+ unfurlMedia?: boolean;
28
+ }
29
+ export interface SlackPostMessageResult extends JsonObject {
30
+ ok: true;
31
+ provider: "slack";
32
+ mode: "chat.postMessage";
33
+ channel: string;
34
+ threadTs?: string;
35
+ slackMessageTs: string[];
36
+ messageCount: number;
37
+ idempotencyKey: string;
38
+ }
39
+ export interface SlackChannelOperationInputs {
40
+ resolve_dm_by_email: SlackResolveDmByEmailInput;
41
+ post_message: SlackPostMessageInput;
42
+ }
43
+ export interface SlackChannelOperationResults {
44
+ resolve_dm_by_email: SlackResolveDmByEmailResult;
45
+ post_message: SlackPostMessageResult;
46
+ }
47
+ /** Typed authored-tool entrypoint for the current Slack channel capability. */
48
+ export declare function callSlackChannelOperation<Operation extends SlackChannelOperation>(ctx: Pick<ToolExecutionContext, "channel">, operation: Operation, input: SlackChannelOperationInputs[Operation]): Promise<SlackChannelOperationResults[Operation]>;
49
+ /** Runtime dispatcher. Only the named semantic operations can reach Slack. */
50
+ export declare function executeSlackChannelOperation(operation: string, input: JsonObject, turn: ChannelTurn, ctx: ChannelContext): Promise<JsonObject>;
51
+ //# sourceMappingURL=channel-operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-operations.d.ts","sourceRoot":"","sources":["../src/channel-operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAchF,eAAO,MAAM,wBAAwB,kDAAmD,CAAC;AACzF,MAAM,MAAM,qBAAqB,GAAG,OAAO,wBAAwB,CAAC,MAAM,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,2CAA2C;;;CAGa,CAAC;AAEtE,MAAM,WAAW,0BAA2B,SAAQ,UAAU;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,2BAA4B,SAAQ,UAAU;IAC7D,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAsB,SAAQ,UAAU;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,sBAAuB,SAAQ,UAAU;IACxD,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,mBAAmB,EAAE,0BAA0B,CAAC;IAChD,YAAY,EAAE,qBAAqB,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,mBAAmB,EAAE,2BAA2B,CAAC;IACjD,YAAY,EAAE,sBAAsB,CAAC;CACtC;AAED,+EAA+E;AAC/E,wBAAsB,yBAAyB,CAAC,SAAS,SAAS,qBAAqB,EACrF,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC1C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,2BAA2B,CAAC,SAAS,CAAC,GAC5C,OAAO,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CAKlD;AAED,8EAA8E;AAC9E,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CAQrB"}
@@ -0,0 +1,145 @@
1
+ import { postSlackMarkdown, slackPhaseError, slackTextDeliveryMetadata } from "./message-delivery.js";
2
+ import { slackApi } from "./slack-api.js";
3
+ const SLACK_CONVERSATION_ID = /^[CDG][A-Z0-9]+$/u;
4
+ const SLACK_DM_ID = /^D[A-Z0-9]+$/u;
5
+ const SLACK_TEAM_ID = /^T[A-Z0-9]+$/u;
6
+ const SLACK_TIMESTAMP = /^\d+\.\d+$/u;
7
+ const SLACK_USER_ID = /^[UW][A-Z0-9]+$/u;
8
+ export const SLACK_CHANNEL_OPERATIONS = ["resolve_dm_by_email", "post_message"];
9
+ /** Exact bot-token scopes used by each semantic operation. */
10
+ export const SLACK_CHANNEL_OPERATION_REQUIRED_BOT_SCOPES = {
11
+ resolve_dm_by_email: ["users:read", "users:read.email", "im:write"],
12
+ post_message: ["chat:write"]
13
+ };
14
+ /** Typed authored-tool entrypoint for the current Slack channel capability. */
15
+ export async function callSlackChannelOperation(ctx, operation, input) {
16
+ if (!ctx.channel?.perform) {
17
+ throw new Error("Slack operations require a host-owned Slack channel capability.");
18
+ }
19
+ return await ctx.channel.perform(operation, input);
20
+ }
21
+ /** Runtime dispatcher. Only the named semantic operations can reach Slack. */
22
+ export async function executeSlackChannelOperation(operation, input, turn, ctx) {
23
+ if (operation === "resolve_dm_by_email") {
24
+ return resolveSlackDmByEmail(validateResolveDmInput(input), turn, ctx);
25
+ }
26
+ if (operation === "post_message") {
27
+ return postSlackMessage(validatePostMessageInput(input), turn, ctx);
28
+ }
29
+ throw new Error(`Unsupported Slack channel operation: ${operation}.`);
30
+ }
31
+ async function resolveSlackDmByEmail(input, turn, ctx) {
32
+ const teamId = input.teamId ?? slackTeamIdForTurn(turn);
33
+ let lookup;
34
+ try {
35
+ lookup = await slackApi(ctx, "users.lookupByEmail", { email: input.email }, teamId);
36
+ }
37
+ catch (error) {
38
+ throw slackPhaseError(`Slack direct-message user lookup failed for ${input.email}`, error);
39
+ }
40
+ const userId = stringValue(recordValue(lookup.user)?.id);
41
+ if (!userId || !SLACK_USER_ID.test(userId)) {
42
+ throw new Error(`Slack direct-message user lookup returned no user id for ${input.email}.`);
43
+ }
44
+ let opened;
45
+ try {
46
+ opened = await slackApi(ctx, "conversations.open", { users: userId }, teamId);
47
+ }
48
+ catch (error) {
49
+ throw slackPhaseError(`Slack direct-message open failed for user ${userId}`, error);
50
+ }
51
+ const channelId = stringValue(recordValue(opened.channel)?.id);
52
+ if (!channelId || !SLACK_DM_ID.test(channelId)) {
53
+ throw new Error(`Slack direct-message open returned no valid channel id for user ${userId}.`);
54
+ }
55
+ return {
56
+ ok: true,
57
+ userId,
58
+ channelId,
59
+ ...(teamId ? { teamId } : {})
60
+ };
61
+ }
62
+ async function postSlackMessage(input, turn, ctx) {
63
+ const teamId = input.teamId ?? slackTeamIdForTurn(turn);
64
+ const results = await postSlackMarkdown(input.text, input.channel, input.threadTs, ctx, teamId, {
65
+ idempotencyKey: input.idempotencyKey,
66
+ ...(input.unfurlLinks !== undefined ? { unfurlLinks: input.unfurlLinks } : {}),
67
+ ...(input.unfurlMedia !== undefined ? { unfurlMedia: input.unfurlMedia } : {})
68
+ });
69
+ return {
70
+ ...slackTextDeliveryMetadata(input.channel, input.threadTs, input.idempotencyKey, results),
71
+ ok: true
72
+ };
73
+ }
74
+ function validateResolveDmInput(input) {
75
+ assertAllowedKeys(input, new Set(["email", "teamId"]), "resolve_dm_by_email");
76
+ const email = requiredString(input.email, "Slack resolve_dm_by_email requires email.");
77
+ if (!email.includes("@"))
78
+ throw new Error("Slack resolve_dm_by_email requires a valid email address.");
79
+ const teamId = optionalTeamId(input.teamId);
80
+ return { email, ...(teamId ? { teamId } : {}) };
81
+ }
82
+ function validatePostMessageInput(input) {
83
+ assertAllowedKeys(input, new Set(["channel", "text", "idempotencyKey", "teamId", "threadTs", "unfurlLinks", "unfurlMedia"]), "post_message");
84
+ const channel = requiredString(input.channel, "Slack post_message requires channel.").toUpperCase();
85
+ if (!SLACK_CONVERSATION_ID.test(channel)) {
86
+ throw new Error("Slack post_message requires a channel id beginning with C, D, or G.");
87
+ }
88
+ const text = requiredString(input.text, "Slack post_message requires non-empty text.", false);
89
+ const idempotencyKey = requiredString(input.idempotencyKey, "Slack post_message requires idempotencyKey.");
90
+ const teamId = optionalTeamId(input.teamId);
91
+ const threadTs = optionalString(input.threadTs);
92
+ if (threadTs && !SLACK_TIMESTAMP.test(threadTs)) {
93
+ throw new Error("Slack post_message threadTs must be a Slack timestamp.");
94
+ }
95
+ if (input.unfurlLinks !== undefined && typeof input.unfurlLinks !== "boolean") {
96
+ throw new Error("Slack post_message unfurlLinks must be a boolean.");
97
+ }
98
+ if (input.unfurlMedia !== undefined && typeof input.unfurlMedia !== "boolean") {
99
+ throw new Error("Slack post_message unfurlMedia must be a boolean.");
100
+ }
101
+ return {
102
+ channel,
103
+ text,
104
+ idempotencyKey,
105
+ ...(teamId ? { teamId } : {}),
106
+ ...(threadTs ? { threadTs } : {}),
107
+ ...(typeof input.unfurlLinks === "boolean" ? { unfurlLinks: input.unfurlLinks } : {}),
108
+ ...(typeof input.unfurlMedia === "boolean" ? { unfurlMedia: input.unfurlMedia } : {})
109
+ };
110
+ }
111
+ function slackTeamIdForTurn(turn) {
112
+ const slack = recordValue(turn.metadata?.slack);
113
+ const delivery = recordValue(turn.delivery);
114
+ return stringValue(slack?.teamId) ?? stringValue(delivery?.teamId);
115
+ }
116
+ function optionalTeamId(value) {
117
+ const teamId = optionalString(value)?.toUpperCase();
118
+ if (teamId && !SLACK_TEAM_ID.test(teamId)) {
119
+ throw new Error("Slack teamId must begin with T.");
120
+ }
121
+ return teamId;
122
+ }
123
+ function requiredString(value, message, trim = true) {
124
+ if (typeof value !== "string" || !value.trim())
125
+ throw new Error(message);
126
+ return trim ? value.trim() : value;
127
+ }
128
+ function optionalString(value) {
129
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
130
+ }
131
+ function assertAllowedKeys(input, allowed, operation) {
132
+ const unsupported = Object.keys(input).filter((key) => !allowed.has(key));
133
+ if (unsupported.length > 0) {
134
+ throw new Error(`Slack ${operation} does not accept: ${unsupported.join(", ")}.`);
135
+ }
136
+ }
137
+ function recordValue(value) {
138
+ return typeof value === "object" && value !== null && !Array.isArray(value)
139
+ ? value
140
+ : undefined;
141
+ }
142
+ function stringValue(value) {
143
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
144
+ }
145
+ //# sourceMappingURL=channel-operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-operations.js","sourceRoot":"","sources":["../src/channel-operations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { JsonObject } from "@assemblyline-agents/core";
2
+ import type { ChannelContext, ChannelTurn } from "@assemblyline-agents/runtime";
3
+ /** Fetch a private Slack file inside the trusted channel boundary. */
4
+ export declare function retrieveSlackFile(input: JsonObject, turn: ChannelTurn, ctx: ChannelContext): Promise<JsonObject>;
5
+ //# sourceMappingURL=file-retrieval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-retrieval.d.ts","sourceRoot":"","sources":["../src/file-retrieval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAMhF,sEAAsE;AACtE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAmCtH"}
@@ -0,0 +1,77 @@
1
+ import { slackWorkspaceCredentials } from "./workspace-credentials.js";
2
+ const MAX_FILE_BYTES = 50 * 1024 * 1024;
3
+ const SLACK_FILE_ID = /^F[A-Z0-9]+$/u;
4
+ /** Fetch a private Slack file inside the trusted channel boundary. */
5
+ export async function retrieveSlackFile(input, turn, ctx) {
6
+ const fileId = stringValue(input.fileId)?.toUpperCase();
7
+ if (!fileId || !SLACK_FILE_ID.test(fileId))
8
+ throw new Error("Slack file retrieval requires a valid file id beginning with F.");
9
+ const teamId = stringValue(input.teamId) ?? slackTeamId(turn.metadata);
10
+ const token = slackWorkspaceCredentials(ctx.env, teamId).botToken?.trim();
11
+ if (!token)
12
+ return failure(fileId, "slack_file_read_not_configured", "No Slack file-read credential is configured for this workspace.");
13
+ const infoResponse = await ctx.fetch(`https://slack.com/api/files.info?file=${encodeURIComponent(fileId)}`, {
14
+ headers: { authorization: `Bearer ${token}` },
15
+ signal: AbortSignal.timeout(30_000)
16
+ });
17
+ const info = await infoResponse.json().catch(() => ({}));
18
+ if (!infoResponse.ok || info.ok !== true || !isRecord(info.file)) {
19
+ const detail = typeof info.error === "string" ? info.error : `HTTP ${infoResponse.status}`;
20
+ return failure(fileId, "slack_file_lookup_failed", `Slack could not resolve ${fileId}: ${detail}.`);
21
+ }
22
+ const file = info.file;
23
+ const downloadUrl = firstString(file.url_private_download, file.url_private);
24
+ if (!downloadUrl || !isAllowedSlackFileUrl(downloadUrl))
25
+ return failure(fileId, "slack_file_download_url_missing", "Slack returned no allowed private download URL.");
26
+ const announcedSize = finitePositiveNumber(file.size);
27
+ if (announcedSize !== undefined && announcedSize > MAX_FILE_BYTES)
28
+ throw new Error(`Slack file ${fileId} is above the 50 MB retrieval limit.`);
29
+ const download = await ctx.fetch(downloadUrl, {
30
+ headers: { authorization: `Bearer ${token}` },
31
+ redirect: "error",
32
+ signal: AbortSignal.timeout(90_000)
33
+ });
34
+ if (!download.ok)
35
+ return failure(fileId, "slack_file_download_failed", `Slack file download failed with HTTP ${download.status}.`);
36
+ const bytes = await boundedBytes(download, MAX_FILE_BYTES);
37
+ return {
38
+ ok: true,
39
+ fileId,
40
+ filename: safeFilename(stringValue(input.filename)) ?? safeFilename(firstString(file.name, file.title)) ?? `${fileId}.bin`,
41
+ bytesBase64: Buffer.from(bytes).toString("base64"),
42
+ sizeBytes: bytes.byteLength,
43
+ contentType: download.headers.get("content-type") ?? "application/octet-stream"
44
+ };
45
+ }
46
+ function failure(fileId, error, message) { return { ok: false, fileId, error, message }; }
47
+ function stringValue(value) { return typeof value === "string" && value.trim() ? value.trim() : undefined; }
48
+ function slackTeamId(metadata) { const slack = isRecord(metadata?.slack) ? metadata.slack : undefined; return stringValue(slack?.teamId); }
49
+ function firstString(...values) { return values.find((value) => typeof value === "string" && value.trim() !== "")?.trim(); }
50
+ function finitePositiveNumber(value) { return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined; }
51
+ function isRecord(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }
52
+ function safeFilename(value) {
53
+ if (!value)
54
+ return undefined;
55
+ const cleaned = value.trim().replace(/[\\/\u0000-\u001f\u007f]+/gu, "-").replace(/[^a-z0-9._() -]+/giu, "-").replace(/\s+/gu, " ").replace(/^-+|-+$/gu, "").slice(0, 160);
56
+ return cleaned && cleaned !== "." && cleaned !== ".." ? cleaned : undefined;
57
+ }
58
+ function isAllowedSlackFileUrl(value) {
59
+ try {
60
+ const url = new URL(value);
61
+ const hostname = url.hostname.toLowerCase();
62
+ return url.protocol === "https:" && (hostname === "files.slack.com" || hostname.endsWith(".slack.com"));
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ async function boundedBytes(response, maxBytes) {
69
+ const declared = Number(response.headers.get("content-length"));
70
+ if (Number.isFinite(declared) && declared > maxBytes)
71
+ throw new Error("The Slack file exceeded the 50 MB retrieval limit.");
72
+ const bytes = new Uint8Array(await response.arrayBuffer());
73
+ if (bytes.byteLength > maxBytes)
74
+ throw new Error("The Slack file exceeded the 50 MB retrieval limit.");
75
+ return bytes;
76
+ }
77
+ //# sourceMappingURL=file-retrieval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-retrieval.js","sourceRoot":"","sources":["../src/file-retrieval.ts"],"names":[],"mappings":"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"}