@fastagent-sh/fastagent 0.28.0 → 0.30.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 +10 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +3 -1
- package/dist/channels/feishu/feishu-api.js +4 -0
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.d.ts +2 -0
- package/dist/channels/feishu/register-app.js +4 -6
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/wait-health.js +1 -1
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -310
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -67
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +63 -24
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +164 -0
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/program.js +339 -108
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.js +50 -6
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +26 -2
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -11
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +80 -39
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +18 -43
- package/dist/deploy/docker/run.js +57 -79
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -22
- package/dist/deploy/fly/run.js +25 -44
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -7
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.d.ts +3 -0
- package/dist/harnesses/pi-durable/mcp.js +32 -0
- package/dist/harnesses/pi-durable/open.d.ts +36 -38
- package/dist/harnesses/pi-durable/open.js +97 -150
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +95 -15
- package/dist/harnesses/pi-durable/serve.js +151 -59
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +8 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/locked-file.d.ts +2 -2
- package/dist/locked-file.js +8 -3
- package/dist/mcp/auth.d.ts +22 -0
- package/dist/mcp/auth.js +180 -0
- package/dist/mcp/client.d.ts +15 -0
- package/dist/mcp/client.js +179 -0
- package/dist/mcp/config.d.ts +86 -0
- package/dist/mcp/config.js +225 -0
- package/dist/mcp/grants.d.ts +4 -0
- package/dist/mcp/grants.js +132 -0
- package/dist/mcp/host.d.ts +19 -0
- package/dist/mcp/host.js +128 -0
- package/dist/mcp/network.d.ts +12 -0
- package/dist/mcp/network.js +150 -0
- package/dist/mcp/sign-in.d.ts +2 -0
- package/dist/mcp/sign-in.js +204 -0
- package/dist/mcp-api.d.ts +107 -0
- package/dist/mcp-api.js +10 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/setup-page.d.ts +51 -0
- package/dist/setup-page.js +149 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +12 -6
- package/dist/value-file.js +36 -25
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +8 -6
- package/dist/channels/lark/onboard.d.ts +0 -36
- package/dist/channels/lark/onboard.js +0 -56
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -201
- package/dist/cli/add-feishu.d.ts +0 -50
- package/dist/cli/add-feishu.js +0 -367
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripEdges } from "../../strip-edges.js";
|
|
1
2
|
// One posture, as on Feishu: the agent hears the channels it is in (participant-model.md §2), so the history scopes
|
|
2
3
|
// and their message events are part of every app. `users:read` names the people in the history a turn reads.
|
|
3
4
|
const SLACK_BOT_SCOPES = [
|
|
@@ -22,7 +23,7 @@ const SLACK_BOT_EVENTS = [
|
|
|
22
23
|
"message.groups",
|
|
23
24
|
"message.mpim",
|
|
24
25
|
];
|
|
25
|
-
|
|
26
|
+
function slackBotScopes() {
|
|
26
27
|
return [...SLACK_BOT_SCOPES].sort();
|
|
27
28
|
}
|
|
28
29
|
function slackBotEvents() {
|
|
@@ -30,11 +31,7 @@ function slackBotEvents() {
|
|
|
30
31
|
}
|
|
31
32
|
/** Slack's bot display name is more restrictive than the app name. */
|
|
32
33
|
function slackBotDisplayName(name) {
|
|
33
|
-
const normalized = name
|
|
34
|
-
.toLowerCase()
|
|
35
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
36
|
-
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "")
|
|
37
|
-
.slice(0, 80);
|
|
34
|
+
const normalized = stripEdges(name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-"), (char) => !/[a-z0-9]/.test(char)).slice(0, 80);
|
|
38
35
|
return normalized || "fastagent";
|
|
39
36
|
}
|
|
40
37
|
export function buildSlackManifest(input) {
|
|
@@ -62,22 +59,21 @@ export function buildSlackManifest(input) {
|
|
|
62
59
|
},
|
|
63
60
|
oauth_config: {
|
|
64
61
|
scopes: { bot: slackBotScopes() },
|
|
65
|
-
...(input.redirectUrl ? { redirect_urls: [input.redirectUrl] } : {}),
|
|
66
62
|
},
|
|
67
63
|
settings: {
|
|
68
|
-
|
|
69
|
-
? {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
bot_events: slackBotEvents(),
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
: {}),
|
|
64
|
+
event_subscriptions: {
|
|
65
|
+
...(input.ingress === "webhook" ? { request_url: input.requestUrl } : {}),
|
|
66
|
+
bot_events: slackBotEvents(),
|
|
67
|
+
},
|
|
76
68
|
org_deploy_enabled: false,
|
|
77
|
-
socket_mode_enabled:
|
|
69
|
+
socket_mode_enabled: input.ingress === "connection",
|
|
78
70
|
// Off, and Slack makes the alternative permanent: an app with rotation on can never turn it off
|
|
79
71
|
// (`cannot_disable_once_enabled`, verified against the API).
|
|
80
72
|
token_rotation_enabled: false,
|
|
81
73
|
},
|
|
82
74
|
};
|
|
83
75
|
}
|
|
76
|
+
/** The page that creates an app from `manifest`, filled in: the person picks a workspace and clicks Create. */
|
|
77
|
+
export function slackManifestLink(manifest) {
|
|
78
|
+
return `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(JSON.stringify(manifest))}`;
|
|
79
|
+
}
|
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
import { needs } from "@fastagent-sh/fastagent";
|
|
2
|
-
import { slackChannel } from "@fastagent-sh/fastagent/slack";
|
|
1
|
+
import { byIngress, needs } from "@fastagent-sh/fastagent";
|
|
2
|
+
import { type SlackChannelOptions, slackChannel, slackSocketChannel } from "@fastagent-sh/fastagent/slack";
|
|
3
3
|
|
|
4
|
-
// Slack
|
|
5
|
-
//
|
|
6
|
-
// 2. Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history,
|
|
7
|
-
// files:read, files:write, reactions:write, channels:history, groups:history, mpim:history,
|
|
8
|
-
// and users:read.
|
|
9
|
-
// 3. Event Subscriptions: app_home_opened, app_context_changed, app_mention, message.im,
|
|
10
|
-
// message.channels, message.groups, and message.mpim.
|
|
11
|
-
// Set Request URL to https://<host>/slack.
|
|
12
|
-
// 4. Install the app (reinstall after changing scopes) and leave token rotation OFF: it cannot be
|
|
13
|
-
// turned off again, and this channel takes one long-lived Bot User OAuth Token.
|
|
4
|
+
// Slack channel. `fastagent channels setup slack` creates the app (from a manifest link that sets the scopes, the
|
|
5
|
+
// events and the Socket Mode switch this file needs) and keeps its credentials.
|
|
14
6
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
// How it receives is the instance's choice, `channels.slack.ingress` in its fastagent.json (webhook | connection).
|
|
8
|
+
// Unset, `fastagent dev` opens a Socket Mode connection OUT (no public URL, no tunnel), and everything else —
|
|
9
|
+
// `fastagent start`, every deployment — receives by webhook at POST /slack, the way a host that wakes on a request is
|
|
10
|
+
// reached. An app in Socket Mode receives over the socket ALONE, so an app serves one instance, and a deployment that
|
|
11
|
+
// receives by webhook has an app of its own.
|
|
12
|
+
//
|
|
13
|
+
// Leave token rotation OFF: it cannot be turned off again, and this channel takes one long-lived Bot User OAuth Token.
|
|
14
|
+
|
|
15
|
+
// Your policy, the same whichever way the channel receives.
|
|
16
|
+
const policy: Pick<SlackChannelOptions, "rendering" | "onError"> = {
|
|
17
|
+
// Slack Agent stream; its inline tool traces show each call's first argument and stay in the delivered message.
|
|
18
|
+
// "classic" settles into the answer alone (and gives up native streaming).
|
|
19
|
+
rendering: "native",
|
|
20
|
+
// Optional per-reply footer, if your policy requires one: aiDisclaimer: "AI-generated; verify important information.",
|
|
21
|
+
// welcome: "Custom first-run DM greeting", // sent once on first DM open; false disables (default: a generic greeting)
|
|
22
|
+
// reactionAck: false, // disable the 👀→✅ ack on the user's message (default on; needs reactions:write)
|
|
23
|
+
// An answer attaches to its question with a thread (Slack has no quote primitive), and that thread is
|
|
24
|
+
// one conversation, with a session of its own: see docs/design/participant-model.md.
|
|
25
|
+
// Dev/personal bot: surface raw errors. Remove this for a customer-facing bot; details remain in logs.
|
|
26
|
+
onError: (failed) => `⚠️ ${failed.details}`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Each way's `needs` declares the env vars it reads: an instance is asked only for its way's, and refused to serve
|
|
30
|
+
// while a required one is unset.
|
|
31
|
+
export default byIngress({
|
|
32
|
+
webhook: needs(["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], (secrets) =>
|
|
33
|
+
slackChannel({
|
|
34
|
+
botToken: secrets.SLACK_BOT_TOKEN, // Install App → Bot User OAuth Token (xoxb-…)
|
|
35
|
+
signingSecret: secrets.SLACK_SIGNING_SECRET, // Basic Information → Signing Secret
|
|
36
|
+
...policy,
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
39
|
+
connection: needs(["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"], (secrets) =>
|
|
40
|
+
slackSocketChannel({
|
|
41
|
+
botToken: secrets.SLACK_BOT_TOKEN,
|
|
42
|
+
appToken: secrets.SLACK_APP_TOKEN, // Basic Information → App-Level Tokens (xapp-…, connections:write)
|
|
43
|
+
...policy,
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { obj, opt, str } from "@earendil-works/pi-schema";
|
|
2
2
|
import { defineTool } from "@earendil-works/pi-durable";
|
|
3
3
|
import { needs } from "@fastagent-sh/fastagent";
|
|
4
4
|
import { slackTransport } from "@fastagent-sh/fastagent/slack";
|
|
@@ -22,13 +22,13 @@ export default needs(["SLACK_BOT_TOKEN"], ({ SLACK_BOT_TOKEN }) =>
|
|
|
22
22
|
"one of `text`/`path`. channelId/threadTs come from the [slack: …] context line in a chat turn; a " +
|
|
23
23
|
"scheduled or woken message has no context line, so name the destination in your instruction. A user ID " +
|
|
24
24
|
"(U…) as channelId messages that user's DM; a file upload needs the DM channel ID (D…) the text send reports.",
|
|
25
|
-
parameters:
|
|
26
|
-
channelId:
|
|
27
|
-
text:
|
|
28
|
-
path:
|
|
29
|
-
title:
|
|
30
|
-
initialComment:
|
|
31
|
-
threadTs:
|
|
25
|
+
parameters: obj({
|
|
26
|
+
channelId: str({ description: "target Slack channel ID, or a user ID (U…) to message that user's DM" }),
|
|
27
|
+
text: opt(str({ description: "standard Markdown message text" })),
|
|
28
|
+
path: opt(str({ description: "absolute path of a local file to upload" })),
|
|
29
|
+
title: opt(str({ description: "file title (file mode only)" })),
|
|
30
|
+
initialComment: opt(str({ description: "message posted with the file (file mode only)" })),
|
|
31
|
+
threadTs: opt(str({ description: "thread parent timestamp, if replying in a thread" })),
|
|
32
32
|
}),
|
|
33
33
|
execute: async ({ channelId, text, path, title, initialComment, threadTs }) => {
|
|
34
34
|
if ((text === undefined) === (path === undefined)) throw new Error("pass exactly one of `text` or `path`");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { obj, opt, str } from "@earendil-works/pi-schema";
|
|
2
2
|
import { defineTool } from "@earendil-works/pi-durable";
|
|
3
3
|
import { sessionIdOf } from "@fastagent-sh/fastagent";
|
|
4
4
|
import { slackThreads } from "@fastagent-sh/fastagent/slack";
|
|
@@ -14,9 +14,9 @@ export default defineTool({
|
|
|
14
14
|
"question here concerns something discussed in another thread, such as whether a problem raised there was " +
|
|
15
15
|
"resolved: the answer is often what a person wrote in the thread. Only this channel's threads can be read, and " +
|
|
16
16
|
"only in a channel turn.",
|
|
17
|
-
parameters:
|
|
18
|
-
threadId:
|
|
19
|
-
|
|
17
|
+
parameters: obj({
|
|
18
|
+
threadId: opt(
|
|
19
|
+
str({
|
|
20
20
|
description: "a thread id from the list (its first message's ts, like 1712345678.123456); omit it to list",
|
|
21
21
|
}),
|
|
22
22
|
),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Slack app's setup asks for (channels/setup.ts). The app is created from a manifest link the person opens, so
|
|
3
|
+
* its scopes, events and Socket Mode switch are what FastAgent needs; the values only they can read off its pages are
|
|
4
|
+
* pasted: the bot token, and for the way the app receives, its app-level token (Socket Mode) or its signing secret.
|
|
5
|
+
*/
|
|
6
|
+
import { type ChannelSetup } from "../setup.ts";
|
|
7
|
+
export declare const slackSetup: ChannelSetup;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Slack app's setup asks for (channels/setup.ts). The app is created from a manifest link the person opens, so
|
|
3
|
+
* its scopes, events and Socket Mode switch are what FastAgent needs; the values only they can read off its pages are
|
|
4
|
+
* pasted: the bot token, and for the way the app receives, its app-level token (Socket Mode) or its signing secret.
|
|
5
|
+
*/
|
|
6
|
+
import { SetupRefused, pasteOne } from "../setup.js";
|
|
7
|
+
import { buildSlackManifest, slackManifestLink } from "./manifest.js";
|
|
8
|
+
import { SlackApiError, createSlackApi } from "./slack-api.js";
|
|
9
|
+
import { TerminalSocketError, openSlackConnection } from "./socket.js";
|
|
10
|
+
const SLACK_API = "https://slack.com/api";
|
|
11
|
+
/** Stands in for the Request URL of an app that receives by webhook, until the person sets the real one. */
|
|
12
|
+
const PLACEHOLDER_REQUEST_URL = "https://example.invalid/slack";
|
|
13
|
+
export const slackSetup = ({ way, appName, publicUrl }) => {
|
|
14
|
+
const manifest = way === "connection"
|
|
15
|
+
? buildSlackManifest({ name: `FastAgent ${appName}`, ingress: "connection" })
|
|
16
|
+
: buildSlackManifest({
|
|
17
|
+
name: `FastAgent ${appName}`,
|
|
18
|
+
ingress: "webhook",
|
|
19
|
+
requestUrl: publicUrl === undefined ? PLACEHOLDER_REQUEST_URL : `${publicUrl}/slack`,
|
|
20
|
+
});
|
|
21
|
+
const values = [
|
|
22
|
+
pasteOne({
|
|
23
|
+
name: "SLACK_BOT_TOKEN",
|
|
24
|
+
label: "Bot User OAuth Token",
|
|
25
|
+
guide: {
|
|
26
|
+
instructions: "Create the app from this link (pick a workspace, then Create), then open Install App → Install to " +
|
|
27
|
+
"Workspace → Allow, and copy the Bot User OAuth Token.",
|
|
28
|
+
url: slackManifestLink(manifest),
|
|
29
|
+
},
|
|
30
|
+
async check(value) {
|
|
31
|
+
if (value.startsWith("xoxe.")) {
|
|
32
|
+
throw new SetupRefused("That is a rotating token, which FastAgent does not support.", {
|
|
33
|
+
fix: "Token rotation cannot be turned off once it is on: create the app again from the link, which leaves it off.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (!value.startsWith("xoxb-")) {
|
|
37
|
+
throw new SetupRefused("That is not a bot token.", {
|
|
38
|
+
fix: "The Bot User OAuth Token is under Install App, and starts with xoxb-.",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const api = createSlackApi({ botToken: value, baseUrl: SLACK_API });
|
|
43
|
+
const identity = await api.authTest();
|
|
44
|
+
return `bot in workspace ${identity.teamId ?? "(unknown)"}`;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof SlackApiError && error.status === 200) {
|
|
48
|
+
throw new SetupRefused("Slack does not accept this token.", {
|
|
49
|
+
fix: "Copy the Bot User OAuth Token again from Install App.",
|
|
50
|
+
why: error.message,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
...(way === "connection"
|
|
58
|
+
? [
|
|
59
|
+
pasteOne({
|
|
60
|
+
name: "SLACK_APP_TOKEN",
|
|
61
|
+
label: "App-Level Token",
|
|
62
|
+
guide: {
|
|
63
|
+
instructions: "Open Basic Information → App-Level Tokens → Generate Token and Scopes, add the scope " +
|
|
64
|
+
"connections:write, and copy the token.",
|
|
65
|
+
},
|
|
66
|
+
async check(value) {
|
|
67
|
+
if (!value.startsWith("xapp-")) {
|
|
68
|
+
throw new SetupRefused("That is not an app-level token.", {
|
|
69
|
+
fix: "It starts with xapp-, and is made under Basic Information → App-Level Tokens (not the bot token).",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await openSlackConnection(SLACK_API, value);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof TerminalSocketError) {
|
|
78
|
+
throw new SetupRefused("Slack does not accept this token.", {
|
|
79
|
+
fix: "Make sure it has the connections:write scope, and copy it again.",
|
|
80
|
+
why: error.message,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
]
|
|
88
|
+
: [
|
|
89
|
+
pasteOne({
|
|
90
|
+
name: "SLACK_SIGNING_SECRET",
|
|
91
|
+
label: "Signing Secret",
|
|
92
|
+
guide: {
|
|
93
|
+
instructions: "Open Basic Information → App Credentials, show the Signing Secret, and copy it.",
|
|
94
|
+
},
|
|
95
|
+
// Slack gives no way to ask whether it is right: the first signed request will say.
|
|
96
|
+
async check(value) {
|
|
97
|
+
if (!/^[a-f0-9]{32}$/i.test(value)) {
|
|
98
|
+
throw new SetupRefused("That is not a signing secret.", {
|
|
99
|
+
fix: "It is 32 letters and digits, under Basic Information → App Credentials (not the Client Secret).",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
]),
|
|
106
|
+
];
|
|
107
|
+
return {
|
|
108
|
+
values,
|
|
109
|
+
finish: () => [
|
|
110
|
+
"invite the app to each channel it should read (/invite @<its name> in the channel)",
|
|
111
|
+
...(way === "webhook"
|
|
112
|
+
? [
|
|
113
|
+
`Event Subscriptions → set the Request URL to ${publicUrl ?? "https://<where it is served>"}/slack once it ` +
|
|
114
|
+
"is served (Slack verifies it as you save; click Retry if it was set before)",
|
|
115
|
+
]
|
|
116
|
+
: []),
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
};
|
|
@@ -102,6 +102,8 @@ export interface SlackApi {
|
|
|
102
102
|
userId?: string;
|
|
103
103
|
botId?: string;
|
|
104
104
|
}>;
|
|
105
|
+
/** The id of the Slack app a bot belongs to (`bots.info`, which a bot token holding `users:read` may call). */
|
|
106
|
+
botAppId(botId: string): Promise<string | undefined>;
|
|
105
107
|
postMessage(target: SlackTarget, text: string): Promise<string>;
|
|
106
108
|
postMarkdown(target: SlackTarget, markdown: string): Promise<string>;
|
|
107
109
|
updateMessage(channelId: string, ts: string, text: string): Promise<void>;
|
|
@@ -261,6 +261,10 @@ function slackClient({ botToken, baseUrl = "https://slack.com/api" }, onSent) {
|
|
|
261
261
|
const data = await call("auth.test", {});
|
|
262
262
|
return { teamId: data.team_id, userId: data.user_id, botId: data.bot_id };
|
|
263
263
|
},
|
|
264
|
+
async botAppId(botId) {
|
|
265
|
+
const data = await call("bots.info", { bot: botId }, "GET");
|
|
266
|
+
return data.bot?.app_id;
|
|
267
|
+
},
|
|
264
268
|
async postMessage(target, text) {
|
|
265
269
|
const data = await call("chat.postMessage", {
|
|
266
270
|
channel: target.channelId,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type { ChannelModule } from "../../channel.ts";
|
|
1
|
+
import type { ChannelModule, LongConnectionChannelModule } from "../../channel.ts";
|
|
2
|
+
import { type OpenSlackSocket } from "./socket.ts";
|
|
2
3
|
import { type SlackEventEnvelope, type SlackFile, type SlackMessageEvent, type SlackRoute, defaultSlackRoute, slackEnvelope } from "./parse.ts";
|
|
3
4
|
import { type SlackFailure, type SlackRendering } from "./preview.ts";
|
|
4
5
|
export { defaultSlackRoute, slackEnvelope };
|
|
5
|
-
export type { SlackEventEnvelope, SlackFailure, SlackFile, SlackMessageEvent, SlackRendering, SlackRoute };
|
|
6
|
+
export type { SlackEventEnvelope, SlackFailure, SlackFile, SlackMessageEvent, SlackRendering, SlackRoute, SlackSocketChannelOptions, };
|
|
7
|
+
/** What `slackSocketChannel` is given: the policy of `slackChannel`, and the app-level token in place of its secret. */
|
|
8
|
+
interface SlackSocketChannelOptions extends SlackPolicyOptions {
|
|
9
|
+
/** The app-level token (`xapp-…`, scope `connections:write`): opens the socket. */
|
|
10
|
+
appToken: string;
|
|
11
|
+
}
|
|
6
12
|
export interface SlackChannelOptions {
|
|
7
13
|
/** Bot User OAuth Token (`xoxb-…`) used for replies and files. */
|
|
8
14
|
botToken: string;
|
|
@@ -35,4 +41,17 @@ export interface SlackChannelOptions {
|
|
|
35
41
|
}
|
|
36
42
|
/** Verify Slack's v0 HMAC over the exact raw body and reject timestamps outside the replay window. */
|
|
37
43
|
export declare function verifySlackSignature(signingSecret: string, timestamp: string, signature: string, rawBody: string, nowMs?: number): boolean;
|
|
44
|
+
/** What a Slack channel is told besides how its events arrive: the policy both ways share. */
|
|
45
|
+
type SlackPolicyOptions = Omit<SlackChannelOptions, "signingSecret">;
|
|
46
|
+
/** Build a Slack channel that receives by the Events API's HTTP Request URL: signed requests in, durable turns out. */
|
|
38
47
|
export declare function slackChannel(options: SlackChannelOptions): ChannelModule;
|
|
48
|
+
/**
|
|
49
|
+
* Build a Slack channel that receives by Socket Mode: the process opens a WebSocket to Slack with the app-level token
|
|
50
|
+
* and takes its events there, so no public URL and no signing secret are involved. An app in Socket Mode receives over
|
|
51
|
+
* the socket alone: it has no Request URL to point anywhere else.
|
|
52
|
+
*/
|
|
53
|
+
export declare function slackSocketChannel(options: SlackSocketChannelOptions,
|
|
54
|
+
/** Internal seam for deterministic tests: the socket the connection opens. */
|
|
55
|
+
deps?: {
|
|
56
|
+
openSocket?: OpenSlackSocket;
|
|
57
|
+
}): LongConnectionChannelModule;
|