@cursor/july 0.1.16 → 0.1.17

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 (236) 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/defaults.d.ts +2 -0
  12. package/dist/channels/slack/defaults.d.ts.map +1 -1
  13. package/dist/channels/slack/defaults.js +29 -3
  14. package/dist/channels/slack/dispatch.d.ts +3 -17
  15. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  16. package/dist/channels/slack/index.d.ts +1 -0
  17. package/dist/channels/slack/index.d.ts.map +1 -1
  18. package/dist/channels/slack/index.js +1 -0
  19. package/dist/channels/slack/interactive.d.ts +39 -3
  20. package/dist/channels/slack/interactive.d.ts.map +1 -1
  21. package/dist/channels/slack/interactive.js +152 -7
  22. package/dist/channels/slack/manifest.d.ts +3 -0
  23. package/dist/channels/slack/manifest.d.ts.map +1 -1
  24. package/dist/channels/slack/manifest.js +3 -0
  25. package/dist/channels/slack/setup.d.ts +4 -1
  26. package/dist/channels/slack/setup.d.ts.map +1 -1
  27. package/dist/channels/slack/setup.js +16 -2
  28. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  29. package/dist/channels/slack/slack-channel.js +24 -3
  30. package/dist/channels/slack/types.d.ts +77 -0
  31. package/dist/channels/slack/types.d.ts.map +1 -1
  32. package/dist/connections.d.ts +4 -1
  33. package/dist/connections.d.ts.map +1 -1
  34. package/dist/connections.js +4 -1
  35. package/dist/docs/404.html +2 -2
  36. package/dist/docs/ab.html +3 -3
  37. package/dist/docs/assets/{app.DjHC-Ggf.js → app.BbEXr2KO.js} +1 -1
  38. package/dist/docs/assets/chunks/@localSearchIndexroot.DCBsrfwP.js +1 -0
  39. package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.DBEi4HDp.js} +1 -1
  40. package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.DGfptcHw.js} +2 -2
  41. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
  42. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
  43. package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
  44. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
  45. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
  46. package/dist/docs/building-with-agents.html +3 -3
  47. package/dist/docs/concepts.html +3 -3
  48. package/dist/docs/deployment.html +3 -3
  49. package/dist/docs/evals.html +3 -3
  50. package/dist/docs/example-agents/approval-buddy.html +3 -3
  51. package/dist/docs/example-agents/benny.html +3 -3
  52. package/dist/docs/example-agents/bugbot.html +3 -3
  53. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  54. package/dist/docs/example-agents/codeowners-review.html +3 -3
  55. package/dist/docs/example-agents/concierge.html +3 -3
  56. package/dist/docs/example-agents/fsd.html +3 -3
  57. package/dist/docs/example-agents/index.html +3 -3
  58. package/dist/docs/example-agents/knowledge-base.html +3 -3
  59. package/dist/docs/example-agents/oncall.html +3 -3
  60. package/dist/docs/example-agents/security-reviewer.html +3 -3
  61. package/dist/docs/example-agents/slack-agent.html +3 -3
  62. package/dist/docs/example-agents/weather-agent.html +3 -3
  63. package/dist/docs/guides/agent-to-agent.html +3 -3
  64. package/dist/docs/guides/cloud-runtime.html +3 -3
  65. package/dist/docs/guides/github.html +3 -3
  66. package/dist/docs/guides/human-in-the-loop.html +3 -3
  67. package/dist/docs/guides/mcp-oauth.html +6 -5
  68. package/dist/docs/guides/slack.html +5 -5
  69. package/dist/docs/guides/webhooks.html +3 -3
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +3 -3
  72. package/dist/docs/index.html +3 -3
  73. package/dist/docs/quickstart.html +3 -3
  74. package/dist/docs/reference/agent-config.html +3 -3
  75. package/dist/docs/reference/channels.html +3 -3
  76. package/dist/docs/reference/cli.html +3 -3
  77. package/dist/docs/reference/connections.html +14 -7
  78. package/dist/docs/reference/hooks.html +3 -3
  79. package/dist/docs/reference/http-api.html +3 -3
  80. package/dist/docs/reference/instructions.html +3 -3
  81. package/dist/docs/reference/playground.html +3 -3
  82. package/dist/docs/reference/project-layout.html +3 -3
  83. package/dist/docs/reference/prompt.html +3 -3
  84. package/dist/docs/reference/schedules.html +3 -3
  85. package/dist/docs/reference/sessions.html +3 -3
  86. package/dist/docs/reference/skills.html +3 -3
  87. package/dist/docs/reference/subagents.html +3 -3
  88. package/dist/docs/reference/tools.html +3 -3
  89. package/dist/docs/scaffolding-agents.html +3 -3
  90. package/dist/docs/storage.html +3 -3
  91. package/dist/docs/troubleshooting.html +3 -3
  92. package/dist/evals/assertions.d.ts +190 -0
  93. package/dist/evals/assertions.d.ts.map +1 -0
  94. package/dist/evals/assertions.js +461 -0
  95. package/dist/evals/expect.d.ts +95 -0
  96. package/dist/evals/expect.d.ts.map +1 -0
  97. package/dist/evals/expect.js +194 -0
  98. package/dist/evals/judge.d.ts +133 -0
  99. package/dist/evals/judge.d.ts.map +1 -0
  100. package/dist/evals/judge.js +228 -0
  101. package/dist/evals/loaders.d.ts +51 -0
  102. package/dist/evals/loaders.d.ts.map +1 -0
  103. package/dist/evals/loaders.js +124 -0
  104. package/dist/evals/matchers.d.ts +95 -0
  105. package/dist/evals/matchers.d.ts.map +1 -0
  106. package/dist/evals/matchers.js +145 -0
  107. package/dist/evals/reporters.d.ts +70 -0
  108. package/dist/evals/reporters.d.ts.map +1 -0
  109. package/dist/evals/reporters.js +221 -0
  110. package/dist/evals/results.d.ts +103 -0
  111. package/dist/evals/results.d.ts.map +1 -0
  112. package/dist/evals/results.js +28 -0
  113. package/dist/evals/run-facts.d.ts +77 -0
  114. package/dist/evals/run-facts.d.ts.map +1 -0
  115. package/dist/evals/run-facts.js +187 -0
  116. package/dist/evals.d.ts +118 -31
  117. package/dist/evals.d.ts.map +1 -1
  118. package/dist/evals.js +46 -12
  119. package/dist/index.d.ts +1 -1
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +1 -1
  122. package/dist/internal/cli-ax.d.ts +27 -0
  123. package/dist/internal/cli-ax.d.ts.map +1 -1
  124. package/dist/internal/cli-ax.js +90 -12
  125. package/dist/internal/cli-mcp.d.ts +8 -0
  126. package/dist/internal/cli-mcp.d.ts.map +1 -1
  127. package/dist/internal/cli-mcp.js +98 -0
  128. package/dist/internal/cli-slack.d.ts +29 -1
  129. package/dist/internal/cli-slack.d.ts.map +1 -1
  130. package/dist/internal/cli-slack.js +777 -12
  131. package/dist/internal/deploy-client.d.ts +7 -0
  132. package/dist/internal/deploy-client.d.ts.map +1 -1
  133. package/dist/internal/deploy-client.js +9 -0
  134. package/dist/internal/discovery.d.ts.map +1 -1
  135. package/dist/internal/discovery.js +29 -9
  136. package/dist/internal/distribution.d.ts.map +1 -1
  137. package/dist/internal/distribution.js +2 -0
  138. package/dist/internal/eval-judge-model.d.ts +59 -0
  139. package/dist/internal/eval-judge-model.d.ts.map +1 -0
  140. package/dist/internal/eval-judge-model.js +131 -0
  141. package/dist/internal/eval-run-store.d.ts.map +1 -1
  142. package/dist/internal/eval-run-store.js +12 -20
  143. package/dist/internal/eval-runner.d.ts +31 -25
  144. package/dist/internal/eval-runner.d.ts.map +1 -1
  145. package/dist/internal/eval-runner.js +329 -126
  146. package/dist/internal/evals-client.d.ts.map +1 -1
  147. package/dist/internal/evals-client.js +4 -1
  148. package/dist/internal/local-env.d.ts +8 -0
  149. package/dist/internal/local-env.d.ts.map +1 -0
  150. package/dist/internal/local-env.js +47 -0
  151. package/dist/internal/playground/static.d.ts +3 -0
  152. package/dist/internal/playground/static.d.ts.map +1 -1
  153. package/dist/internal/playground/static.js +3 -0
  154. package/dist/internal/run-client.d.ts +15 -0
  155. package/dist/internal/run-client.d.ts.map +1 -1
  156. package/dist/internal/run-client.js +3 -2
  157. package/dist/internal/sdk-runner.d.ts +36 -1
  158. package/dist/internal/sdk-runner.d.ts.map +1 -1
  159. package/dist/internal/sdk-runner.js +65 -25
  160. package/dist/internal/server.d.ts.map +1 -1
  161. package/dist/internal/server.js +10 -1
  162. package/dist/internal/session-engine.d.ts +18 -5
  163. package/dist/internal/session-engine.d.ts.map +1 -1
  164. package/dist/internal/session-engine.js +72 -19
  165. package/dist/internal/slack-provision-client.d.ts +130 -0
  166. package/dist/internal/slack-provision-client.d.ts.map +1 -0
  167. package/dist/internal/slack-provision-client.js +284 -0
  168. package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
  169. package/dist/playground/assets/index-CidizGZv.css +1 -0
  170. package/dist/playground/assets/index-DNqirsrK.js +85 -0
  171. package/dist/playground/index.html +2 -2
  172. package/dist/types.d.ts +70 -4
  173. package/dist/types.d.ts.map +1 -1
  174. package/dist/types.js +26 -0
  175. package/docs/guides/mcp-oauth.md +10 -5
  176. package/docs/guides/slack.md +3 -2
  177. package/docs/reference/connections.md +28 -18
  178. package/package.json +20 -2
  179. package/skills/create-agent/SKILL.md +15 -7
  180. package/skills/debug/SKILL.md +3 -1
  181. package/skills/mcp-auth/SKILL.md +1 -1
  182. package/skills/setup-slack/SKILL.md +173 -11
  183. package/src/bin/agent-serve.ts +63 -2
  184. package/src/channels/slack/api.ts +16 -3
  185. package/src/channels/slack/auth.ts +16 -1
  186. package/src/channels/slack/blocks.ts +139 -0
  187. package/src/channels/slack/defaults.ts +32 -4
  188. package/src/channels/slack/dispatch.ts +3 -18
  189. package/src/channels/slack/index.ts +1 -0
  190. package/src/channels/slack/interactive.ts +183 -19
  191. package/src/channels/slack/manifest.ts +3 -0
  192. package/src/channels/slack/setup.ts +16 -2
  193. package/src/channels/slack/slack-channel.ts +37 -6
  194. package/src/channels/slack/types.ts +89 -0
  195. package/src/connections.ts +4 -1
  196. package/src/evals/assertions.ts +704 -0
  197. package/src/evals/expect.ts +272 -0
  198. package/src/evals/judge.ts +346 -0
  199. package/src/evals/loaders.ts +113 -0
  200. package/src/evals/matchers.ts +246 -0
  201. package/src/evals/reporters.ts +274 -0
  202. package/src/evals/results.ts +132 -0
  203. package/src/evals/run-facts.ts +274 -0
  204. package/src/evals.ts +205 -42
  205. package/src/index.ts +2 -0
  206. package/src/internal/cli-ax.ts +134 -15
  207. package/src/internal/cli-mcp.ts +118 -5
  208. package/src/internal/cli-slack.ts +993 -15
  209. package/src/internal/deploy-client.ts +10 -0
  210. package/src/internal/discovery.ts +31 -5
  211. package/src/internal/distribution.ts +2 -0
  212. package/src/internal/eval-judge-model.ts +148 -0
  213. package/src/internal/eval-run-store.ts +10 -14
  214. package/src/internal/eval-runner.ts +460 -214
  215. package/src/internal/evals-client.ts +5 -0
  216. package/src/internal/local-env.ts +53 -0
  217. package/src/internal/playground/static.ts +3 -0
  218. package/src/internal/run-client.ts +17 -1
  219. package/src/internal/sdk-runner.ts +103 -22
  220. package/src/internal/server.ts +10 -0
  221. package/src/internal/session-engine.ts +89 -21
  222. package/src/internal/slack-provision-client.ts +438 -0
  223. package/src/types.ts +92 -4
  224. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  225. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  226. package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
  227. package/dist/internal/json-dir-store.d.ts +0 -32
  228. package/dist/internal/json-dir-store.d.ts.map +0 -1
  229. package/dist/internal/persistence-coordinator.d.ts +0 -127
  230. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  231. package/dist/persistence.d.ts +0 -184
  232. package/dist/persistence.d.ts.map +0 -1
  233. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  234. package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
  235. package/dist/playground/assets/index-ByV4nfch.css +0 -1
  236. /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
