@cursor/july 0.1.16 → 0.1.18

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 (273) hide show
  1. package/README.md +9 -4
  2. package/dist/bin/agent-serve.js +60 -2
  3. package/dist/channels/slack/api.d.ts.map +1 -1
  4. package/dist/channels/slack/api.js +13 -3
  5. package/dist/channels/slack/auth.d.ts +7 -1
  6. package/dist/channels/slack/auth.d.ts.map +1 -1
  7. package/dist/channels/slack/auth.js +14 -0
  8. package/dist/channels/slack/blocks.d.ts +61 -0
  9. package/dist/channels/slack/blocks.d.ts.map +1 -0
  10. package/dist/channels/slack/blocks.js +90 -0
  11. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  12. package/dist/channels/slack/cursor-account.js +1 -1
  13. package/dist/channels/slack/defaults.d.ts +2 -0
  14. package/dist/channels/slack/defaults.d.ts.map +1 -1
  15. package/dist/channels/slack/defaults.js +29 -3
  16. package/dist/channels/slack/dispatch.d.ts +3 -17
  17. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  18. package/dist/channels/slack/dispatch.js +4 -1
  19. package/dist/channels/slack/inbound.d.ts.map +1 -1
  20. package/dist/channels/slack/inbound.js +6 -1
  21. package/dist/channels/slack/index.d.ts +2 -0
  22. package/dist/channels/slack/index.d.ts.map +1 -1
  23. package/dist/channels/slack/index.js +2 -0
  24. package/dist/channels/slack/init.d.ts.map +1 -1
  25. package/dist/channels/slack/init.js +4 -1
  26. package/dist/channels/slack/interactive.d.ts +39 -3
  27. package/dist/channels/slack/interactive.d.ts.map +1 -1
  28. package/dist/channels/slack/interactive.js +153 -7
  29. package/dist/channels/slack/manifest.d.ts +13 -0
  30. package/dist/channels/slack/manifest.d.ts.map +1 -1
  31. package/dist/channels/slack/manifest.js +36 -14
  32. package/dist/channels/slack/message-body.d.ts +13 -0
  33. package/dist/channels/slack/message-body.d.ts.map +1 -0
  34. package/dist/channels/slack/message-body.js +82 -0
  35. package/dist/channels/slack/setup.d.ts +4 -1
  36. package/dist/channels/slack/setup.d.ts.map +1 -1
  37. package/dist/channels/slack/setup.js +16 -2
  38. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  39. package/dist/channels/slack/slack-channel.js +24 -3
  40. package/dist/channels/slack/thread-context.d.ts +0 -2
  41. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  42. package/dist/channels/slack/thread-context.js +1 -65
  43. package/dist/channels/slack/types.d.ts +93 -1
  44. package/dist/channels/slack/types.d.ts.map +1 -1
  45. package/dist/connections.d.ts +4 -1
  46. package/dist/connections.d.ts.map +1 -1
  47. package/dist/connections.js +4 -1
  48. package/dist/docs/404.html +2 -2
  49. package/dist/docs/ab.html +3 -3
  50. package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
  51. package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
  52. package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
  53. package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
  55. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
  56. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
  57. package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
  58. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
  59. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
  60. package/dist/docs/building-with-agents.html +3 -3
  61. package/dist/docs/concepts.html +3 -3
  62. package/dist/docs/deployment.html +3 -3
  63. package/dist/docs/evals.html +3 -3
  64. package/dist/docs/example-agents/approval-buddy.html +3 -3
  65. package/dist/docs/example-agents/benny.html +3 -3
  66. package/dist/docs/example-agents/bugbot.html +3 -3
  67. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  68. package/dist/docs/example-agents/codeowners-review.html +3 -3
  69. package/dist/docs/example-agents/concierge.html +3 -3
  70. package/dist/docs/example-agents/fsd.html +3 -3
  71. package/dist/docs/example-agents/index.html +3 -3
  72. package/dist/docs/example-agents/knowledge-base.html +3 -3
  73. package/dist/docs/example-agents/oncall.html +3 -3
  74. package/dist/docs/example-agents/security-reviewer.html +5 -5
  75. package/dist/docs/example-agents/slack-agent.html +3 -3
  76. package/dist/docs/example-agents/weather-agent.html +3 -3
  77. package/dist/docs/guides/agent-to-agent.html +3 -3
  78. package/dist/docs/guides/cloud-runtime.html +3 -3
  79. package/dist/docs/guides/github.html +3 -3
  80. package/dist/docs/guides/human-in-the-loop.html +3 -3
  81. package/dist/docs/guides/mcp-oauth.html +6 -5
  82. package/dist/docs/guides/slack.html +5 -5
  83. package/dist/docs/guides/webhooks.html +3 -3
  84. package/dist/docs/hashmap.json +1 -1
  85. package/dist/docs/hillclimbing.html +3 -3
  86. package/dist/docs/index.html +3 -3
  87. package/dist/docs/quickstart.html +3 -3
  88. package/dist/docs/reference/agent-config.html +3 -3
  89. package/dist/docs/reference/channels.html +3 -3
  90. package/dist/docs/reference/cli.html +3 -3
  91. package/dist/docs/reference/connections.html +14 -7
  92. package/dist/docs/reference/hooks.html +3 -3
  93. package/dist/docs/reference/http-api.html +3 -3
  94. package/dist/docs/reference/instructions.html +3 -3
  95. package/dist/docs/reference/playground.html +3 -3
  96. package/dist/docs/reference/project-layout.html +3 -3
  97. package/dist/docs/reference/prompt.html +3 -3
  98. package/dist/docs/reference/schedules.html +3 -3
  99. package/dist/docs/reference/sessions.html +3 -3
  100. package/dist/docs/reference/skills.html +3 -3
  101. package/dist/docs/reference/subagents.html +3 -3
  102. package/dist/docs/reference/tools.html +3 -3
  103. package/dist/docs/scaffolding-agents.html +3 -3
  104. package/dist/docs/storage.html +3 -3
  105. package/dist/docs/troubleshooting.html +3 -3
  106. package/dist/evals/assertions.d.ts +190 -0
  107. package/dist/evals/assertions.d.ts.map +1 -0
  108. package/dist/evals/assertions.js +461 -0
  109. package/dist/evals/expect.d.ts +95 -0
  110. package/dist/evals/expect.d.ts.map +1 -0
  111. package/dist/evals/expect.js +194 -0
  112. package/dist/evals/judge.d.ts +133 -0
  113. package/dist/evals/judge.d.ts.map +1 -0
  114. package/dist/evals/judge.js +228 -0
  115. package/dist/evals/loaders.d.ts +51 -0
  116. package/dist/evals/loaders.d.ts.map +1 -0
  117. package/dist/evals/loaders.js +124 -0
  118. package/dist/evals/matchers.d.ts +95 -0
  119. package/dist/evals/matchers.d.ts.map +1 -0
  120. package/dist/evals/matchers.js +145 -0
  121. package/dist/evals/reporters.d.ts +70 -0
  122. package/dist/evals/reporters.d.ts.map +1 -0
  123. package/dist/evals/reporters.js +221 -0
  124. package/dist/evals/results.d.ts +103 -0
  125. package/dist/evals/results.d.ts.map +1 -0
  126. package/dist/evals/results.js +28 -0
  127. package/dist/evals/run-facts.d.ts +77 -0
  128. package/dist/evals/run-facts.d.ts.map +1 -0
  129. package/dist/evals/run-facts.js +187 -0
  130. package/dist/evals.d.ts +118 -31
  131. package/dist/evals.d.ts.map +1 -1
  132. package/dist/evals.js +46 -12
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/internal/builtin-tools/index.d.ts +10 -0
  137. package/dist/internal/builtin-tools/index.d.ts.map +1 -0
  138. package/dist/internal/builtin-tools/index.js +27 -0
  139. package/dist/internal/builtin-tools/reminders.d.ts +9 -0
  140. package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
  141. package/dist/internal/builtin-tools/reminders.js +138 -0
  142. package/dist/internal/cli-ax.d.ts +27 -0
  143. package/dist/internal/cli-ax.d.ts.map +1 -1
  144. package/dist/internal/cli-ax.js +90 -12
  145. package/dist/internal/cli-mcp.d.ts +8 -0
  146. package/dist/internal/cli-mcp.d.ts.map +1 -1
  147. package/dist/internal/cli-mcp.js +98 -0
  148. package/dist/internal/cli-slack.d.ts +29 -1
  149. package/dist/internal/cli-slack.d.ts.map +1 -1
  150. package/dist/internal/cli-slack.js +799 -13
  151. package/dist/internal/cursor-event-relay.d.ts +30 -0
  152. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  153. package/dist/internal/cursor-event-relay.js +37 -0
  154. package/dist/internal/deploy-client.d.ts +7 -0
  155. package/dist/internal/deploy-client.d.ts.map +1 -1
  156. package/dist/internal/deploy-client.js +9 -0
  157. package/dist/internal/discovery.d.ts.map +1 -1
  158. package/dist/internal/discovery.js +70 -9
  159. package/dist/internal/distribution.d.ts.map +1 -1
  160. package/dist/internal/distribution.js +2 -0
  161. package/dist/internal/eval-judge-model.d.ts +59 -0
  162. package/dist/internal/eval-judge-model.d.ts.map +1 -0
  163. package/dist/internal/eval-judge-model.js +131 -0
  164. package/dist/internal/eval-run-store.d.ts.map +1 -1
  165. package/dist/internal/eval-run-store.js +12 -20
  166. package/dist/internal/eval-runner.d.ts +31 -25
  167. package/dist/internal/eval-runner.d.ts.map +1 -1
  168. package/dist/internal/eval-runner.js +329 -126
  169. package/dist/internal/evals-client.d.ts.map +1 -1
  170. package/dist/internal/evals-client.js +4 -1
  171. package/dist/internal/local-env.d.ts +8 -0
  172. package/dist/internal/local-env.d.ts.map +1 -0
  173. package/dist/internal/local-env.js +47 -0
  174. package/dist/internal/playground/static.d.ts +3 -0
  175. package/dist/internal/playground/static.d.ts.map +1 -1
  176. package/dist/internal/playground/static.js +3 -0
  177. package/dist/internal/reminder-runner.d.ts +7 -0
  178. package/dist/internal/reminder-runner.d.ts.map +1 -1
  179. package/dist/internal/reminder-runner.js +21 -0
  180. package/dist/internal/run-client.d.ts +15 -0
  181. package/dist/internal/run-client.d.ts.map +1 -1
  182. package/dist/internal/run-client.js +3 -2
  183. package/dist/internal/sdk-runner.d.ts +51 -1
  184. package/dist/internal/sdk-runner.d.ts.map +1 -1
  185. package/dist/internal/sdk-runner.js +176 -36
  186. package/dist/internal/server.d.ts.map +1 -1
  187. package/dist/internal/server.js +11 -1
  188. package/dist/internal/session-engine.d.ts +18 -5
  189. package/dist/internal/session-engine.d.ts.map +1 -1
  190. package/dist/internal/session-engine.js +81 -19
  191. package/dist/internal/slack-provision-client.d.ts +133 -0
  192. package/dist/internal/slack-provision-client.d.ts.map +1 -0
  193. package/dist/internal/slack-provision-client.js +286 -0
  194. package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
  195. package/dist/playground/assets/index-DRjS5klQ.js +85 -0
  196. package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
  197. package/dist/playground/index.html +2 -2
  198. package/dist/types.d.ts +98 -4
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +26 -0
  201. package/docs/example-agents/security-reviewer.md +1 -1
  202. package/docs/guides/mcp-oauth.md +10 -5
  203. package/docs/guides/slack.md +3 -2
  204. package/docs/reference/connections.md +28 -18
  205. package/package.json +20 -2
  206. package/skills/create-agent/SKILL.md +15 -7
  207. package/skills/debug/SKILL.md +3 -1
  208. package/skills/mcp-auth/SKILL.md +1 -1
  209. package/skills/setup-slack/SKILL.md +177 -11
  210. package/src/bin/agent-serve.ts +63 -2
  211. package/src/channels/slack/api.ts +16 -3
  212. package/src/channels/slack/auth.ts +16 -1
  213. package/src/channels/slack/blocks.ts +139 -0
  214. package/src/channels/slack/cursor-account.ts +1 -0
  215. package/src/channels/slack/defaults.ts +32 -4
  216. package/src/channels/slack/dispatch.ts +6 -19
  217. package/src/channels/slack/inbound.ts +6 -1
  218. package/src/channels/slack/index.ts +2 -0
  219. package/src/channels/slack/init.ts +3 -0
  220. package/src/channels/slack/interactive.ts +184 -19
  221. package/src/channels/slack/manifest.ts +37 -6
  222. package/src/channels/slack/message-body.ts +90 -0
  223. package/src/channels/slack/setup.ts +16 -2
  224. package/src/channels/slack/slack-channel.ts +37 -6
  225. package/src/channels/slack/thread-context.ts +1 -69
  226. package/src/channels/slack/types.ts +105 -1
  227. package/src/connections.ts +4 -1
  228. package/src/evals/assertions.ts +704 -0
  229. package/src/evals/expect.ts +272 -0
  230. package/src/evals/judge.ts +346 -0
  231. package/src/evals/loaders.ts +113 -0
  232. package/src/evals/matchers.ts +246 -0
  233. package/src/evals/reporters.ts +274 -0
  234. package/src/evals/results.ts +132 -0
  235. package/src/evals/run-facts.ts +274 -0
  236. package/src/evals.ts +205 -42
  237. package/src/index.ts +2 -0
  238. package/src/internal/builtin-tools/index.ts +40 -0
  239. package/src/internal/builtin-tools/reminders.ts +158 -0
  240. package/src/internal/cli-ax.ts +134 -15
  241. package/src/internal/cli-mcp.ts +118 -5
  242. package/src/internal/cli-slack.ts +1017 -15
  243. package/src/internal/cursor-event-relay.ts +67 -0
  244. package/src/internal/deploy-client.ts +10 -0
  245. package/src/internal/discovery.ts +83 -5
  246. package/src/internal/distribution.ts +2 -0
  247. package/src/internal/eval-judge-model.ts +148 -0
  248. package/src/internal/eval-run-store.ts +10 -14
  249. package/src/internal/eval-runner.ts +460 -214
  250. package/src/internal/evals-client.ts +5 -0
  251. package/src/internal/local-env.ts +53 -0
  252. package/src/internal/playground/static.ts +3 -0
  253. package/src/internal/reminder-runner.ts +30 -0
  254. package/src/internal/run-client.ts +17 -1
  255. package/src/internal/sdk-runner.ts +234 -35
  256. package/src/internal/server.ts +12 -0
  257. package/src/internal/session-engine.ts +103 -21
  258. package/src/internal/slack-provision-client.ts +442 -0
  259. package/src/types.ts +125 -4
  260. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  261. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  262. package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
  263. package/dist/internal/json-dir-store.d.ts +0 -32
  264. package/dist/internal/json-dir-store.d.ts.map +0 -1
  265. package/dist/internal/persistence-coordinator.d.ts +0 -127
  266. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  267. package/dist/persistence.d.ts +0 -184
  268. package/dist/persistence.d.ts.map +0 -1
  269. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  270. package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
  271. package/dist/playground/assets/index-ByV4nfch.css +0 -1
  272. /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
  273. /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
