@cursor/july 0.1.26 → 0.1.28

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 (324) hide show
  1. package/dist/ab.d.ts +6 -0
  2. package/dist/ab.d.ts.map +1 -1
  3. package/dist/ab.js +1 -0
  4. package/dist/artifacts.d.ts +34 -0
  5. package/dist/artifacts.d.ts.map +1 -0
  6. package/dist/artifacts.js +57 -0
  7. package/dist/bin/agent-serve.js +43 -12
  8. package/dist/channels/github/github-channel.d.ts +1 -1
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +14 -1
  11. package/dist/channels/github/types.d.ts +6 -1
  12. package/dist/channels/github/types.d.ts.map +1 -1
  13. package/dist/channels/slack/api.d.ts +2 -1
  14. package/dist/channels/slack/api.d.ts.map +1 -1
  15. package/dist/channels/slack/api.js +1 -0
  16. package/dist/channels/slack/auth.d.ts +13 -13
  17. package/dist/channels/slack/auth.d.ts.map +1 -1
  18. package/dist/channels/slack/auth.js +19 -10
  19. package/dist/channels/slack/cursor-account.d.ts +2 -1
  20. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  21. package/dist/channels/slack/cursor-account.js +3 -1
  22. package/dist/channels/slack/defaults.d.ts +7 -4
  23. package/dist/channels/slack/defaults.d.ts.map +1 -1
  24. package/dist/channels/slack/defaults.js +14 -4
  25. package/dist/channels/slack/dispatch.d.ts +10 -1
  26. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  27. package/dist/channels/slack/dispatch.js +59 -21
  28. package/dist/channels/slack/external-policy.d.ts +27 -0
  29. package/dist/channels/slack/external-policy.d.ts.map +1 -0
  30. package/dist/channels/slack/external-policy.js +53 -0
  31. package/dist/channels/slack/index.d.ts +1 -0
  32. package/dist/channels/slack/index.d.ts.map +1 -1
  33. package/dist/channels/slack/index.js +1 -0
  34. package/dist/channels/slack/interactive.d.ts +8 -1
  35. package/dist/channels/slack/interactive.d.ts.map +1 -1
  36. package/dist/channels/slack/interactive.js +43 -15
  37. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  38. package/dist/channels/slack/slack-channel.js +22 -8
  39. package/dist/channels/slack/types.d.ts +16 -1
  40. package/dist/channels/slack/types.d.ts.map +1 -1
  41. package/dist/docs/404.html +2 -2
  42. package/dist/docs/ab.html +3 -3
  43. package/dist/docs/assets/{app.DYcC9FY-.js → app.CqjrYaT2.js} +1 -1
  44. package/dist/docs/assets/chunks/@localSearchIndexroot.VWQoPFJ1.js +1 -0
  45. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.ag-MwUel.js} +1 -1
  46. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.BS-DJWat.js} +2 -2
  47. package/dist/docs/assets/{guides_slack.md.D_xFKlnF.js → guides_slack.md.CjmJSvZS.js} +8 -4
  48. package/dist/docs/assets/guides_slack.md.CjmJSvZS.lean.js +1 -0
  49. package/dist/docs/assets/guides_webhooks.md.wSOYas3X.js +183 -0
  50. package/dist/docs/assets/{guides_webhooks.md.B1EswtUu.lean.js → guides_webhooks.md.wSOYas3X.lean.js} +1 -1
  51. package/dist/docs/assets/{index.md.Dfv5ic9t.js → index.md.CH_s5uZe.js} +1 -1
  52. package/dist/docs/assets/{reference_channels.md.DdmiKgqf.js → reference_channels.md.CDhTRfUz.js} +2 -2
  53. package/dist/docs/assets/{reference_channels.md.DdmiKgqf.lean.js → reference_channels.md.CDhTRfUz.lean.js} +1 -1
  54. package/dist/docs/building-with-agents.html +3 -3
  55. package/dist/docs/concepts.html +3 -3
  56. package/dist/docs/deployment.html +3 -3
  57. package/dist/docs/evals.html +3 -3
  58. package/dist/docs/example-agents/approval-buddy.html +3 -3
  59. package/dist/docs/example-agents/benny.html +3 -3
  60. package/dist/docs/example-agents/bugbot.html +3 -3
  61. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  62. package/dist/docs/example-agents/codeowners-review.html +3 -3
  63. package/dist/docs/example-agents/concierge.html +3 -3
  64. package/dist/docs/example-agents/fsd.html +3 -3
  65. package/dist/docs/example-agents/index.html +3 -3
  66. package/dist/docs/example-agents/knowledge-base.html +3 -3
  67. package/dist/docs/example-agents/oncall.html +3 -3
  68. package/dist/docs/example-agents/security-reviewer.html +3 -3
  69. package/dist/docs/example-agents/slack-agent.html +3 -3
  70. package/dist/docs/example-agents/weather-agent.html +3 -3
  71. package/dist/docs/guides/agent-to-agent.html +3 -3
  72. package/dist/docs/guides/cloud-runtime.html +3 -3
  73. package/dist/docs/guides/github.html +3 -3
  74. package/dist/docs/guides/human-in-the-loop.html +3 -3
  75. package/dist/docs/guides/mcp-oauth.html +3 -3
  76. package/dist/docs/guides/slack.html +11 -7
  77. package/dist/docs/guides/webhooks.html +138 -5
  78. package/dist/docs/hashmap.json +1 -1
  79. package/dist/docs/hillclimbing.html +3 -3
  80. package/dist/docs/index.html +5 -5
  81. package/dist/docs/quickstart.html +3 -3
  82. package/dist/docs/reference/agent-config.html +3 -3
  83. package/dist/docs/reference/channels.html +5 -5
  84. package/dist/docs/reference/cli.html +3 -3
  85. package/dist/docs/reference/connections.html +3 -3
  86. package/dist/docs/reference/hooks.html +3 -3
  87. package/dist/docs/reference/http-api.html +3 -3
  88. package/dist/docs/reference/instructions.html +3 -3
  89. package/dist/docs/reference/playground.html +3 -3
  90. package/dist/docs/reference/project-layout.html +3 -3
  91. package/dist/docs/reference/prompt.html +3 -3
  92. package/dist/docs/reference/schedules.html +3 -3
  93. package/dist/docs/reference/sessions.html +3 -3
  94. package/dist/docs/reference/skills.html +3 -3
  95. package/dist/docs/reference/subagents.html +3 -3
  96. package/dist/docs/reference/tools.html +3 -3
  97. package/dist/docs/scaffolding-agents.html +3 -3
  98. package/dist/docs/storage.html +3 -3
  99. package/dist/docs/troubleshooting.html +3 -3
  100. package/dist/evals/assertions.d.ts +10 -1
  101. package/dist/evals/assertions.d.ts.map +1 -1
  102. package/dist/evals/assertions.js +12 -0
  103. package/dist/evals.d.ts +1 -1
  104. package/dist/evals.d.ts.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +1 -0
  108. package/dist/internal/ab-fold.d.ts.map +1 -1
  109. package/dist/internal/ab-fold.js +4 -0
  110. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  111. package/dist/internal/ab-snapshot.js +1 -0
  112. package/dist/internal/artifacts-store.d.ts +45 -0
  113. package/dist/internal/artifacts-store.d.ts.map +1 -0
  114. package/dist/internal/artifacts-store.js +250 -0
  115. package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
  116. package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
  117. package/dist/internal/builtin-tools/artifacts.js +73 -0
  118. package/dist/internal/builtin-tools/index.d.ts +8 -1
  119. package/dist/internal/builtin-tools/index.d.ts.map +1 -1
  120. package/dist/internal/builtin-tools/index.js +15 -0
  121. package/dist/internal/cli-ax.d.ts +19 -0
  122. package/dist/internal/cli-ax.d.ts.map +1 -1
  123. package/dist/internal/cli-ax.js +196 -57
  124. package/dist/internal/cli-slack.d.ts.map +1 -1
  125. package/dist/internal/cli-slack.js +14 -6
  126. package/dist/internal/cursor-slack-relay.d.ts +2 -0
  127. package/dist/internal/cursor-slack-relay.d.ts.map +1 -1
  128. package/dist/internal/cursor-slack-relay.js +1 -0
  129. package/dist/internal/deploy-client.d.ts +2 -0
  130. package/dist/internal/deploy-client.d.ts.map +1 -1
  131. package/dist/internal/deploy-client.js +1 -0
  132. package/dist/internal/discovery.d.ts.map +1 -1
  133. package/dist/internal/discovery.js +91 -3
  134. package/dist/internal/distribution.d.ts.map +1 -1
  135. package/dist/internal/distribution.js +1 -0
  136. package/dist/internal/model-pricing.d.ts +49 -0
  137. package/dist/internal/model-pricing.d.ts.map +1 -0
  138. package/dist/internal/model-pricing.js +377 -0
  139. package/dist/internal/reminder-runner.d.ts.map +1 -1
  140. package/dist/internal/reminder-runner.js +1 -0
  141. package/dist/internal/schedule-runner.d.ts.map +1 -1
  142. package/dist/internal/schedule-runner.js +1 -0
  143. package/dist/internal/sdk-runner.d.ts.map +1 -1
  144. package/dist/internal/sdk-runner.js +9 -1
  145. package/dist/internal/server.d.ts.map +1 -1
  146. package/dist/internal/server.js +49 -5
  147. package/dist/internal/session-cost.d.ts +49 -0
  148. package/dist/internal/session-cost.d.ts.map +1 -0
  149. package/dist/internal/session-cost.js +175 -0
  150. package/dist/internal/session-engine.d.ts +35 -1
  151. package/dist/internal/session-engine.d.ts.map +1 -1
  152. package/dist/internal/session-engine.js +128 -7
  153. package/dist/internal/storage-coordinator.d.ts +15 -0
  154. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  155. package/dist/internal/storage-coordinator.js +52 -0
  156. package/dist/internal/trajectory.d.ts +9 -1
  157. package/dist/internal/trajectory.d.ts.map +1 -1
  158. package/dist/internal/trajectory.js +32 -5
  159. package/dist/playground/assets/index-Bul1WfrB.css +1 -0
  160. package/dist/playground/assets/index-CkN74U_L.js +87 -0
  161. package/dist/playground/index.html +2 -2
  162. package/dist/storage.d.ts +4 -0
  163. package/dist/storage.d.ts.map +1 -1
  164. package/dist/storage.js +4 -0
  165. package/dist/types.d.ts +164 -1
  166. package/dist/types.d.ts.map +1 -1
  167. package/docs/README.md +1 -0
  168. package/docs/guides/slack.md +32 -7
  169. package/docs/guides/webhooks.md +183 -0
  170. package/docs/reference/channels.md +2 -1
  171. package/package.json +26 -26
  172. package/skills/setup-slack/SKILL.md +3 -0
  173. package/src/ab.ts +7 -0
  174. package/src/bin/agent-serve.ts +42 -5
  175. package/src/channels/slack/auth.ts +29 -22
  176. package/src/channels/slack/cursor-account.ts +2 -0
  177. package/src/channels/slack/defaults.ts +24 -4
  178. package/src/channels/slack/dispatch.ts +42 -1
  179. package/src/channels/slack/external-policy.ts +62 -0
  180. package/src/channels/slack/index.ts +1 -0
  181. package/src/channels/slack/interactive.ts +42 -10
  182. package/src/channels/slack/slack-channel.ts +20 -3
  183. package/src/channels/slack/types.ts +12 -0
  184. package/src/internal/ab-fold.ts +4 -0
  185. package/src/internal/ab-snapshot.ts +1 -0
  186. package/src/internal/cli-ax.ts +205 -34
  187. package/src/internal/cursor-slack-relay.ts +3 -0
  188. package/src/internal/deploy-client.ts +3 -0
  189. package/src/internal/discovery.ts +8 -1
  190. package/src/internal/model-pricing.ts +426 -0
  191. package/src/internal/sdk-runner.ts +10 -2
  192. package/src/internal/server.ts +9 -5
  193. package/src/internal/session-cost.ts +231 -0
  194. package/src/internal/session-engine.ts +28 -0
  195. package/src/internal/trajectory.ts +45 -3
  196. package/src/types.ts +24 -1
  197. package/dist/channels/github/instrument.d.ts +0 -20
  198. package/dist/channels/github/instrument.d.ts.map +0 -1
  199. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  200. package/dist/docs/assets/guides_slack.md.D_xFKlnF.lean.js +0 -1
  201. package/dist/docs/assets/guides_webhooks.md.B1EswtUu.js +0 -50
  202. package/dist/internal/json-dir-store.d.ts +0 -32
  203. package/dist/internal/json-dir-store.d.ts.map +0 -1
  204. package/dist/internal/json-dir-store.js +0 -100
  205. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  206. package/dist/playground/assets/index-DqXdAFGa.js +0 -85
  207. package/src/bin/agent-serve.version.test.ts +0 -64
  208. package/src/channels/github/api.test.ts +0 -64
  209. package/src/channels/github/auth.test.ts +0 -105
  210. package/src/channels/github/cursor-account.test.ts +0 -204
  211. package/src/channels/github/forward.test.ts +0 -457
  212. package/src/channels/github/github.test.ts +0 -985
  213. package/src/channels/github/replay.test.ts +0 -179
  214. package/src/channels/slack/api.post-message.test.ts +0 -148
  215. package/src/channels/slack/approvals.test.ts +0 -328
  216. package/src/channels/slack/block-actions.test.ts +0 -469
  217. package/src/channels/slack/bot-mentions.test.ts +0 -267
  218. package/src/channels/slack/channel-watch.test.ts +0 -363
  219. package/src/channels/slack/cursor-account.test.ts +0 -301
  220. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  221. package/src/channels/slack/dispatch.test.ts +0 -875
  222. package/src/channels/slack/eval-directive.test.ts +0 -273
  223. package/src/channels/slack/message-body.test.ts +0 -54
  224. package/src/channels/slack/nudge-store.test.ts +0 -143
  225. package/src/channels/slack/slack.test.ts +0 -391
  226. package/src/channels/slack/stop.test.ts +0 -23
  227. package/src/channels/slack/thread-context.test.ts +0 -202
  228. package/src/evals/assertions.test.ts +0 -617
  229. package/src/evals/expect.test.ts +0 -144
  230. package/src/evals/judge.test.ts +0 -181
  231. package/src/evals/loaders.test.ts +0 -132
  232. package/src/evals/matchers.test.ts +0 -95
  233. package/src/evals/reporters.test.ts +0 -303
  234. package/src/evals/run-facts.test.ts +0 -259
  235. package/src/internal/ab-snapshot.test.ts +0 -325
  236. package/src/internal/approval-gate.test.ts +0 -49
  237. package/src/internal/approvals.integration.test.ts +0 -383
  238. package/src/internal/artifacts-store.test.ts +0 -283
  239. package/src/internal/authored-loaders.test.ts +0 -31
  240. package/src/internal/builtin-tools/artifacts.test.ts +0 -247
  241. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  242. package/src/internal/channel-route-schema.test.ts +0 -294
  243. package/src/internal/chat-attach.test.ts +0 -262
  244. package/src/internal/cli-deploy.test.ts +0 -1991
  245. package/src/internal/cli-docs.test.ts +0 -161
  246. package/src/internal/cli-mcp.test.ts +0 -789
  247. package/src/internal/cli-skills.test.ts +0 -133
  248. package/src/internal/cli-slack.test.ts +0 -1654
  249. package/src/internal/cloud-merge.test.ts +0 -74
  250. package/src/internal/cron.test.ts +0 -22
  251. package/src/internal/cursor/account-mcp.test.ts +0 -807
  252. package/src/internal/cursor/backend-client.test.ts +0 -591
  253. package/src/internal/cursor/credentials.test.ts +0 -351
  254. package/src/internal/cursor/github-credentials.test.ts +0 -136
  255. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  256. package/src/internal/cursor-account.integration.test.ts +0 -441
  257. package/src/internal/cursor-event-relay.test.ts +0 -746
  258. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  259. package/src/internal/cursor-slack-relay.test.ts +0 -525
  260. package/src/internal/deploy-source.test.ts +0 -111
  261. package/src/internal/discovery.artifact-tool.test.ts +0 -136
  262. package/src/internal/discovery.artifacts.test.ts +0 -119
  263. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  264. package/src/internal/discovery.concurrency.test.ts +0 -60
  265. package/src/internal/discovery.cursor-account.test.ts +0 -133
  266. package/src/internal/discovery.cwd.test.ts +0 -83
  267. package/src/internal/discovery.hosting.test.ts +0 -80
  268. package/src/internal/discovery.identity.test.ts +0 -44
  269. package/src/internal/docs-site.test.ts +0 -66
  270. package/src/internal/duration.test.ts +0 -29
  271. package/src/internal/eval-judge-model.test.ts +0 -187
  272. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  273. package/src/internal/eval-run-store.storage.test.ts +0 -211
  274. package/src/internal/eval-runner.http.test.ts +0 -403
  275. package/src/internal/eval-runner.run.test.ts +0 -928
  276. package/src/internal/evals-client.test.ts +0 -307
  277. package/src/internal/event-mapper.test.ts +0 -243
  278. package/src/internal/github-fanout.test.ts +0 -213
  279. package/src/internal/handleAgentServeTrigger.test.ts +0 -185
  280. package/src/internal/host-kv.test.ts +0 -82
  281. package/src/internal/host-platforms.test.ts +0 -126
  282. package/src/internal/http-channel.test.ts +0 -402
  283. package/src/internal/init-project.test.ts +0 -270
  284. package/src/internal/install-cursor-skills.test.ts +0 -262
  285. package/src/internal/local-env.test.ts +0 -120
  286. package/src/internal/log-ring.test.ts +0 -31
  287. package/src/internal/logs-client.test.ts +0 -350
  288. package/src/internal/mcp-endpoint.test.ts +0 -436
  289. package/src/internal/mcp-host.test.ts +0 -298
  290. package/src/internal/mcp-oauth.test.ts +0 -148
  291. package/src/internal/net.test.ts +0 -17
  292. package/src/internal/peer-connections.test.ts +0 -128
  293. package/src/internal/peer-mcp.integration.test.ts +0 -289
  294. package/src/internal/playground/toolchain.test.ts +0 -53
  295. package/src/internal/playground-cli.test.ts +0 -187
  296. package/src/internal/playground-proxy.test.ts +0 -376
  297. package/src/internal/prompt-context.integration.test.ts +0 -232
  298. package/src/internal/prompt-context.test.ts +0 -127
  299. package/src/internal/reminder-runner.test.ts +0 -432
  300. package/src/internal/reminder-store.test.ts +0 -53
  301. package/src/internal/request-headers.test.ts +0 -27
  302. package/src/internal/resolve-prod-target.test.ts +0 -787
  303. package/src/internal/resolved-connections.test.ts +0 -295
  304. package/src/internal/router.test.ts +0 -57
  305. package/src/internal/sdk-runner.test.ts +0 -290
  306. package/src/internal/server.artifacts.test.ts +0 -334
  307. package/src/internal/session-engine.artifacts.test.ts +0 -293
  308. package/src/internal/session-engine.coalesce.test.ts +0 -169
  309. package/src/internal/session-engine.concurrency.test.ts +0 -250
  310. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  311. package/src/internal/session-engine.interrupt.test.ts +0 -577
  312. package/src/internal/session-engine.storage.test.ts +0 -547
  313. package/src/internal/session-urls.test.ts +0 -28
  314. package/src/internal/sessions-client.test.ts +0 -518
  315. package/src/internal/storage-coordinator.test.ts +0 -517
  316. package/src/internal/tool-call.test.ts +0 -458
  317. package/src/internal/tool-result.test.ts +0 -52
  318. package/src/internal/trajectory.approvals.test.ts +0 -83
  319. package/src/internal/trajectory.subagents.test.ts +0 -198
  320. package/src/internal/turn-governor.test.ts +0 -137
  321. package/src/internal/update-check.test.ts +0 -485
  322. package/src/internal/workspace.test.ts +0 -207
  323. package/src/storage-backends/cursor-hosted.test.ts +0 -121
  324. /package/dist/docs/assets/{index.md.Dfv5ic9t.lean.js → index.md.CH_s5uZe.lean.js} +0 -0
