@fastagent-sh/fastagent 0.29.0 → 0.30.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.
Files changed (312) hide show
  1. package/README.md +9 -4
  2. package/dist/atomic-write.d.ts +4 -5
  3. package/dist/atomic-write.js +4 -5
  4. package/dist/authoring.d.ts +32 -15
  5. package/dist/authoring.js +147 -127
  6. package/dist/channels/agentcore-protocol.d.ts +8 -3
  7. package/dist/channels/agentcore-protocol.js +3 -1
  8. package/dist/channels/agentcore-service.d.ts +1 -0
  9. package/dist/channels/agentcore-service.js +14 -4
  10. package/dist/channels/agentcore.d.ts +9 -0
  11. package/dist/channels/agentcore.js +29 -4
  12. package/dist/channels/context.d.ts +2 -0
  13. package/dist/channels/context.js +5 -1
  14. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  15. package/dist/channels/feishu/feishu.d.ts +0 -3
  16. package/dist/channels/feishu/feishu.js +10 -10
  17. package/dist/channels/feishu/register-app.js +3 -5
  18. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  19. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  20. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  21. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  22. package/dist/channels/feishu/setup-mode.js +2 -22
  23. package/dist/channels/feishu/setup.d.ts +12 -0
  24. package/dist/channels/feishu/setup.js +197 -0
  25. package/dist/channels/kit/abortable.d.ts +8 -0
  26. package/dist/channels/kit/abortable.js +22 -0
  27. package/dist/channels/lark/lark.d.ts +0 -3
  28. package/dist/channels/lark/lark.js +0 -5
  29. package/dist/channels/lark/scaffold/channel.ts +33 -35
  30. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  31. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  32. package/dist/channels/lark/setup.d.ts +9 -0
  33. package/dist/channels/lark/setup.js +57 -0
  34. package/dist/channels/setup.d.ts +112 -0
  35. package/dist/channels/setup.js +31 -0
  36. package/dist/channels/slack/history.js +1 -1
  37. package/dist/channels/slack/manifest.d.ts +17 -7
  38. package/dist/channels/slack/manifest.js +12 -16
  39. package/dist/channels/slack/scaffold/channel.ts +44 -30
  40. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  41. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  42. package/dist/channels/slack/setup.d.ts +7 -0
  43. package/dist/channels/slack/setup.js +119 -0
  44. package/dist/channels/slack/slack-api.d.ts +2 -0
  45. package/dist/channels/slack/slack-api.js +4 -0
  46. package/dist/channels/slack/slack.d.ts +21 -2
  47. package/dist/channels/slack/slack.js +398 -338
  48. package/dist/channels/slack/socket.d.ts +57 -0
  49. package/dist/channels/slack/socket.js +305 -0
  50. package/dist/channels/telegram/polling.d.ts +27 -0
  51. package/dist/channels/telegram/polling.js +105 -0
  52. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  53. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  54. package/dist/channels/telegram/setup.d.ts +2 -0
  55. package/dist/channels/telegram/setup.js +42 -0
  56. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  57. package/dist/channels/telegram/telegram-api.js +7 -6
  58. package/dist/channels/telegram/telegram.d.ts +11 -3
  59. package/dist/channels/telegram/telegram.js +184 -157
  60. package/dist/channels/webhook-evidence.d.ts +11 -0
  61. package/dist/channels/webhook-evidence.js +71 -0
  62. package/dist/channels/webhooks.d.ts +37 -0
  63. package/dist/channels/webhooks.js +70 -0
  64. package/dist/child-env.d.ts +16 -0
  65. package/dist/child-env.js +30 -0
  66. package/dist/cli/box-login.js +4 -1
  67. package/dist/cli/channel-app.d.ts +3 -0
  68. package/dist/cli/channel-app.js +35 -0
  69. package/dist/cli/channel-setup.d.ts +38 -0
  70. package/dist/cli/channel-setup.js +195 -0
  71. package/dist/cli/commands/add.d.ts +6 -14
  72. package/dist/cli/commands/add.js +45 -303
  73. package/dist/cli/commands/auth.js +8 -2
  74. package/dist/cli/commands/build.d.ts +5 -0
  75. package/dist/cli/commands/build.js +16 -0
  76. package/dist/cli/commands/channels.d.ts +19 -0
  77. package/dist/cli/commands/channels.js +230 -0
  78. package/dist/cli/commands/content.d.ts +7 -3
  79. package/dist/cli/commands/content.js +16 -10
  80. package/dist/cli/commands/deploy/agentcore.js +55 -23
  81. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  82. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  83. package/dist/cli/commands/deploy/docker.js +32 -65
  84. package/dist/cli/commands/deploy/fly.js +28 -12
  85. package/dist/cli/commands/deploy/railway.js +38 -13
  86. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  87. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  88. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  89. package/dist/cli/commands/deploy/shared.js +25 -8
  90. package/dist/cli/commands/deploy.js +53 -60
  91. package/dist/cli/commands/dev.d.ts +2 -0
  92. package/dist/cli/commands/dev.js +15 -3
  93. package/dist/cli/commands/env.js +1 -1
  94. package/dist/cli/commands/info.d.ts +2 -0
  95. package/dist/cli/commands/info.js +60 -25
  96. package/dist/cli/commands/init.js +3 -1
  97. package/dist/cli/commands/invoke.d.ts +2 -0
  98. package/dist/cli/commands/invoke.js +20 -7
  99. package/dist/cli/commands/login.d.ts +0 -7
  100. package/dist/cli/commands/login.js +22 -10
  101. package/dist/cli/commands/logout.js +1 -1
  102. package/dist/cli/commands/mcp.js +14 -6
  103. package/dist/cli/commands/models.js +3 -2
  104. package/dist/cli/commands/schedules.d.ts +4 -2
  105. package/dist/cli/commands/schedules.js +14 -10
  106. package/dist/cli/commands/secrets.d.ts +14 -0
  107. package/dist/cli/commands/secrets.js +213 -0
  108. package/dist/cli/commands/start.d.ts +14 -14
  109. package/dist/cli/commands/start.js +46 -60
  110. package/dist/cli/content-view.d.ts +4 -6
  111. package/dist/cli/content-view.js +5 -11
  112. package/dist/cli/first-run-channels.d.ts +3 -0
  113. package/dist/cli/first-run-channels.js +63 -0
  114. package/dist/cli/instance.d.ts +41 -0
  115. package/dist/cli/instance.js +155 -0
  116. package/dist/cli/invoke-stream.d.ts +5 -2
  117. package/dist/cli/invoke-stream.js +7 -2
  118. package/dist/cli/kernel.js +3 -0
  119. package/dist/cli/program.js +282 -110
  120. package/dist/cli/serve.d.ts +4 -5
  121. package/dist/cli/serve.js +4 -7
  122. package/dist/cli/shared.d.ts +11 -2
  123. package/dist/cli/shared.js +32 -19
  124. package/dist/cli/ticket.d.ts +7 -0
  125. package/dist/cli/ticket.js +106 -30
  126. package/dist/content/declare.d.ts +31 -37
  127. package/dist/content/declare.js +59 -60
  128. package/dist/content/file.d.ts +23 -12
  129. package/dist/content/file.js +48 -35
  130. package/dist/content/git.d.ts +0 -10
  131. package/dist/content/git.js +5 -39
  132. package/dist/content/resolve.d.ts +18 -22
  133. package/dist/content/resolve.js +32 -66
  134. package/dist/content/source.d.ts +7 -8
  135. package/dist/content/source.js +10 -28
  136. package/dist/credentials-page.d.ts +26 -0
  137. package/dist/credentials-page.js +185 -0
  138. package/dist/declared-secrets.d.ts +10 -3
  139. package/dist/declared-secrets.js +8 -4
  140. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  141. package/dist/deploy/agentcore/destroy.js +25 -1
  142. package/dist/deploy/agentcore/forwarder.js +1 -1
  143. package/dist/deploy/agentcore/plan.d.ts +7 -21
  144. package/dist/deploy/agentcore/plan.js +47 -49
  145. package/dist/deploy/agentcore/run.d.ts +18 -10
  146. package/dist/deploy/agentcore/run.js +93 -70
  147. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  148. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  149. package/dist/deploy/agentcore/shell.js +12 -19
  150. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  151. package/dist/deploy/agentcore/sigv4.js +15 -0
  152. package/dist/deploy/agentcore/slr.d.ts +18 -0
  153. package/dist/deploy/agentcore/slr.js +27 -0
  154. package/dist/deploy/agentcore/store.d.ts +7 -0
  155. package/dist/deploy/agentcore/store.js +115 -0
  156. package/dist/deploy/api.d.ts +14 -14
  157. package/dist/deploy/api.js +26 -24
  158. package/dist/deploy/box-shell.d.ts +3 -1
  159. package/dist/deploy/build-context.d.ts +4 -4
  160. package/dist/deploy/build-context.js +11 -15
  161. package/dist/deploy/channel-ingress.d.ts +35 -9
  162. package/dist/deploy/channel-ingress.js +119 -22
  163. package/dist/deploy/container.d.ts +35 -10
  164. package/dist/deploy/container.js +79 -37
  165. package/dist/deploy/delivered-text.d.ts +13 -0
  166. package/dist/deploy/delivered-text.js +86 -0
  167. package/dist/deploy/docker/plan.d.ts +7 -8
  168. package/dist/deploy/docker/plan.js +31 -52
  169. package/dist/deploy/docker/run.d.ts +5 -43
  170. package/dist/deploy/docker/run.js +9 -84
  171. package/dist/deploy/file-store.d.ts +2 -0
  172. package/dist/deploy/file-store.js +17 -0
  173. package/dist/deploy/fly/plan.d.ts +3 -2
  174. package/dist/deploy/fly/plan.js +20 -9
  175. package/dist/deploy/fly/run.d.ts +18 -8
  176. package/dist/deploy/fly/run.js +24 -42
  177. package/dist/deploy/fly/store.d.ts +12 -0
  178. package/dist/deploy/fly/store.js +83 -0
  179. package/dist/deploy/hosts.d.ts +1 -2
  180. package/dist/deploy/hosts.js +1 -2
  181. package/dist/deploy/preflight.d.ts +18 -5
  182. package/dist/deploy/preflight.js +135 -63
  183. package/dist/deploy/railway/ignore.d.ts +8 -0
  184. package/dist/deploy/railway/ignore.js +57 -0
  185. package/dist/deploy/railway/plan.d.ts +1 -2
  186. package/dist/deploy/railway/plan.js +8 -6
  187. package/dist/deploy/railway/run.d.ts +21 -15
  188. package/dist/deploy/railway/run.js +32 -78
  189. package/dist/deploy/railway/store.d.ts +29 -0
  190. package/dist/deploy/railway/store.js +143 -0
  191. package/dist/deploy/runner.d.ts +5 -2
  192. package/dist/deploy/runner.js +4 -3
  193. package/dist/deploy/secret-store.d.ts +63 -0
  194. package/dist/deploy/secret-store.js +98 -0
  195. package/dist/deploy/secrets.d.ts +29 -46
  196. package/dist/deploy/secrets.js +49 -122
  197. package/dist/deploy/workspace.d.ts +6 -12
  198. package/dist/deploy/workspace.js +18 -94
  199. package/dist/dev-supervisor.js +6 -8
  200. package/dist/environment/declare.js +3 -2
  201. package/dist/environment/mise.d.ts +3 -1
  202. package/dist/environment/mise.js +4 -2
  203. package/dist/feishu.d.ts +1 -1
  204. package/dist/feishu.js +1 -1
  205. package/dist/harnesses/models/catalog.d.ts +1 -0
  206. package/dist/harnesses/models/catalog.js +1 -2
  207. package/dist/harnesses/models/environment.d.ts +7 -2
  208. package/dist/harnesses/models/environment.js +9 -5
  209. package/dist/harnesses/models/host.d.ts +0 -7
  210. package/dist/harnesses/models/host.js +10 -1
  211. package/dist/harnesses/models/login.d.ts +0 -8
  212. package/dist/harnesses/models/login.js +5 -4
  213. package/dist/harnesses/models/models.d.ts +7 -2
  214. package/dist/harnesses/models/models.js +28 -4
  215. package/dist/harnesses/pi-durable/agent.js +59 -38
  216. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  217. package/dist/harnesses/pi-durable/build.js +270 -0
  218. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  219. package/dist/harnesses/pi-durable/built.js +136 -0
  220. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  221. package/dist/harnesses/pi-durable/config.js +266 -0
  222. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  223. package/dist/harnesses/pi-durable/define.js +23 -2
  224. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  225. package/dist/harnesses/pi-durable/definition.js +143 -0
  226. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  227. package/dist/harnesses/pi-durable/events.js +179 -136
  228. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  229. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  230. package/dist/harnesses/pi-durable/mcp.js +1 -3
  231. package/dist/harnesses/pi-durable/open.d.ts +35 -37
  232. package/dist/harnesses/pi-durable/open.js +91 -153
  233. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  234. package/dist/harnesses/pi-durable/reads.js +1 -3
  235. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  236. package/dist/harnesses/pi-durable/registry.js +29 -26
  237. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  238. package/dist/harnesses/pi-durable/release.js +10 -7
  239. package/dist/harnesses/pi-durable/serve.d.ts +91 -15
  240. package/dist/harnesses/pi-durable/serve.js +109 -47
  241. package/dist/harnesses/pi-durable/sessions.js +15 -12
  242. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  243. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  244. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  245. package/dist/harnesses/pi-durable/text.js +115 -59
  246. package/dist/harnesses/pi-durable/wake.js +8 -8
  247. package/dist/http/client.js +9 -3
  248. package/dist/http/contract.d.ts +3 -3
  249. package/dist/http/server.d.ts +1 -1
  250. package/dist/http/server.js +1 -1
  251. package/dist/index.d.ts +8 -5
  252. package/dist/index.js +4 -1
  253. package/dist/instance-name.d.ts +7 -0
  254. package/dist/instance-name.js +26 -0
  255. package/dist/lark.d.ts +1 -1
  256. package/dist/lark.js +1 -1
  257. package/dist/mcp/client.js +11 -2
  258. package/dist/mcp/config.d.ts +70 -18
  259. package/dist/mcp/config.js +136 -53
  260. package/dist/mcp/host.d.ts +5 -2
  261. package/dist/mcp/host.js +14 -6
  262. package/dist/mcp/sign-in.js +2 -2
  263. package/dist/mcp-api.d.ts +14 -0
  264. package/dist/paths.d.ts +22 -8
  265. package/dist/paths.js +44 -11
  266. package/dist/runtime-websocket.d.ts +14 -0
  267. package/dist/runtime-websocket.js +39 -0
  268. package/dist/scaffold/add-channel.d.ts +9 -16
  269. package/dist/scaffold/add-channel.js +13 -83
  270. package/dist/scaffold/templates/env.example +4 -5
  271. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  272. package/dist/scaffold/templates/gitignore +9 -6
  273. package/dist/scaffold/templates.d.ts +6 -2
  274. package/dist/scaffold/templates.js +7 -2
  275. package/dist/scaffold/vendor-skill.js +4 -2
  276. package/dist/service.d.ts +44 -6
  277. package/dist/service.js +110 -10
  278. package/dist/serving-command.d.ts +10 -0
  279. package/dist/serving-command.js +1 -0
  280. package/dist/slack.d.ts +1 -1
  281. package/dist/slack.js +1 -1
  282. package/dist/strip-edges.d.ts +10 -0
  283. package/dist/strip-edges.js +22 -0
  284. package/dist/telegram.d.ts +1 -1
  285. package/dist/telegram.js +1 -1
  286. package/dist/tunnel.d.ts +0 -1
  287. package/dist/tunnel.js +2 -8
  288. package/dist/value-file.d.ts +11 -6
  289. package/dist/value-file.js +21 -9
  290. package/dist/version.d.ts +7 -1
  291. package/dist/version.js +9 -3
  292. package/package.json +6 -5
  293. package/dist/channels/lark/credentials-page.d.ts +0 -25
  294. package/dist/channels/lark/credentials-page.js +0 -206
  295. package/dist/channels/lark/onboard.d.ts +0 -52
  296. package/dist/channels/lark/onboard.js +0 -86
  297. package/dist/channels/slack/config-api.d.ts +0 -69
  298. package/dist/channels/slack/config-api.js +0 -162
  299. package/dist/channels/slack/onboard.d.ts +0 -40
  300. package/dist/channels/slack/onboard.js +0 -136
  301. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  302. package/dist/channels/slack/onboarding-state.js +0 -64
  303. package/dist/channels/slack/register-webhook.d.ts +0 -12
  304. package/dist/channels/slack/register-webhook.js +0 -67
  305. package/dist/channels/slack/setup-server.d.ts +0 -32
  306. package/dist/channels/slack/setup-server.js +0 -246
  307. package/dist/cli/add-feishu.d.ts +0 -41
  308. package/dist/cli/add-feishu.js +0 -391
  309. package/dist/cli/add-slack.d.ts +0 -18
  310. package/dist/cli/add-slack.js +0 -216
  311. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  312. 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