@@ -22,7 +22,7 @@ Docs: `docs/guides/mcp-oauth.md`, `docs/reference/connections.md`,
22
22
 
23
23
  | Need | Use |
24
24
  | --- | --- |
25
- | Connector already in Cursor dashboard (Linear, Notion, …) | `defineConnection({ cursorAccount: true, servers: […] })` |
25
+ | Connector already in Cursor dashboard (Linear, Notion, …) | `defineConnection({ cursorAccount: true })` or `servers: "*"` for every connected server; `servers: […]` to allowlist |
26
26
  | Remote URL that speaks OAuth; host must hold tokens | `defineConnection({ url, oauth: true })` + this skill |
27
27
  | Static bearer / API key | `headers` / env on a plain `{ url }` connection |
28
28
 
@@ -1,16 +1,138 @@
1
1
  ---
2
2
  name: agentkit-setup-slack
3
3
  description: >-
4
- Set up a Slack channel for an agentkit project Socket Mode,
5
- manifests, doctor, smoke test, and opt-in Slack tool approvals (HITL).
6
- Read this skill when wiring Slack for an agentkit project.
4
+ Put an agentkit agent in Slack. Covers the required connection decision
5
+ (Cursor-hosted connection vs dedicated Socket Mode Slack app) with an
6
+ AskQuestion gate, then both setup paths end to end — cursorAccount
7
+ sign-in + smoke, or manifests, tokens, doctor, smoke test, and opt-in
8
+ Slack tool approvals (HITL). Read this skill when wiring Slack for an
9
+ agentkit project.
7
10
  ---