@@ -177,6 +177,189 @@ swaps the default for `bearerAuth` on every channel that doesn't author
177
177
  its own chain, and `--allow-anonymous` swaps it for `allowAll()`
178
178
  (trusted networks only). Authored `auth` arrays always win over both.
179
179
 
180
+ ## Example: Linear as the control plane
181
+
182
+ Everything above composes into a working ticket-driven agent. This
183
+ example wires Linear to the agent: new issues and comments start or
184
+ resume sessions, and replies land back on the issue as comments. The
185
+ same shape works for any tracker with signed webhooks.
186
+
187
+ Create the webhook in Linear under Settings → API → "New webhook",
188
+ pointed at `https://<your-host>/<slug>/v1/channels/linear`, and copy
189
+ the signing secret. Linear requires a public HTTPS URL, so use a
190
+ tunnel during local development or test with signed fixtures (below).
191
+ Set three environment variables:
192
+
193
+ ```bash
194
+ LINEAR_WEBHOOK_SECRET=lin_wh_... # the webhook's signing secret
195
+ LINEAR_API_KEY=lin_api_... # posts replies as comments
196
+ LINEAR_AGENT_USER_ID=... # the API key's user: query { viewer { id } }
197
+ ```
198
+
199
+ `LINEAR_AGENT_USER_ID` matters: replies posted with the API key
200
+ trigger the Comment webhook again, so the channel must recognize and
201
+ skip its own comments. Without the guard, every reply starts another
202
+ turn.
203
+
204
+ Author `agent/channels/linear.ts`:
205
+
206
+ ```ts
207
+ import { Buffer } from "node:buffer";
208
+ import { createHmac, timingSafeEqual } from "node:crypto";
209
+ import { allowAll, defineChannel, POST } from "@cursor/july/channels";
210
+ import { z } from "zod";
211
+
212
+ const secret = process.env.LINEAR_WEBHOOK_SECRET ?? "";
213
+ const apiKey = process.env.LINEAR_API_KEY ?? "";
214
+ const agentUserId = process.env.LINEAR_AGENT_USER_ID ?? "";
215
+
216
+ interface LinearWebhook {
217
+ action: string;
218
+ type: string;
219
+ url?: string;
220
+ webhookTimestamp: number;
221
+ data: {
222
+ id: string;
223
+ title?: string;
224
+ description?: string;
225
+ body?: string;
226
+ issueId?: string;
227
+ userId?: string;
228
+ };
229
+ }
230
+
231
+ // Linear signs the raw body: hex HMAC-SHA256 in `Linear-Signature`.
232
+ function verified(rawBody: string, header: string | null): boolean {
233
+ if (secret === "" || header === null) {
234
+ return false;
235
+ }
236
+ const expected = createHmac("sha256", secret).update(rawBody).digest();
237
+ const received = Buffer.from(header, "hex");
238
+ return (
239
+ received.length === expected.length && timingSafeEqual(received, expected)
240
+ );
241
+ }
242
+
243
+ async function postComment(issueId: string, body: string): Promise<void> {
244
+ const response = await fetch("https://api.linear.app/graphql", {
245
+ method: "POST",
246
+ headers: { "content-type": "application/json", authorization: apiKey },
247
+ body: JSON.stringify({
248
+ query:
249
+ "mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success } }",
250
+ variables: { input: { issueId, body } },
251
+ }),
252
+ });
253
+ if (!response.ok) {
254
+ throw new Error(`commentCreate failed: ${response.status}`);
255
+ }
256
+ }
257
+
258
+ function issueIdFromToken(token: string | null): string | null {
259
+ if (token === null || !token.startsWith("linear:")) {
260
+ return null;
261
+ }
262
+ return token.slice("linear:".length);
263
+ }
264
+
265
+ export default defineChannel({
266
+ // The HMAC check is the request auth, so admit everything at the route.
267
+ auth: [allowAll()],
268
+ routes: [
269
+ POST("/", {
270
+ description: "Linear webhook ingress",
271
+ // The payload shape varies by `Linear-Event`; parse after verifying.
272
+ bodySchema: z.unknown(),
273
+ handler: async (request, { send, waitUntil }) => {
274
+ const rawBody = await request.text();
275
+ if (!verified(rawBody, request.headers.get("linear-signature"))) {
276
+ return Response.json({ ok: false }, { status: 401 });
277
+ }
278
+ const event = JSON.parse(rawBody) as LinearWebhook;
279
+ // Reject stale deliveries to guard against replay.
280
+ if (Math.abs(Date.now() - event.webhookTimestamp) > 60_000) {
281
+ return Response.json({ ok: false }, { status: 401 });
282
+ }
283
+ // Skip the agent's own comments so replies don't re-trigger it.
284
+ if (event.type === "Comment" && event.data.userId === agentUserId) {
285
+ return Response.json({ ok: true });
286
+ }
287
+
288
+ // New issue → new session. New comment → follow-up on the same
289
+ // session, keyed by issue id through the continuation token.
290
+ let issueId: string | undefined;
291
+ let message: string | undefined;
292
+ if (event.type === "Issue" && event.action === "create") {
293
+ issueId = event.data.id;
294
+ message = `New Linear issue: ${event.data.title}\n\n${
295
+ event.data.description ?? ""
296
+ }\n${event.url ?? ""}`;
297
+ } else if (event.type === "Comment" && event.action === "create") {
298
+ issueId = event.data.issueId;
299
+ message = event.data.body;
300
+ }
301
+ if (issueId === undefined || message === undefined) {
302
+ return Response.json({ ok: true });
303
+ }
304
+
305
+ // Linear retries on any non-200 and times out after five
306
+ // seconds: ACK now, run the turn in the background.
307
+ waitUntil(send(message, { continuationToken: `linear:${issueId}` }));
308
+ return Response.json({ ok: true });
309
+ },
310
+ }),
311
+ ],
312
+ events: {
313
+ async "message.completed"(event, channel) {
314
+ if (event.data.finishReason === "tool_call" || event.data.text === "") {
315
+ return;
316
+ }
317
+ const issueId = issueIdFromToken(channel.continuationToken);
318
+ if (issueId !== null) {
319
+ await postComment(issueId, event.data.text);
320
+ }
321
+ },
322
+ async "turn.failed"(event, channel) {
323
+ const issueId = issueIdFromToken(channel.continuationToken);
324
+ if (issueId !== null) {
325
+ await postComment(issueId, `Turn failed: ${event.data.message}`);
326
+ }
327
+ },
328
+ },
329
+ });
330
+ ```
331
+
332
+ The channel handles ingress and reply delivery deterministically. To
333
+ let the model read and update Linear during the turn (search related
334
+ issues, change state, assign), add the Linear MCP connection alongside
335
+ it:
336
+
337
+ ```ts
338
+ // agent/mcp-connections/linear.ts
339
+ import { defineConnection } from "@cursor/july/connections";
340
+
341
+ // Uses the signed-in Cursor account's Linear connector.
342
+ export default defineConnection({ cursorAccount: true, servers: ["Linear"] });
343
+ ```
344
+
345
+ See [MCP connections](../reference/connections.md) for the direct
346
+ `https://mcp.linear.app/mcp` form when the host isn't signed in to
347
+ Cursor.
348
+
349
+ To test without a public URL, save a payload from the webhook's
350
+ delivery log (or the sample in
351
+ [Linear's webhook docs](https://linear.app/developers/webhooks)) under
352
+ `fixtures/`, refresh its `webhookTimestamp`, and sign it yourself:
353
+
354
+ ```bash
355
+ SIG=$(node -e 'const {createHmac}=require("node:crypto");const fs=require("node:fs");
356
+ process.stdout.write(createHmac("sha256",process.env.LINEAR_WEBHOOK_SECRET)
357
+ .update(fs.readFileSync("fixtures/issue-create.json")).digest("hex"))')
358
+ curl -X POST http://127.0.0.1:3000/<slug>/v1/channels/linear/ \
359
+ -H 'content-type: application/json' -H "linear-signature: $SIG" \
360
+ --data-binary @fixtures/issue-create.json
361
+ ```
362
+
180
363
  ## Test a channel
181
364
 
182
365
  Start with curl and saved payloads under `fixtures/`. The playground's
@@ -174,7 +174,8 @@ declares `[localDevStrict()]` stays loopback-only even on an
174
174
  ## First class channels
175
175
 
176
176
  **Slack** (`@cursor/july/channels/slack`): Socket Mode
177
- transport, streaming replies, engagement rules, approval cards. Author
177
+ transport, streaming replies, engagement rules, approval cards, and a
178
+ default block on Slack Connect / guest / other-workspace senders. Author
178
179
  `agent/channels/slack.ts` with `slackChannel()`. Guide:
179
180
  [Slack](../guides/slack.md).
180
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -190,6 +190,24 @@
190
190
  "default": "./package.json"
191
191
  }
192
192
  },