- export function slackBotScopes() {
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
- ...(input.requestUrl
69
- ? {
70
- event_subscriptions: {
71
- request_url: input.requestUrl,
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: false,
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 HTTP Events API channel. Setup:
5
- // 1. Create a Slack app at https://api.slack.com/apps and add a bot user.
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
- // `needs` declares the env vars this channel reads: fastagent carries them to a deployed box and
16
- // refuses to serve while one is unset.
17
- export default needs(["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], (secrets) =>
18
- slackChannel({
19
- botToken: secrets.SLACK_BOT_TOKEN, // OAuth & Permissions → Bot User OAuth Token (xoxb-…)
20
- signingSecret: secrets.SLACK_SIGNING_SECRET, // Basic Information → App Credentials
21
- // Slack Agent stream; its inline tool traces show each call's first argument and stay in the
22
- // delivered message. "classic" settles into the answer alone (and gives up native streaming).
23
- rendering: "native",
24
- // Optional per-reply footer, if your policy requires one: aiDisclaimer: "AI-generated; verify important information.",
25
- // welcome: "Custom first-run DM greeting", // sent once on first DM open; false disables (default: a generic greeting)
26
- // reactionAck: false, // disable the 👀→✅ ack on the user's message (default on; needs reactions:write)
27
- // An answer attaches to its question with a thread (Slack has no quote primitive), and that thread is
28
- // one conversation, with a session of its own: see docs/design/participant-model.md.
29
- // Dev/personal bot: surface raw errors. Remove this for a customer-facing bot; details remain in logs.
30
- onError: (failed) => `⚠️ ${failed.details}`,
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 { Type } from "@earendil-works/pi-ai";
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: Type.Object({
26
- channelId: Type.String({ description: "target Slack channel ID, or a user ID (U…) to message that user's DM" }),
27
- text: Type.Optional(Type.String({ description: "standard Markdown message text" })),
28
- path: Type.Optional(Type.String({ description: "absolute path of a local file to upload" })),
29
- title: Type.Optional(Type.String({ description: "file title (file mode only)" })),
30
- initialComment: Type.Optional(Type.String({ description: "message posted with the file (file mode only)" })),
31
- threadTs: Type.Optional(Type.String({ description: "thread parent timestamp, if replying in a thread" })),
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 { Type } from "@earendil-works/pi-ai";
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: Type.Object({
18
- threadId: Type.Optional(
19
- Type.String({
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;