8
11
 
9
12
  # Setup Slack for agentkit
10
13
 
11
14
  Use this skill when a human asks to wire Slack to an agentkit agent.
12
15
 
13
- ## Goal
16
+ There are **two ways to connect**, and they differ mostly in setup cost:
17
+
18
+ - **Path A — Cursor-hosted connection**
19
+ (`slackChannel({ cursorAccount: true })`): rides the Cursor Slack
20
+ integration already installed in the workspace. No Slack app, no
21
+ manifests, no tokens, no Slack admin. Live in minutes.
22
+ - **Path B — Dedicated Slack app** (Socket Mode): the agent's own bot
23
+ user, created from generated manifests with an `xoxb-…` + `xapp-…`
24
+ token pair. Required for channel watching, approval buttons, or a
25
+ distinct bot handle. When the workspace is enrolled in Cursor's
26
+ **Add to Slack** (feature gate + a workspace-manager consent),
27
+ `agentkit slack create` provisions all of it in one command.
28
+
29
+ **Do not generate manifests or ask for tokens until the decision below
30
+ is made.** Defaulting into Path B buries the user in Slack admin work
31
+ (two app creations, token minting, env wiring) they may not need.
32
+
33
+ ## Step 0 — Choose the connection (required gate)
34
+
35
+ Walk the user through the tradeoffs before touching files:
36
+
37
+ | | A: Cursor-hosted | B: Dedicated Slack app |
38
+ | --- | --- | --- |
39
+ | Setup | Zero Slack admin — sign the host in (`agentkit login` / `CURSOR_API_KEY`) and serve | Enrolled in Cursor's Add to Slack: one `agentkit slack create` per env (app + install + tokens, signed-in host). Otherwise: a Slack admin creates **two** apps (dev + prod) from manifests, installs them, mints `xoxb-…` + `xapp-…` for each |
40
+ | Prereq | Cursor Slack integration installed in the workspace and linked to the Cursor account | Permission to create Slack apps; token storage/rotation is on you |
41
+ | How users reach it | `@Cursor <AgentName> …`, DMs to @Cursor, replies in claimed threads | Its own bot user: @mention, DM, channel invites |
42
+ | Identity | Posts as the Cursor app under the agent's name + icon | Own app name, avatar, and handle |
43
+ | Channel watching (dispatch without a mention) | Not available | `engagement.channelPosts` allowlist |
44
+ | Tool approvals / Block Kit interactivity | Not available (construction throws) | `toolApprovals: true` Approve/Deny cards |
45
+ | Reply delivery | Status while working, then one final post (no streaming edits) | Streaming replies with postMessage fallback |
46
+ | Multi-agent hosts | Any number of agents, unique `agentName` each, no extra Slack apps | One Slack app + token pair **per agent** |
47
+
48
+ Decision shortcuts — skip the question when the request already decides:
49
+
50
+ - Needs channel watching, Approve/Deny buttons, `onBlockAction`, or its
51
+ own bot user / handle → **Path B**. Say which requirement forced it.
52
+ - Workspace has no Cursor Slack integration and won't install it →
53
+ **Path B**.
54
+ - "Just get it into Slack", a first bring-up, or the user already said
55
+ to use the Cursor Slack integration, with none of the above → **Path A**.
56
+
57
+ ### Collect the decision with AskQuestion
58
+
59
+ Otherwise ask — one single-select question via the `AskQuestion` tool,
60
+ recommended option first so one click accepts it:
61
+
62
+ - **Cursor-hosted Slack connection (recommended)** — zero Slack setup;
63
+ sign in and mention `@Cursor <AgentName>`. No channel watching or
64
+ approval buttons.
65
+ - **Dedicated Slack app (Socket Mode)** — own bot user, channel
66
+ watching, approval buttons; needs a Slack admin to create two apps
67
+ and mint tokens.
68
+
69
+ If `AskQuestion` is unavailable in your environment, or the user does
70
+ not answer, **default to Path A (Cursor-hosted)**: state the assumption
71
+ in one line and proceed. It is the reversible choice — a dedicated app
72
+ can be added later as a *second* channel file without losing anything.
73
+ Never mix both in one channel definition: `cursorAccount: true` rejects
74
+ `credentials` / `envPrefix` at construction.
75
+
76
+ ## Path A — Cursor-hosted connection
77
+
78
+ Goal: mention `@Cursor <AgentName>` in the linked workspace and get a
79
+ threaded reply. No Slack app, no tokens, no manifests.
80
+
81
+ ### A1 — Author the channel (agent)
82
+
83
+ Write `agent/channels/slack.ts` by hand — do **not** run
84
+ `agentkit slack init` (that scaffolds the Path B manifests + env):
85
+
86
+ ```ts
87
+ import { slackChannel } from "@cursor/july/channels/slack";
88
+
89
+ export default slackChannel({
90
+ cursorAccount: true,
91
+ agentName: "MyAgent", // single token, no whitespace; prefer PascalCase
92
+ agentIcon: { emoji: ":robot_face:" },
93
+ });
94
+ ```
95
+
96
+ `agentName` defaults from the mount slug (PascalCase) and must be a
97
+ single token — string literals with spaces fail typecheck; runtime
98
+ values throw at construction/serve. Names must be unique on the host;
99
+ an unmatched `@Cursor <name>` stays with Cursor's normal Slack agent.
100
+
101
+ ### A2 — Sign in + serve (agent)
102
+
103
+ ```bash
104
+ agentkit login # or export CURSOR_API_KEY
105
+ agentkit dev .
106
+ ```
107
+
108
+ Confirm log: `[agentkit] cursor-slack: relaying Slack for <AgentName> …`.
109
+ An unsigned host fails `serve` with a sign-in hint naming the channel.
110
+
111
+ ### A3 — Smoke (human)
112
+
113
+ 1. In the Slack workspace linked to the Cursor account, post
114
+ `@Cursor MyAgent <question>` (or DM @Cursor)
115
+ 2. Confirm a working status while the turn runs, then a threaded reply
116
+ posted under the agent's name and icon
117
+ 3. Reply in the thread without re-mentioning — it stays with the agent
118
+
119
+ ### Path A limits (enforced at construction)
120
+
121
+ - `credentials` / `envPrefix` → throw (author a second channel file for
122
+ a dedicated app instead)
123
+ - `engagement.channelPosts`, `toolApprovals`, `interactivity`,
124
+ `onBlockAction` → throw (Socket Mode only)
125
+ - `engagement.mentions: false` / `directMessages: false` → throw (the
126
+ relay always delivers those)
127
+ - Streaming is forced off: rotating status while thinking, one final
128
+ reply post
129
+
130
+ When one of these limits becomes a real requirement, keep the
131
+ cursor-account channel and add a Path B channel file beside it.
132
+
133
+ ## Path B — Dedicated Slack app (Socket Mode)
134
+
135
+ Goal:
14
136
 