193
+ "scripts": {
194
+ "build": "tsgo -p tsconfig.build.json && pnpm run build:playground && pnpm run docs:build",
195
+ "build:playground": "vite build --config playground/vite.config.ts",
196
+ "build:playground:central": "vite build --mode central --config playground/vite.config.ts",
197
+ "dev:playground": "vite --config playground/vite.config.ts",
198
+ "check": "biome check src && tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
199
+ "docs:dev": "vitepress dev docs",
200
+ "docs:build": "vitepress build docs",
201
+ "docs:preview": "vitepress preview docs",
202
+ "fix": "biome check --write --unsafe src",
203
+ "test": "vitest run --passWithNoTests",
204
+ "type-check": "tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
205
+ "build-dev": "echo noop",
206
+ "build-types": "tsgo -p tsconfig.build.json --emitDeclarationOnly",
207
+ "verify-publish": "node scripts/verify-publish-artifacts.mjs",
208
+ "smoke-publish": "node scripts/smoke-publish-artifact.mjs",
209
+ "publish-check": "pnpm run build && pnpm run verify-publish && pnpm run smoke-publish"
210
+ },
193
211
  "engines": {
194
212
  "node": ">=22.13"
195
213
  },
@@ -209,13 +227,14 @@
209
227
  "zod-to-json-schema": "^3.24.5"