@@ -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
@@ -92,7 +244,7 @@ Human:
92
244
 
93
245
  ## Features (defaults ON)
94
246
 
95
- - Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback
247
+ - Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback (Path B; Path A posts one final reply)
96
248
  - Thinking / task cards for tool calls
97
249
  - `assistant.threads.setStatus` with rotating `loading_messages`
98
250
  - Thread title + suggested prompts
@@ -101,7 +253,7 @@ Human:
101
253
 
102
254
  If a Slack API is unavailable (`method_not_found` / `missing_scope`), the pack falls back to postMessage + setStatus.
103
255
 
104
- ## Engagement: mentions by default, channel watch opt-in
256
+ ## Engagement: mentions by default, channel watch opt-in (Path B only)
105
257
 
106
258
  The pack dispatches only on `app_mention` + DMs unless told otherwise —
107
259
  summoned, never proactive. To watch channels (dispatch on new posts without
@@ -128,7 +280,7 @@ bot to each watched channel. Posts mentioning the bot stay on the
128
280
  thread. `engagement.mentions` / `engagement.directMessages` can be set to
129
281
  `false` to turn those surfaces off.
130
282
 
131
- ## Tool approvals over Slack (opt-in HITL)
283
+ ## Tool approvals over Slack (opt-in HITL, Path B only)
132
284
 
133
285
  Tools with `needsApproval` park until a human approves or denies. To route
134
286
  that through Slack, set `toolApprovals: true` on `slackChannel` — it posts
@@ -163,15 +315,25 @@ Requirements and caveats:
163
315
 
164
316
  ## Notes
165
317
 
166
- - Dev and prod are **separate Slack apps** (separate tokens).
167
- - Socket Mode only no Events HTTP / Request URL path.
318
+ - Dev and prod are **separate Slack apps** (separate tokens) on Path B.
319
+ Path A has no per-env app split the host's Cursor account decides
320
+ where it relays.
321
+ - Socket Mode / the Cursor relay are outbound only — no Events HTTP /
322
+ Request URL path on either connection.
168
323
  - Do not `await waitForCompletion()` inside Slack dispatch handlers — the pack uses `waitUntil`.
169
324
  - Do not share one token pair across multiple agents in the same process.
170
325
 
171
326
  ## CLI reference
172
327
 
328
+ `slack create` / `destroy` / `init` / `manifest` / `doctor` are Path B
329
+ tooling; Path A needs only `login`.
330
+
173
331
  ```bash
174
332
  agentkit slack setup
333
+ agentkit slack create --dir . # provision via Add to Slack (dev app)
334
+ agentkit slack create --dir . --prod # prod app
335
+ agentkit slack create --dir . --slack-team T0123ABCD
336
+ agentkit slack destroy --dir . [--prod] [--slack-team T0123ABCD]
175
337
  agentkit slack init --dir . --name "My Agent"
176
338
  agentkit slack init --dir . --no-prefix
177
339
  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
+ }
@@ -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 !== "") {