15
137
  - Importable **dev** and **prod** Slack app manifests (Socket Mode on)
16
138
  - App-level Token (`xapp-…`, `connections:write`) + Bot token (`xoxb-…`)
@@ -18,10 +140,40 @@ Use this skill when a human asks to wire Slack to an agentkit agent.
18
140
  - Thread ↔ session via `continuationToken = channelId:threadTs`
19
141
  - One Slack app (token pair) **per agent** when serving multiple demos
20
142
 
21
- ## Phases
22
-
23
143
  Update `.agent-serve/slack/setup-status.json` as you complete phases.
24
144
 
145
+ ### Provisioned variant — `agentkit slack create` (primary when enrolled)
146
+
147
+ When the Slack workspace is enrolled in Cursor's **Add to Slack**
148
+ (the provisioning feature gate plus a workspace-manager consent), one
149
+ command collapses Phases 0–3 — manifest import, app creation, install,
150
+ and token minting:
151
+
152
+ ```bash
153
+ agentkit slack create --dir <agent> # dev app (default)
154
+ agentkit slack create --dir <agent> --prod # prod app
155
+ ```
156
+
157
+ `create` scaffolds `agent/channels/slack.ts` + the `.agent-serve/slack`
158
+ files when missing (never overwriting existing ones), runs the browser
159
+ consent flow when the workspace is not connected yet, provisions +
160
+ installs the app, writes `<PREFIX>_SLACK_BOT_TOKEN` /
161
+ `<PREFIX>_SLACK_APP_TOKEN` into `<dir>/.env.local` (replacing stale
162
+ lines), and finishes with `doctor`. It needs a signed-in host
163
+ (`agentkit login` / `CURSOR_API_KEY`). Pass `--slack-team <id>` when
164
+ several workspaces are connected. If the install lands in
165
+ `pending_admin_approval`, a Slack workspace admin must approve the
166
+ printed link — then re-run `create` to finish and mint the tokens.
167
+ `agentkit slack destroy` deletes the provisioned app (tokens left in
168
+ `.env.local` simply stop working). Skip to Phase 4 when `create`
169
+ succeeds — `dev`/`serve` load the agent's `.env.local` (real env wins).
170
+ Note: re-running `create` rebuilds the app manifest from the command's
171
+ flags, so any customization made outside the CLI resets to it.
172
+
173
+ If the command reports the feature gate is not enabled (the workspace
174
+ is not enrolled), fall back to the manual manifest-import flow below
175
+ (Phases 0–3).
176
+
25
177
  ### Phase 0 — Generate (agent)