210
228
  },
211
229
  "devDependencies": {
212
- "@base-ui/react": "1.4.0",
230
+ "@anysphere/ui": "workspace:*",
231
+ "@base-ui/react": "1.6.0",
213
232
  "@stylexjs/stylex": "0.18.3",
214
- "@stylexswc/rs-compiler": "^0.16.4",
233
+ "@stylexswc/rs-compiler": "^0.18.0",
215
234
  "@tanstack/react-virtual": "3.13.23",
216
235
  "@types/js-yaml": "4.0.9",
217
236
  "@types/ms": "^2.1.0",
218
- "@types/node": "22.14.1",
237
+ "@types/node": "catalog:",
219
238
  "@types/pg": "^8.16.0",
220
239
  "@types/react": "^19.0.0",
221
240
  "@types/react-dom": "^19.0.0",
@@ -228,29 +247,10 @@
228
247
  "react-markdown": "^10.1.0",
229
248
  "rehype-highlight": "^7.0.2",
230
249
  "remark-gfm": "^4.0.1",
231
- "typescript": "^5.8.0",
250
+ "typescript": "catalog:",
232
251
  "vite": "^7.3.0",
233
252
  "vitepress": "1.6.4",
234
253
  "vitest": "^4.0.18",
235
- "zustand": "^5.0.9",
236
- "@anysphere/ui": "1.0.0"
237
- },
238
- "scripts": {
239
- "build": "tsgo -p tsconfig.build.json && pnpm run build:playground && pnpm run docs:build",
240
- "build:playground": "vite build --config playground/vite.config.ts",
241
- "build:playground:central": "vite build --mode central --config playground/vite.config.ts",
242
- "dev:playground": "vite --config playground/vite.config.ts",
243
- "check": "biome check src && tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
244
- "docs:dev": "vitepress dev docs",
245
- "docs:build": "vitepress build docs",
246
- "docs:preview": "vitepress preview docs",
247
- "fix": "biome check --write --unsafe src",
248
- "test": "vitest run --passWithNoTests",
249
- "type-check": "tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
250
- "build-dev": "echo noop",
251
- "build-types": "tsgo -p tsconfig.build.json --emitDeclarationOnly",
252
- "verify-publish": "node scripts/verify-publish-artifacts.mjs",
253
- "smoke-publish": "node scripts/smoke-publish-artifact.mjs",
254
- "publish-check": "pnpm run build && pnpm run verify-publish && pnpm run smoke-publish"
254
+ "zustand": "^5.0.9"
255
255
  }