26
178
 
27
179
  ```bash
@@ -38,6 +190,10 @@ Creates:
38
190
  Use `--no-prefix` for the shared `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` globals
39
191
  (single-agent only). Use `--prefix CUSTOM` to override the default prefix.
40
192
 
193
+ These files deliberately omit `settings.managed_app_settings` — Slack accepts
194
+ that block only from a manager app's user token, so it belongs to the
195
+ `slack create` path, not to a manifest a human pastes in.
196
+
41
197
  ### Phase 1 — Create apps (human)
42
198
 
43
199
  1. Open https://api.slack.com/apps → **Create New App** → **From a manifest**
@@ -92,7 +248,7 @@ Human:
92
248
 
93
249
  ## Features (defaults ON)
94
250
 
95
- - Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback
251
+ - Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback (Path B; Path A posts one final reply)
96
252
  - Thinking / task cards for tool calls
97
253
  - `assistant.threads.setStatus` with rotating `loading_messages`
98
254
  - Thread title + suggested prompts
@@ -101,7 +257,7 @@ Human:
101
257
 
102
258
  If a Slack API is unavailable (`method_not_found` / `missing_scope`), the pack falls back to postMessage + setStatus.
103
259
 
104
- ## Engagement: mentions by default, channel watch opt-in
260
+ ## Engagement: mentions by default, channel watch opt-in (Path B only)
105
261
 
106
262
  The pack dispatches only on `app_mention` + DMs unless told otherwise —
107
263
  summoned, never proactive. To watch channels (dispatch on new posts without
@@ -128,7 +284,7 @@ bot to each watched channel. Posts mentioning the bot stay on the
128
284
  thread. `engagement.mentions` / `engagement.directMessages` can be set to
129
285
  `false` to turn those surfaces off.
130
286
 
131
- ## Tool approvals over Slack (opt-in HITL)
287
+ ## Tool approvals over Slack (opt-in HITL, Path B only)
132
288
 
133
289
  Tools with `needsApproval` park until a human approves or denies. To route
134
290
  that through Slack, set `toolApprovals: true` on `slackChannel` — it posts
@@ -163,15 +319,25 @@ Requirements and caveats:
163
319
 
164
320
  ## Notes
165
321
 
166
- - Dev and prod are **separate Slack apps** (separate tokens).
167
- - Socket Mode only no Events HTTP / Request URL path.
322
+ - Dev and prod are **separate Slack apps** (separate tokens) on Path B.
323
+ Path A has no per-env app split the host's Cursor account decides
324
+ where it relays.
325
+ - Socket Mode / the Cursor relay are outbound only — no Events HTTP /
326
+ Request URL path on either connection.
168
327
  - Do not `await waitForCompletion()` inside Slack dispatch handlers — the pack uses `waitUntil`.
169
328
  - Do not share one token pair across multiple agents in the same process.
170
329
 
171
330
  ## CLI reference
172
331
 
332
+ `slack create` / `destroy` / `init` / `manifest` / `doctor` are Path B
333
+ tooling; Path A needs only `login`.
334
+
173
335
  ```bash
174
336
  agentkit slack setup
337
+ agentkit slack create --dir . # provision via Add to Slack (dev app)
338
+ agentkit slack create --dir . --prod # prod app
339
+ agentkit slack create --dir . --slack-team T0123ABCD
340
+ agentkit slack destroy --dir . [--prod] [--slack-team T0123ABCD]
175
341
  agentkit slack init --dir . --name "My Agent"
176
342
  agentkit slack init --dir . --no-prefix
177
343
  agentkit slack init --dir . --channel-posts # + message.channels/groups events
@@ -56,6 +56,7 @@ import {
56
56
  CLI_COMMAND_NAME as CLI,
57
57
  packageVersion,
58
58
  } from "../internal/distribution.js";
59
+ import { loadAgentLocalEnv } from "../internal/local-env.js";
59
60
  import { clearTerminal, stdoutPalette } from "../internal/terminal-style.js";
60
61
  import { cmdUpdate, warnIfOutdated } from "../internal/update-check.js";
61
62
  import type { AgentProject, AgentServeHandle } from "../types.js";
@@ -100,7 +101,9 @@ Usage:
100
101
  ${CLI} mcp oauth <connection> [--dir <path>] [--store] [--slug <slug>]
101
102
  ${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
102
103
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
103
- [--no-stream] [--no-wait]
104
+ [--strict] [--max-concurrency <n>]
105
+ [--junit <path>] [--artifacts <dir>|--no-artifacts]
106
+ [--skip-report] [--no-stream] [--no-wait]
104
107
  [--prod | --url <baseUrl>] [--slug <slug>]
105
108
  [--team <id>]
106
109
  ${CLI} eval status [runId] --prod|--url <baseUrl> [--dir <path>] [--json]
@@ -223,7 +226,8 @@ Commands:
223
226
  secrets Manage deployment secrets: set (upsert by name; NAMEs without
224
227
  =VALUE are prompted for with hidden input, or read from piped
225
228
  stdin), list (names + createdAt; never values), unset.
226
- slack Slack channel pack: init, manifests, doctor (see slack help).
229
+ slack Slack channel pack: create/destroy/icon (Cursor Add to Slack
230
+ provisioning), init, manifests, doctor (see slack help).
227
231
  github GitHub webhook pack: forward live deliveries, events, doctor
228
232
  (wraps \`gh webhook forward\`; see github help).
229
233
 
@@ -320,6 +324,13 @@ Options:
320
324
  --tag eval: only run cases with this tag (repeatable).
321
325
  --list eval: print discovered cases without running.
322
326
  --verbose eval: stream t.log lines and reply snippets.
327
+ --strict eval: a missed soft threshold (SCORED) also exits 1.
328
+ --max-concurrency eval: override evals.config.ts maxConcurrency.
329
+ --junit eval: write JUnit XML here for CI annotations.
330
+ --artifacts eval: run-artifact directory (default a timestamped dir
331
+ under <dir>/.agent-serve/evals/).
332
+ --no-artifacts eval: do not write run artifacts.
333
+ --skip-report eval: ignore reporters from evals.config.ts / eval files.
323
334
  --no-stream run/eval: silence live progress on stderr (JSON still prints).
324
335
  --json JSON output (init/info/chat/eval/logs/sessions/session/
325
336
  github; run defaults to JSON). logs: NDJSON progress/log
@@ -433,10 +444,14 @@ async function main(argv: string[]): Promise<number> {
433
444
  "base-url": { type: "string" },
434
445
  print: { type: "boolean", default: false },
435
446
  store: { type: "boolean", default: false },
447
+ remote: { type: "boolean", default: false },
436
448
  transport: { type: "string" },
437
449
  prefix: { type: "string" },
438
450
  "no-prefix": { type: "boolean", default: false },
439
451
  "channel-posts": { type: "boolean", default: false },
452
+ "slack-team": { type: "string" },
453
+ icon: { type: "string" },
454
+ force: { type: "boolean", default: false },
440
455
  repo: { type: "string", multiple: true },
441
456
  org: { type: "string" },
442
457
  "cursor-events": { type: "boolean", default: false },
@@ -456,6 +471,12 @@ async function main(argv: string[]): Promise<number> {
456
471
  all: { type: "boolean", default: false },
457
472
  "cursor-events-repo": { type: "string", multiple: true },
458
473
  "allow-domain": { type: "string", multiple: true },
474
+ strict: { type: "boolean", default: false },
475
+ junit: { type: "string" },
476
+ artifacts: { type: "string" },
477
+ "no-artifacts": { type: "boolean", default: false },
478
+ "skip-report": { type: "boolean", default: false },
479
+ "max-concurrency": { type: "string" },
459
480
  },
460
481
  });
461
482
  const dir = values.dir ?? ".";
@@ -483,6 +504,13 @@ async function main(argv: string[]): Promise<number> {
483
504
  prefix: values.prefix,
484
505
  noPrefix: values["no-prefix"],
485
506
  channelPosts: values["channel-posts"],
507
+ slackTeam: values["slack-team"],
508
+ prod: values.prod,
509
+ team: values.team,
510
+ apiKey: values["api-key"],
511
+ icon: values.icon,
512
+ force: values.force,
513
+ iconSource: positionals[0] === "icon" ? positionals[1] : undefined,
486
514
  });
487
515
  }
488
516
  case "github": {
@@ -545,6 +573,7 @@ async function main(argv: string[]): Promise<number> {
545
573
  print: values.print,
546
574
  json: values.json,
547
575
  store: values.store,
576
+ remote: values.remote,
548
577
  oauthConnection: mcpSub === "oauth" ? positionals[1] : undefined,
549
578
  });
550
579
  }
@@ -754,6 +783,11 @@ async function main(argv: string[]): Promise<number> {
754
783
  stateRoot: values["state-root"],
755
784
  timeoutMs: parseTimeoutMs(values["timeout-ms"]),
756
785
  stream: values["no-stream"] ? false : undefined,
786
+ strict: values.strict,
787
+ junit: values.junit,
788
+ artifacts: values["no-artifacts"] ? false : values.artifacts,
789
+ skipReport: values["skip-report"],
790
+ maxConcurrency: parseMaxConcurrency(values["max-concurrency"]),
757
791
  })
758
792
  );
759
793
  }
@@ -1016,6 +1050,16 @@ async function main(argv: string[]): Promise<number> {
1016
1050
  }
1017
1051
  const dev = command === "dev" || values.dev === true;
1018
1052
  const style = stdoutPalette();
1053
+ // Real env wins (loadEnvFile never overrides). Runs inside start()
1054
+ // so dev's Enter-restart picks up tokens written since boot.
1055
+ const loadLocalEnv = (): void => {
1056
+ const loadedEnvFiles = loadAgentLocalEnv(serveDir);
1057
+ if (loadedEnvFiles.length > 0) {
1058
+ process.stdout.write(
1059
+ `${style.dim(`env: loaded ${loadedEnvFiles.join(", ")}`)}\n`
1060
+ );
1061
+ }
1062
+ };
1019
1063
  const serveOptions = {
1020
1064
  port,
1021
1065
  host: values.host,
@@ -1055,6 +1099,7 @@ async function main(argv: string[]): Promise<number> {
1055
1099
  };
1056
1100
 
1057
1101
  const start = async (): Promise<AgentServeHandle> => {
1102
+ loadLocalEnv();
1058
1103
  const next = await serve(serveDir, serveOptions);
1059
1104
  printServeBanner(next, bannerOpts);
1060
1105
  if (enterToRestart) {
@@ -1179,6 +1224,11 @@ function axFromOptions(
1179
1224
  once: partial.once,
1180
1225
  rawEvents: partial.rawEvents,
1181
1226
  print: partial.print,
1227
+ strict: partial.strict,
1228
+ junit: partial.junit,
1229
+ artifacts: partial.artifacts,
1230
+ skipReport: partial.skipReport,
1231
+ maxConcurrency: partial.maxConcurrency,
1182
1232
  };
1183
1233
  }
1184
1234
 
@@ -1217,6 +1267,17 @@ function parseTimeoutMs(value: string | undefined): number | undefined {
1217
1267
  return n;
1218
1268
  }
1219
1269
 
1270
+ function parseMaxConcurrency(value: string | undefined): number | undefined {
1271
+ if (value === undefined) {
1272
+ return undefined;
1273
+ }
1274
+ const n = Number.parseInt(value, 10);
1275
+ if (Number.isNaN(n) || n <= 0) {
1276
+ throw new Error(`invalid --max-concurrency: ${value}`);
1277
+ }
1278
+ return n;
1279
+ }
1280
+
1220
1281
  /**
1221
1282
  * Load mounts the same way `serve` does (multi-agent layout by default).
1222
1283
  */
@@ -350,7 +350,14 @@ export async function postMessage(args: {
350
350
  base.thread_ts = args.threadTs;
351
351
  }
352
352
  const hasBlocks = args.blocks !== undefined && args.blocks.length > 0;
353
- const usePlainText = args.plainText === true || hasBlocks;
353
+ // User-group mentions (`<!subteam^…>`) and Slack mrkdwn links
354
+ // (`<https://…|label>`) only render correctly via mrkdwn `text`,
355
+ // not `markdown_text`.
356
+ const usePlainText =
357
+ args.plainText === true ||
358
+ hasBlocks ||
359
+ args.text.includes("<!subteam^") ||
360
+ /<https?:\/\/[^|>]+\|/.test(args.text);
354
361
 
355
362
  let response: Record<string, unknown>;
356
363
  if (usePlainText) {
@@ -451,8 +458,14 @@ export async function updateMessage(args: {
451
458
  channel: args.channel,
452
459
  ts: args.ts,
453
460
  };
454
- if (args.blocks !== undefined || args.plainText === true) {
455
- // Blocks / plainText force the legacy text path (same as postMessage).
461
+ if (
462
+ args.blocks !== undefined ||
463
+ args.plainText === true ||
464
+ args.text.includes("<!subteam^") ||
465
+ /<https?:\/\/[^|>]+\|/.test(args.text)
466
+ ) {
467
+ // Blocks / plainText / user-group mentions / mrkdwn links force the
468
+ // legacy text path (same as postMessage).
456
469
  return callSlackApiSoft({
457
470
  botToken: args.botToken,
458
471
  operation: "chat.update",
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { samePrincipal } from "../../internal/auth.js";
6
6
  import type { AuthContext } from "../../types.js";
7
- import type { SlackBinding, SlackMessage } from "./types.js";
7
+ import type { SlackBinding, SlackBlockAction, SlackMessage } from "./types.js";
8
8
 
9
9
  /**
10
10
  * Build a thread-scoped principal so any participant in the same Slack
@@ -88,6 +88,21 @@ export function buildSlackInteractiveApprovalAuth(args: {
88
88
  return clickerAuth;
89
89
  }
90
90
 
91
+ /**
92
+ * Default auth for an `onBlockAction` dispatch: the click's thread-scoped
93
+ * principal with the clicker in attributes, so replies in the same thread
94
+ * continue the session and the clicker is the attributable user.
95
+ */
96
+ export function blockActionAuth(action: SlackBlockAction): AuthContext {
97
+ return buildSlackAuthContext({
98
+ channelId: action.channelId,
99
+ threadTs: action.threadTs,
100
+ teamId: action.teamId,
101
+ userId: action.userId,
102
+ userName: action.userName,
103
+ });
104
+ }
105
+
91
106
  export function defaultSlackAuth(
92
107
  message: SlackMessage,
93
108
  ctx: SlackBinding,
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Block Kit building helpers shared by card-building agents.
3
+ */
4
+
5
+ /** Slack cap for one section block's mrkdwn text. */
6
+ export const SLACK_SECTION_MRKDWN_MAX = 3000;
7
+
8
+ /** Slack caps for a native `table` block (one table per message). */
9
+ export const SLACK_TABLE_MAX_ROWS = 100;
10
+ export const SLACK_TABLE_MAX_COLUMNS = 20;
11
+
12
+ /**
13
+ * Pack lines into as few mrkdwn section blocks as fit the 3000-char cap,
14
+ * preserving order. Long lists (a full service fleet, a file inventory)
15
+ * overflow a single section and make `chat.postMessage` refuse the whole
16
+ * card with `invalid_blocks`.
17
+ */
18
+ export function mrkdwnSections(lines: readonly string[]): unknown[] {
19
+ const texts: string[] = [];
20
+ let current = "";
21
+ for (const raw of lines) {
22
+ // A single oversized line (one huge blob) hard-splits at the cap.
23
+ const parts: string[] = [];
24
+ for (
25
+ let i = 0;
26
+ i < Math.max(raw.length, 1);
27
+ i += SLACK_SECTION_MRKDWN_MAX
28
+ ) {
29
+ parts.push(raw.slice(i, i + SLACK_SECTION_MRKDWN_MAX));
30
+ }
31
+ for (const line of parts) {
32
+ const next = current === "" ? line : `${current}\n${line}`;
33
+ if (next.length > SLACK_SECTION_MRKDWN_MAX && current !== "") {
34
+ texts.push(current);
35
+ current = line;
36
+ } else {
37
+ current = next;
38
+ }
39
+ }
40
+ }
41
+ if (current !== "") {
42
+ texts.push(current);
43
+ }
44
+ return texts.map((text) => ({
45
+ type: "section",
46
+ text: { type: "mrkdwn", text },
47
+ }));
48
+ }
49
+
50
+ export type SlackTableAlign = "left" | "center" | "right";
51
+
52
+ export type SlackTableColumnSetting = {
53
+ align?: SlackTableAlign;
54
+ is_wrapped?: boolean;
55
+ };
56
+
57
+ /** Cell types accepted by Slack's native `table` block. */
58
+ export type SlackTableCell =
59
+ | { type: "raw_text"; text: string }
60
+ | {
61
+ type: "rich_text";
62
+ elements: Array<{
63
+ type: "rich_text_section";
64
+ elements: Array<
65
+ | { type: "text"; text: string }
66
+ | { type: "link"; url: string; text: string }
67
+ >;
68
+ }>;
69
+ };
70
+
71
+ export type SlackTableBlock = {
72
+ type: "table";
73
+ rows: SlackTableCell[][];
74
+ column_settings?: SlackTableColumnSetting[];
75
+ };
76
+
77
+ /** Plain text cell (header or body). */
78
+ export function slackRawTextCell(text: string): SlackTableCell {
79
+ return { type: "raw_text", text };
80
+ }
81
+
82
+ /** Linked cell via rich_text (preferred over mrkdwn `<url|label>` in tables). */
83
+ export function slackLinkCell(args: {
84
+ url: string;
85
+ text: string;
86
+ }): SlackTableCell {
87
+ return {
88
+ type: "rich_text",
89
+ elements: [
90
+ {
91
+ type: "rich_text_section",
92
+ elements: [{ type: "link", url: args.url, text: args.text }],
93
+ },
94
+ ],
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Build a native Slack `table` block. First row is the header. Slack allows
100
+ * at most one table block per message, 100 rows, and 20 columns — callers
101
+ * should keep tables short.
102
+ */
103
+ export function slackTableBlock(args: {
104
+ headers: readonly string[];
105
+ rows: readonly (readonly SlackTableCell[])[];
106
+ columnSettings?: readonly SlackTableColumnSetting[];
107
+ }): SlackTableBlock | undefined {
108
+ if (args.headers.length === 0) {
109
+ return undefined;
110
+ }
111
+ if (args.headers.length > SLACK_TABLE_MAX_COLUMNS) {
112
+ throw new Error(
113
+ `slackTableBlock: ${args.headers.length} columns exceeds Slack cap of ${SLACK_TABLE_MAX_COLUMNS}`
114
+ );
115
+ }
116
+ // Header + body rows share the 100-row cap.
117
+ if (args.rows.length + 1 > SLACK_TABLE_MAX_ROWS) {
118
+ throw new Error(
119
+ `slackTableBlock: ${args.rows.length + 1} rows exceeds Slack cap of ${SLACK_TABLE_MAX_ROWS}`
120
+ );
121
+ }
122
+ const columnCount = args.headers.length;
123
+ for (const [index, row] of args.rows.entries()) {
124
+ if (row.length !== columnCount) {
125
+ throw new Error(
126
+ `slackTableBlock: row ${index} has ${row.length} cells; expected ${columnCount}`
127
+ );
128
+ }
129
+ }
130
+ const headerRow = args.headers.map((header) => slackRawTextCell(header));
131
+ const block: SlackTableBlock = {
132
+ type: "table",
133
+ rows: [headerRow, ...args.rows.map((row) => [...row])],
134
+ };
135
+ if (args.columnSettings !== undefined && args.columnSettings.length > 0) {
136
+ block.column_settings = [...args.columnSettings];
137
+ }
138
+ return block;
139
+ }
@@ -137,6 +137,7 @@ export async function dispatchCursorSlackEnvelope(args: {
137
137
 
138
138
  const message: SlackMessage = {
139
139
  text: envelope.text,
140
+ body: envelope.text,
140
141
  markdown: slackTextToMarkdown(envelope.text),
141
142
  ts: envelope.messageTs,
142
143
  threadTs: envelope.threadTs,
@@ -26,6 +26,37 @@ import type {
26
26
  SlackSuggestedPrompt,
27
27
  } from "./types.js";
28
28
 
29
+ const SLACK_TURN_FAILURE_FALLBACK =
30
+ "I hit an error while handling your request. Please try again or rephrase.";
31
+ const SLACK_TURN_FAILURE_MAX_CHARS = 400;
32
+
33
+ /** Scrubbed turn.failed body for Slack (secrets / paths / URLs stripped). */
34
+ export function formatSlackTurnFailureMessage(raw: string | undefined): string {
35
+ const scrubbed = scrubSlackErrorDetail(raw?.trim() ?? "");
36
+ if (scrubbed === "") {
37
+ return SLACK_TURN_FAILURE_FALLBACK;
38
+ }
39
+ if (/^I hit an error/i.test(scrubbed)) {
40
+ return scrubbed;
41
+ }
42
+ return `I hit an error while handling your request: ${scrubbed}`;
43
+ }
44
+
45
+ function scrubSlackErrorDetail(message: string): string {
46
+ const scrubbed = message
47
+ .replace(/https?:\/\/\S+/gi, "[url]")
48
+ .replace(/\b(crsr_|xox[baprs]-|sk-|Bearer\s+)\S+/gi, "[redacted]")
49
+ .replace(/\/(?:home|Users|var\/folders)\/\S+/g, "[path]")
50
+ .replace(/\b[A-Za-z]:(?:\\|\/)[^\s]+/g, "[path]")
51
+ .replace(/\\\\[^\s]+/g, "[path]")
52
+ .replace(/\s+/g, " ")
53
+ .trim();
54
+ if (scrubbed.length <= SLACK_TURN_FAILURE_MAX_CHARS) {
55
+ return scrubbed;
56
+ }
57
+ return `${scrubbed.slice(0, SLACK_TURN_FAILURE_MAX_CHARS - 1)}…`;
58
+ }
59
+
29
60
  function resolveLoadingMessages(
30
61
  loadingMessages: readonly string[] | undefined
31
62
  ): string[] {
@@ -434,10 +465,7 @@ export function buildDefaultEvents(
434
465
  if (state.channelId === null || state.threadTs === null) {
435
466
  return;
436
467
  }
437
- // Do not echo engine/tool error text into Slack — it can leak
438
- // paths, tokens, or other sensitive detail to workspace members.
439
- const errorText =
440
- "I hit an error while handling your request. Please try again or rephrase.";
468
+ const errorText = formatSlackTurnFailureMessage(event.data.message);
441
469
 
442
470
  let delivered = false;
443
471
  if (state.streamTs !== null && state.streamTs !== "") {