256
- }
256
+ }
@@ -97,6 +97,9 @@ export default slackChannel({
97
97
  single token — string literals with spaces fail typecheck; runtime
98
98
  values throw at construction/serve. Names must be unique on the host;
99
99
  an unmatched `@Cursor <name>` stays with Cursor's normal Slack agent.
100
+ External senders (Slack Connect / guests / other workspaces) are
101
+ blocked by default; see `policy.blockExternals` in
102
+ [docs/guides/slack.md](../../docs/guides/slack.md).
100
103
 
101
104
  ### A2 — Sign in + serve (agent)
102
105
 
package/src/ab.ts CHANGED
@@ -78,6 +78,12 @@ export interface ABMetrics {
78
78
  outputTokens: number;
79
79
  cacheReadTokens: number;
80
80
  cacheWriteTokens: number;
81
+ /**
82
+ * Sum of estimated turn costs in USD (the `cost.totalUsd` recorded on
83
+ * `turn.completed` events). Turns whose model has no known rates
84
+ * contribute 0.
85
+ */
86
+ costUsd: number;
81
87
  /** Sum of turn wall times (turn.started → turn.completed/failed). */
82
88
  wallTimeMs: number;
83
89
  /** Custom counters from {@link ABConfig.derive}. */
@@ -315,6 +321,7 @@ export function emptyABMetrics(): ABMetrics {
315
321
  outputTokens: 0,
316
322
  cacheReadTokens: 0,
317
323
  cacheWriteTokens: 0,
324
+ costUsd: 0,
318
325
  wallTimeMs: 0,
319
326
  custom: {},
320
327
  };
@@ -29,6 +29,7 @@ import {
29
29
  type AxCliOptions,
30
30
  cmdCall,
31
31
  cmdChat,
32
+ cmdCost,
32
33
  cmdEval,
33
34
  cmdEvalCancel,
34
35
  cmdEvalStatus,
@@ -55,6 +56,7 @@ import {
55
56
  } from "../internal/cli-deploy.js";
56
57
  import {
57
58
  CLI_COMMAND_NAME as CLI,
59
+ PACKAGE_NAME,
58
60
  packageVersion,
59
61
  } from "../internal/distribution.js";
60
62
  import { loadAgentLocalEnv } from "../internal/local-env.js";
@@ -83,7 +85,9 @@ Usage:
83
85
  ${CLI} sessions [--url <baseUrl> | --prod] [--json]
84
86
  [--slug <slug>] [--team <id>] [--dir <path>]
85
87
  ${CLI} session <sessionId> [--url <baseUrl> | --prod]
86
- [--json | --text | --events]
88
+ [--json | --text | --events] [--out <file.ndjson>]
89
+ [--slug <slug>] [--team <id>] [--dir <path>]
90
+ ${CLI} cost [sessionId] [--url <baseUrl> | --prod] [--json]
87
91
  [--slug <slug>] [--team <id>] [--dir <path>]
88
92
  ${CLI} playground [--url <baseUrl> | --prod] [--session <id>] [--print]
89
93
  [--slug <slug>] [--team <id>] [--dir <path>]
@@ -106,11 +110,12 @@ Usage:
106
110
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
107
111
  [--strict] [--max-concurrency <n>]
108
112
  [--junit <path>] [--artifacts <dir>|--no-artifacts]
113
+ [--out <file.json>]
109
114
  [--skip-report] [--no-stream] [--no-wait]
110
115
  [--prod | --url <baseUrl>] [--slug <slug>]
111
116
  [--team <id>]
112
117
  ${CLI} eval status [runId] --prod|--url <baseUrl> [--dir <path>] [--json]
113
- [--slug <slug>] [--team <id>]
118
+ [--out <file.json>] [--slug <slug>] [--team <id>]
114
119
  ${CLI} eval cancel <runId> --prod|--url <baseUrl> [--dir <path>] [--json]
115
120
  [--slug <slug>] [--team <id>]
116
121
  ${CLI} info [--dir <path>] [--json]
@@ -161,6 +166,10 @@ Commands:
161
166
  session Show one session. Default: text trajectory. --json for trajectory
162
167
  JSON; --events for the raw event list. To continue interactively,
163
168
  use \`${CLI} resume [id]\`.
169
+ cost Pull cost metrics. With a session id: per-turn token usage and
170
+ estimated cost for that session. Without one: one row per
171
+ session plus a total. Costs are the engine's recorded estimates
172
+ from turn.completed events; --json for machine-readable reports.
164
173
  playground Open the web playground. --prod starts a loopback proxy that
165
174
  injects hosted engine credentials (browsers cannot set them).
166
175
  --print skips opening a browser. Ctrl-C stops the proxy.
@@ -384,7 +393,11 @@ Options:
384
393
  --comment github replay: comment body for comment events.
385
394
  --context github replay: commit-status context.
386
395
  --dry-run github replay: print synthesized payloads, don't POST.
387
- --out github replay: write synthesized payloads as fixtures.
396
+ --out eval / eval status <runId>: also write the full results
397
+ JSON to this path. session: write the raw NDJSON trace
398
+ there instead of printing (same format as run --events;
399
+ playground-openable). github replay: write synthesized
400
+ payloads as fixtures.
388
401
  `;
389
402
 
390
403
  async function main(argv: string[]): Promise<number> {
@@ -767,6 +780,7 @@ async function main(argv: string[]): Promise<number> {
767
780
  apiKey: values["api-key"],
768
781
  slug: values.slug,
769
782
  bearerToken: values["bearer-token"],
783
+ out: values.out,
770
784
  })
771
785
  );
772
786
  }
@@ -811,6 +825,7 @@ async function main(argv: string[]): Promise<number> {
811
825
  artifacts: values["no-artifacts"] ? false : values.artifacts,
812
826
  skipReport: values["skip-report"],
813
827
  maxConcurrency: parseMaxConcurrency(values["max-concurrency"]),
828
+ out: values.out,
814
829
  })
815
830
  );
816
831
  }
@@ -904,6 +919,23 @@ async function main(argv: string[]): Promise<number> {
904
919
  json: values.json,
905
920
  text: values.text,
906
921
  rawEvents: values["raw-events"],
922
+ out: values.out,
923
+ })
924
+ );
925
+ }
926
+
927
+ case "cost": {
928
+ return cmdCost(
929
+ positionals[0] ?? values["session-id"] ?? values.session,
930
+ axFromOptions({
931
+ dir,
932
+ url: values.url,
933
+ prod: values.prod,
934
+ team: values.team,
935
+ apiKey: values["api-key"],
936
+ slug: values.slug,
937
+ bearerToken: values["bearer-token"],
938
+ json: values.json,
907
939
  })
908
940
  );
909
941
  }
@@ -1267,6 +1299,7 @@ function axFromOptions(
1267
1299
  artifacts: partial.artifacts,
1268
1300
  skipReport: partial.skipReport,
1269
1301
  maxConcurrency: partial.maxConcurrency,
1302
+ out: partial.out,
1270
1303
  };
1271
1304
  }
1272
1305
 
@@ -1383,11 +1416,15 @@ function printServeBanner(
1383
1416
  ): void {
1384
1417
  const c = stdoutPalette();
1385
1418
  const agents = handle.agents;
1419
+ // Name the framework build in the boot line: hosted engines install the
1420
+ // published package at provision time, so the serve log is the only
1421
+ // durable record of which build a deployment's turns actually ran on.
1422
+ const versionTag = c.dim(`(${PACKAGE_NAME} ${packageVersion()})`);
1386
1423
  if (agents.length > 1) {
1387
- process.stdout.write(`${agents.length} agents listening\n`);
1424
+ process.stdout.write(`${agents.length} agents listening ${versionTag}\n`);
1388
1425
  } else {
1389
1426
  const info = projectInfo(agents[0]?.project ?? handle.project);
1390
- process.stdout.write(`${info.name} listening\n`);
1427
+ process.stdout.write(`${info.name} listening ${versionTag}\n`);
1391
1428
  }
1392
1429
 
1393
1430
  const playgroundUrl = resolvePlaygroundBannerUrl(handle, opts.noPlayground);
@@ -4,29 +4,39 @@
4
4
 
5
5
  import { samePrincipal } from "../../internal/auth.js";
6
6
  import type { AuthContext } from "../../types.js";
7
- import type { SlackBinding, SlackBlockAction, SlackMessage } from "./types.js";
7
+ import type {
8
+ SlackBinding,
9
+ SlackBlockAction,
10
+ SlackMessage,
11
+ SlackRespondTo,
12
+ } from "./types.js";
8
13
 
9
- /**
10
- * Build a thread-scoped principal so any participant in the same Slack
11
- * thread can continue the session. The triggering user is preserved in
12
- * attributes for message context.
13
- */
14
- export function buildSlackAuthContext(args: {
14
+ export type BuildSlackAuthArgs = {
15
15
  channelId: string;
16
16
  threadTs: string;
17
17
  teamId?: string;
18
18
  userId?: string;
19
19
  userName?: string;
20
20
  isBot?: boolean;
21
- /**
22
- * Engagement surface that started the session (e.g. `"channel_post"` for
23
- * the opt-in channel watch). Recorded as an attribute; the principal stays
24
- * thread-scoped so an @mention in the same thread continues the session.
25
- */
26
21
  engagement?: string;
27
- }): AuthContext {
22
+ /** Defaults to `"thread"`. `"author"` appends `:{userId}` to the principal. */
23
+ respondTo?: SlackRespondTo;
24
+ };
25
+
26
+ /**
27
+ * Build a Slack session principal.
28
+ *
29
+ * - `"thread"` (default): any participant can continue.
30
+ * - `"author"`: only the starter (`…:{userId}`), same shape approvals already honor.
31
+ */
32
+ export function buildSlackAuthContext(args: BuildSlackAuthArgs): AuthContext {
28
33
  const team = args.teamId ?? "unknown";
29
- const principalId = `slack:${team}:${args.channelId}:${args.threadTs}`;
34
+ const respondTo = args.respondTo ?? "thread";
35
+ const base = `slack:${team}:${args.channelId}:${args.threadTs}`;
36
+ const authorScoped =
37
+ respondTo === "author" && args.userId !== undefined && args.userId !== "";
38
+ const principalId = authorScoped ? `${base}:${args.userId}` : base;
39
+
30
40
  const attributes: Record<string, string> = {
31
41
  channel_id: args.channelId,
32
42
  thread_ts: args.threadTs,
@@ -106,7 +116,7 @@ export function blockActionAuth(action: SlackBlockAction): AuthContext {
106
116
  export function defaultSlackAuth(
107
117
  message: SlackMessage,
108
118
  ctx: SlackBinding,
109
- options?: { engagement?: string }
119
+ options?: { engagement?: string; respondTo?: SlackRespondTo }
110
120
  ): AuthContext | null {
111
121
  if (message.author === undefined) {
112
122
  return null;
@@ -114,14 +124,11 @@ export function defaultSlackAuth(
114
124
  return buildSlackAuthContext({
115
125
  channelId: ctx.slack.channelId,
116
126
  threadTs: ctx.slack.threadTs,
117
- ...(message.teamId === undefined ? {} : { teamId: message.teamId }),
127
+ teamId: message.teamId,
118
128
  userId: message.author.userId,
119
- ...(message.author.userName === undefined
120
- ? {}
121
- : { userName: message.author.userName }),
129
+ userName: message.author.userName,
122
130
  isBot: message.author.isBot,
123
- ...(options?.engagement === undefined
124
- ? {}
125
- : { engagement: options.engagement }),
131
+ engagement: options?.engagement,
132
+ respondTo: options?.respondTo,
126
133
  });
127
134
  }
@@ -172,6 +172,8 @@ export async function dispatchCursorSlackEnvelope(args: {
172
172
  log,
173
173
  evalCommandsEnabled: runtime.options.evalCommands !== false,
174
174
  cursorAgentName: envelope.agentName,
175
+ // Backend router already refused externals for this envelope.
176
+ blockExternals: false,
175
177
  ...(args.hasContinuationSession === undefined
176
178
  ? {}
177
179
  : { hasContinuationSession: args.hasContinuationSession }),
@@ -20,9 +20,11 @@ import { createStreamDeliveryStrategy } from "./live-delivery.js";
20
20
  import { defaultSlackLog, type SlackLog } from "./log.js";
21
21
  import { buildTaskChunkForResult, buildTaskChunksForCalls } from "./stream.js";
22
22
  import type {
23
+ SlackAccessPolicy,
23
24
  SlackChannelState,
24
25
  SlackCredentials,
25
26
  SlackMessageHandler,
27
+ SlackRespondTo,
26
28
  SlackSuggestedPrompt,
27
29
  } from "./types.js";
28
30
 
@@ -30,6 +32,19 @@ const SLACK_TURN_FAILURE_FALLBACK =
30
32
  "I hit an error while handling your request. Please try again or rephrase.";
31
33
  const SLACK_TURN_FAILURE_MAX_CHARS = 400;
32
34
 
35
+ export function resolveSlackRespondTo(
36
+ policy: SlackAccessPolicy | undefined
37
+ ): SlackRespondTo {
38
+ return policy?.respondTo === "author" ? "author" : "thread";
39
+ }
40
+
41
+ /** Defaults to true when omitted. */
42
+ export function resolveSlackBlockExternals(
43
+ policy: SlackAccessPolicy | undefined
44
+ ): boolean {
45
+ return policy?.blockExternals !== false;
46
+ }
47
+
33
48
  /** Scrubbed turn.failed body for Slack (secrets / paths / URLs stripped). */
34
49
  export function formatSlackTurnFailureMessage(raw: string | undefined): string {
35
50
  const scrubbed = scrubSlackErrorDetail(raw?.trim() ?? "");
@@ -68,29 +83,34 @@ function resolveLoadingMessages(
68
83
 
69
84
  /** Default mention/DM inbound handler (optional custom loading rotation). */
70
85
  export function createDefaultOnInbound(
71
- loadingMessages?: readonly string[]
86
+ loadingMessages?: readonly string[],
87
+ policy?: SlackAccessPolicy
72
88
  ): SlackMessageHandler {
73
89
  const rotating = resolveLoadingMessages(loadingMessages);
90
+ const respondTo = resolveSlackRespondTo(policy);
74
91
  return async (ctx, message) => {
75
92
  await ctx.thread.startTyping("Thinking…", rotating);
76
- const auth = defaultSlackAuth(message, ctx);
93
+ const auth = defaultSlackAuth(message, ctx, { respondTo });
77
94
  return auth === null ? null : { auth };
78
95
  };
79
96
  }
80
97
 
81
98
  /**
82
99
  * Default watched-channel-post handler (optional custom loading rotation).
83
- * Same thread-scoped principal as mentions, tagged with
100
+ * Same principal policy as mentions, tagged with
84
101
  * `engagement: "channel_post"` for auth policies.
85
102
  */
86
103
  export function createDefaultOnChannelPost(
87
- loadingMessages?: readonly string[]
104
+ loadingMessages?: readonly string[],
105
+ policy?: SlackAccessPolicy
88
106
  ): SlackMessageHandler {
89
107
  const rotating = resolveLoadingMessages(loadingMessages);
108
+ const respondTo = resolveSlackRespondTo(policy);
90
109
  return async (ctx, message) => {
91
110
  await ctx.thread.startTyping("Thinking…", rotating);
92
111
  const auth = defaultSlackAuth(message, ctx, {
93
112
  engagement: "channel_post",
113
+ respondTo,
94
114
  });
95
115
  return auth === null ? null : { auth };
96
116
  };