@cursor/july 0.1.39 → 0.1.41

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 (259) hide show
  1. package/AGENTS.md +4 -3
  2. package/dist/bin/agent-serve.js +28 -9
  3. package/dist/channels/slack/api.d.ts.map +1 -1
  4. package/dist/channels/slack/api.js +18 -8
  5. package/dist/channels/slack/attachments.d.ts +82 -0
  6. package/dist/channels/slack/attachments.d.ts.map +1 -0
  7. package/dist/channels/slack/attachments.js +520 -0
  8. package/dist/channels/slack/cursor-account.d.ts +7 -1
  9. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  10. package/dist/channels/slack/cursor-account.js +2 -2
  11. package/dist/channels/slack/defaults.d.ts.map +1 -1
  12. package/dist/channels/slack/defaults.js +26 -17
  13. package/dist/channels/slack/dispatch.d.ts +4 -3
  14. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  15. package/dist/channels/slack/dispatch.js +84 -21
  16. package/dist/channels/slack/inbound.d.ts +6 -0
  17. package/dist/channels/slack/inbound.d.ts.map +1 -1
  18. package/dist/channels/slack/inbound.js +48 -2
  19. package/dist/channels/slack/index.d.ts +1 -0
  20. package/dist/channels/slack/index.d.ts.map +1 -1
  21. package/dist/channels/slack/index.js +1 -0
  22. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  23. package/dist/channels/slack/live-delivery.js +16 -4
  24. package/dist/channels/slack/manifest.d.ts.map +1 -1
  25. package/dist/channels/slack/manifest.js +1 -0
  26. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  27. package/dist/channels/slack/slack-channel.js +7 -4
  28. package/dist/channels/slack/types.d.ts +49 -1
  29. package/dist/channels/slack/types.d.ts.map +1 -1
  30. package/dist/docs/404.html +2 -2
  31. package/dist/docs/ab.html +6 -6
  32. package/dist/docs/assets/{ab.md.6cLOW7--.js → ab.md.hdxsvX5M.js} +2 -2
  33. package/dist/docs/assets/{app.BQ8Hihdf.js → app.mIw24pb1.js} +1 -1
  34. package/dist/docs/assets/{building-with-agents.md.txrcGU2B.js → building-with-agents.md.PeZaZA1P.js} +1 -1
  35. package/dist/docs/assets/chunks/@localSearchIndexroot.BA7V8dEJ.js +1 -0
  36. package/dist/docs/assets/chunks/{VPLocalSearchBox.BBCr8Yuy.js → VPLocalSearchBox.hOzpPwrG.js} +1 -1
  37. package/dist/docs/assets/chunks/{theme.CK_NiGC-.js → theme.BtFhUYmh.js} +2 -2
  38. package/dist/docs/assets/{deployment.md.CuK5SNjN.js → deployment.md.BHVjVqRa.js} +4 -4
  39. package/dist/docs/assets/{deployment.md.CuK5SNjN.lean.js → deployment.md.BHVjVqRa.lean.js} +1 -1
  40. package/dist/docs/assets/{evals.md.BQXI3rXy.js → evals.md.qeoGgPw0.js} +3 -3
  41. package/dist/docs/assets/example-agents_index.md.xmJ87d_3.js +2 -0
  42. package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.js → guides_webhooks.md.DB-r_er9.js} +47 -4
  43. package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.lean.js → guides_webhooks.md.DB-r_er9.lean.js} +1 -1
  44. package/dist/docs/assets/index.md.BKpbkCa6.js +24 -0
  45. package/dist/docs/assets/{index.md.CmhptOmN.lean.js → index.md.BKpbkCa6.lean.js} +1 -1
  46. package/dist/docs/assets/{quickstart.md.C_b6ESpD.js → quickstart.md.rIbfahJM.js} +4 -4
  47. package/dist/docs/assets/{reference_cli.md.Byvrg8eu.js → reference_cli.md.DqEqQpGn.js} +5 -5
  48. package/dist/docs/assets/{reference_cli.md.Byvrg8eu.lean.js → reference_cli.md.DqEqQpGn.lean.js} +1 -1
  49. package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.js → reference_hooks.md.BHeG-vjK.js} +2 -2
  50. package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.lean.js → reference_hooks.md.BHeG-vjK.lean.js} +1 -1
  51. package/dist/docs/assets/{storage.md.mQDtIULc.js → storage.md.DcGSnIxX.js} +10 -4
  52. package/dist/docs/assets/{storage.md.mQDtIULc.lean.js → storage.md.DcGSnIxX.lean.js} +1 -1
  53. package/dist/docs/assets/troubleshooting.md.CkgwmVI0.js +1 -0
  54. package/dist/docs/assets/{troubleshooting.md.B5RVX_tL.lean.js → troubleshooting.md.CkgwmVI0.lean.js} +1 -1
  55. package/dist/docs/building-with-agents.html +5 -5
  56. package/dist/docs/concepts.html +3 -3
  57. package/dist/docs/deployment.html +6 -6
  58. package/dist/docs/evals.html +7 -7
  59. package/dist/docs/example-agents/approval-buddy.html +3 -3
  60. package/dist/docs/example-agents/benny.html +3 -3
  61. package/dist/docs/example-agents/bugbot.html +3 -3
  62. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  63. package/dist/docs/example-agents/codeowners-review.html +3 -3
  64. package/dist/docs/example-agents/concierge.html +3 -3
  65. package/dist/docs/example-agents/fsd.html +3 -3
  66. package/dist/docs/example-agents/index.html +5 -5
  67. package/dist/docs/example-agents/knowledge-base.html +3 -3
  68. package/dist/docs/example-agents/oncall.html +3 -3
  69. package/dist/docs/example-agents/security-reviewer.html +3 -3
  70. package/dist/docs/example-agents/slack-agent.html +3 -3
  71. package/dist/docs/example-agents/weather-agent.html +3 -3
  72. package/dist/docs/guides/agent-to-agent.html +3 -3
  73. package/dist/docs/guides/cloud-runtime.html +3 -3
  74. package/dist/docs/guides/github.html +3 -3
  75. package/dist/docs/guides/human-in-the-loop.html +3 -3
  76. package/dist/docs/guides/mcp-oauth.html +3 -3
  77. package/dist/docs/guides/slack.html +3 -3
  78. package/dist/docs/guides/webhooks.html +49 -6
  79. package/dist/docs/hashmap.json +1 -1
  80. package/dist/docs/hillclimbing.html +3 -3
  81. package/dist/docs/index.html +7 -7
  82. package/dist/docs/quickstart.html +8 -8
  83. package/dist/docs/reference/agent-config.html +3 -3
  84. package/dist/docs/reference/artifacts.html +3 -3
  85. package/dist/docs/reference/channels.html +3 -3
  86. package/dist/docs/reference/cli.html +8 -8
  87. package/dist/docs/reference/connections.html +3 -3
  88. package/dist/docs/reference/hooks.html +5 -5
  89. package/dist/docs/reference/http-api.html +3 -3
  90. package/dist/docs/reference/instructions.html +3 -3
  91. package/dist/docs/reference/playground.html +3 -3
  92. package/dist/docs/reference/project-layout.html +3 -3
  93. package/dist/docs/reference/prompt.html +3 -3
  94. package/dist/docs/reference/schedules.html +3 -3
  95. package/dist/docs/reference/sessions.html +3 -3
  96. package/dist/docs/reference/skills.html +3 -3
  97. package/dist/docs/reference/subagents.html +3 -3
  98. package/dist/docs/reference/tools.html +3 -3
  99. package/dist/docs/scaffolding-agents.html +3 -3
  100. package/dist/docs/storage.html +14 -8
  101. package/dist/docs/troubleshooting.html +5 -5
  102. package/dist/index.d.ts +4 -2
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +3 -1
  105. package/dist/internal/cli-ax.d.ts.map +1 -1
  106. package/dist/internal/cli-ax.js +22 -2
  107. package/dist/internal/cli-deploy.d.ts +6 -0
  108. package/dist/internal/cli-deploy.d.ts.map +1 -1
  109. package/dist/internal/cli-deploy.js +14 -4
  110. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
  111. package/dist/internal/cli-mcp-oauth.js +3 -1
  112. package/dist/internal/cli-mcp.js +4 -4
  113. package/dist/internal/cursor-event-relay.d.ts +45 -0
  114. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  115. package/dist/internal/cursor-event-relay.js +96 -2
  116. package/dist/internal/deploy-client.js +1 -1
  117. package/dist/internal/discovery.d.ts.map +1 -1
  118. package/dist/internal/discovery.js +35 -7
  119. package/dist/internal/distribution.d.ts.map +1 -1
  120. package/dist/internal/distribution.js +3 -1
  121. package/dist/internal/durable-session-store.d.ts +48 -0
  122. package/dist/internal/durable-session-store.d.ts.map +1 -0
  123. package/dist/internal/durable-session-store.js +86 -0
  124. package/dist/internal/hosting.d.ts +1 -1
  125. package/dist/internal/hosting.js +2 -2
  126. package/dist/internal/init-project.d.ts +34 -0
  127. package/dist/internal/init-project.d.ts.map +1 -1
  128. package/dist/internal/init-project.js +121 -2
  129. package/dist/internal/ledger-serving.d.ts +101 -0
  130. package/dist/internal/ledger-serving.d.ts.map +1 -0
  131. package/dist/internal/ledger-serving.js +174 -0
  132. package/dist/internal/listen-port.d.ts +22 -0
  133. package/dist/internal/listen-port.d.ts.map +1 -0
  134. package/dist/internal/listen-port.js +77 -0
  135. package/dist/internal/mcp-oauth.d.ts +12 -1
  136. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  137. package/dist/internal/mcp-oauth.js +25 -3
  138. package/dist/internal/playground/static.d.ts +21 -4
  139. package/dist/internal/playground/static.d.ts.map +1 -1
  140. package/dist/internal/playground/static.js +82 -18
  141. package/dist/internal/sdk-runner.d.ts.map +1 -1
  142. package/dist/internal/sdk-runner.js +10 -1
  143. package/dist/internal/server.d.ts.map +1 -1
  144. package/dist/internal/server.js +67 -49
  145. package/dist/internal/session-engine.d.ts +57 -1
  146. package/dist/internal/session-engine.d.ts.map +1 -1
  147. package/dist/internal/session-engine.js +117 -28
  148. package/dist/internal/slack-provision-client.js +1 -1
  149. package/dist/internal/workspace.d.ts +6 -2
  150. package/dist/internal/workspace.d.ts.map +1 -1
  151. package/dist/internal/workspace.js +10 -2
  152. package/dist/kv/memory-kv.d.ts +23 -0
  153. package/dist/kv/memory-kv.d.ts.map +1 -0
  154. package/dist/kv/memory-kv.js +103 -0
  155. package/dist/kv.d.ts +22 -0
  156. package/dist/kv.d.ts.map +1 -0
  157. package/dist/kv.js +20 -0
  158. package/dist/ledger/session-ledger.d.ts +204 -0
  159. package/dist/ledger/session-ledger.d.ts.map +1 -0
  160. package/dist/ledger/session-ledger.js +382 -0
  161. package/dist/ledger.d.ts +11 -0
  162. package/dist/ledger.d.ts.map +1 -0
  163. package/dist/ledger.js +9 -0
  164. package/dist/playground/assets/index-B3HBneZZ.js +85 -0
  165. package/dist/playground/assets/index-Bqk9nplh.css +1 -0
  166. package/dist/playground/index.html +2 -2
  167. package/dist/storage-backends/cursor-hosted.d.ts +25 -3
  168. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  169. package/dist/storage-backends/cursor-hosted.js +50 -2
  170. package/dist/storage-backends/file-kv.d.ts +5 -2
  171. package/dist/storage-backends/file-kv.d.ts.map +1 -1
  172. package/dist/storage-backends/file-kv.js +107 -7
  173. package/dist/storage.d.ts +60 -5
  174. package/dist/storage.d.ts.map +1 -1
  175. package/dist/storage.js +28 -4
  176. package/dist/types.d.ts +123 -3
  177. package/dist/types.d.ts.map +1 -1
  178. package/dist/types.js +14 -0
  179. package/docs/README.md +10 -7
  180. package/docs/ab.md +2 -2
  181. package/docs/building-with-agents.md +1 -1
  182. package/docs/deployment.md +34 -11
  183. package/docs/evals.md +3 -3
  184. package/docs/example-agents/index.md +1 -1
  185. package/docs/guides/webhooks.md +87 -0
  186. package/docs/quickstart.md +7 -8
  187. package/docs/reference/cli.md +15 -6
  188. package/docs/reference/hooks.md +8 -2
  189. package/docs/storage.md +67 -7
  190. package/docs/troubleshooting.md +10 -2
  191. package/package.json +20 -7
  192. package/skills/create-agent/SKILL.md +1 -1
  193. package/src/bin/agent-serve.ts +27 -11
  194. package/src/channels/slack/api.ts +19 -14
  195. package/src/channels/slack/attachments.ts +635 -0
  196. package/src/channels/slack/cursor-account.ts +8 -0
  197. package/src/channels/slack/defaults.ts +34 -18
  198. package/src/channels/slack/dispatch.ts +121 -6
  199. package/src/channels/slack/inbound.ts +60 -0
  200. package/src/channels/slack/index.ts +1 -0
  201. package/src/channels/slack/live-delivery.ts +17 -3
  202. package/src/channels/slack/manifest.ts +1 -0
  203. package/src/channels/slack/slack-channel.ts +7 -0
  204. package/src/channels/slack/types.ts +47 -1
  205. package/src/index.ts +9 -0
  206. package/src/internal/cli-ax.ts +25 -1
  207. package/src/internal/cli-deploy.ts +22 -4
  208. package/src/internal/cli-mcp-oauth.ts +3 -1
  209. package/src/internal/cli-mcp.ts +4 -4
  210. package/src/internal/cursor-event-relay.ts +168 -0
  211. package/src/internal/deploy-client.ts +1 -1
  212. package/src/internal/discovery.ts +42 -5
  213. package/src/internal/distribution.ts +3 -1
  214. package/src/internal/durable-session-store.ts +95 -0
  215. package/src/internal/hosting.ts +2 -2
  216. package/src/internal/init-project.ts +151 -2
  217. package/src/internal/ledger-serving.ts +240 -0
  218. package/src/internal/listen-port.ts +85 -0
  219. package/src/internal/mcp-oauth.ts +29 -4
  220. package/src/internal/playground/static.ts +111 -20
  221. package/src/internal/sdk-runner.ts +11 -1
  222. package/src/internal/server.ts +76 -62
  223. package/src/internal/session-engine.ts +192 -16
  224. package/src/internal/slack-provision-client.ts +1 -1
  225. package/src/internal/workspace.ts +14 -3
  226. package/src/kv/memory-kv.ts +99 -0
  227. package/src/kv.ts +27 -0
  228. package/src/ledger/session-ledger.ts +535 -0
  229. package/src/ledger.ts +29 -0
  230. package/src/storage-backends/cursor-hosted.ts +108 -3
  231. package/src/storage-backends/file-kv.ts +128 -12
  232. package/src/storage.ts +90 -6
  233. package/src/types.ts +130 -4
  234. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  235. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  236. package/dist/docs/assets/chunks/@localSearchIndexroot.B-VTH4As.js +0 -1
  237. package/dist/docs/assets/example-agents_index.md.DgGBwckv.js +0 -2
  238. package/dist/docs/assets/index.md.CmhptOmN.js +0 -24
  239. package/dist/docs/assets/troubleshooting.md.B5RVX_tL.js +0 -1
  240. package/dist/internal/json-dir-store.d.ts +0 -32
  241. package/dist/internal/json-dir-store.d.ts.map +0 -1
  242. package/dist/internal/persistence-coordinator.d.ts +0 -127
  243. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  244. package/dist/multi-tenant.d.ts +0 -80
  245. package/dist/multi-tenant.d.ts.map +0 -1
  246. package/dist/multi-tenant.js +0 -69
  247. package/dist/persistence.d.ts +0 -184
  248. package/dist/persistence.d.ts.map +0 -1
  249. package/dist/playground/assets/index-BRhBbnd4.css +0 -1
  250. package/dist/playground/assets/index-_2WKw9a-.js +0 -85
  251. package/dist/storage-backends/postgres-kv.d.ts +0 -82
  252. package/dist/storage-backends/postgres-kv.d.ts.map +0 -1
  253. package/dist/storage-backends/postgres-kv.js +0 -185
  254. package/src/storage-backends/postgres-kv.ts +0 -253
  255. /package/dist/docs/assets/{ab.md.6cLOW7--.lean.js → ab.md.hdxsvX5M.lean.js} +0 -0
  256. /package/dist/docs/assets/{building-with-agents.md.txrcGU2B.lean.js → building-with-agents.md.PeZaZA1P.lean.js} +0 -0
  257. /package/dist/docs/assets/{evals.md.BQXI3rXy.lean.js → evals.md.qeoGgPw0.lean.js} +0 -0
  258. /package/dist/docs/assets/{example-agents_index.md.DgGBwckv.lean.js → example-agents_index.md.xmJ87d_3.lean.js} +0 -0
  259. /package/dist/docs/assets/{quickstart.md.C_b6ESpD.lean.js → quickstart.md.rIbfahJM.lean.js} +0 -0
@@ -177,6 +177,18 @@ 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
+ ## Hosted aliases need an alias-token relay
181
+
182
+ Cursor-managed deployments expose a stable alias URL. External callers
183
+ must send `X-Agent-Alias-Token` on every request to that URL. Channel
184
+ auth still runs after the alias gate.
185
+
186
+ Webhook providers that cannot attach custom headers cannot POST
187
+ straight at a managed alias. Put a relay (or other authenticating
188
+ intermediary) in front, use a Cursor-managed ingress path that does not
189
+ use the public alias (for example Cursor Slack / GitHub event relay),
190
+ or self-host. See [Deployment](../deployment.md#use-the-hosted-agent).
191
+
180
192
  ## Example: Linear as the control plane
181
193
 
182
194
  Everything above composes into a working ticket-driven agent. This
@@ -346,6 +358,81 @@ See [MCP connections](../reference/connections.md) for the direct
346
358
  `https://mcp.linear.app/mcp` form when the host isn't signed in to
347
359
  Cursor.
348
360
 
361
+ ### Production Linear agents: expiring OAuth tokens
362
+
363
+ A plain API key fits a personal integration. A production Linear agent
364
+ is an OAuth application: installing it in a workspace mints an access
365
+ token with an expiry plus a refresh token. The serve host can be
366
+ replaced at any time, so the pair can't live in process memory.
367
+ Persist it in [`host.kv`](../storage.md#author-kv-ctx-host-kv) instead.
368
+ Route handlers receive `host` in their args, and event handlers get the
369
+ same services on `ctx`.
370
+
371
+ Two prerequisites:
372
+
373
+ - Keep the OAuth client id and client secret in
374
+ [deployment secrets](../deployment.md). They're static, so
375
+ deploy-time env vars fit them.
376
+ - Back kv with durable storage. On Cursor-managed hosting, use
377
+ `cursorHostedStorage` in `agent/storage.ts`
378
+ ([Storage](../storage.md)). Without it, kv falls back to local disk
379
+ and a host replacement drops the tokens.
380
+
381
+ Save the pair when the install flow completes, then refresh on demand:
382
+
383
+ ```ts
384
+ import type { HostKvApi } from "@cursor/july/kv";
385
+
386
+ interface LinearTokens {
387
+ accessToken: string;
388
+ refreshToken: string;
389
+ expiresAtMs: number;
390
+ }
391
+
392
+ const TOKENS_KEY = "linear/oauth-tokens";
393
+
394
+ export async function linearAccessToken(kv: HostKvApi): Promise<string> {
395
+ const tokens = (await kv.get(TOKENS_KEY)) as LinearTokens | undefined;
396
+ if (tokens === undefined) {
397
+ throw new Error("No Linear tokens stored. Complete the install flow first.");
398
+ }
399
+ if (Date.now() < tokens.expiresAtMs - 60_000) {
400
+ return tokens.accessToken;
401
+ }
402
+ const response = await fetch("https://api.linear.app/oauth/token", {
403
+ method: "POST",
404
+ headers: { "content-type": "application/x-www-form-urlencoded" },
405
+ body: new URLSearchParams({
406
+ grant_type: "refresh_token",
407
+ refresh_token: tokens.refreshToken,
408
+ client_id: process.env.LINEAR_CLIENT_ID ?? "",
409
+ client_secret: process.env.LINEAR_CLIENT_SECRET ?? "",
410
+ }),
411
+ });
412
+ if (!response.ok) {
413
+ throw new Error(`Linear token refresh failed: ${response.status}`);
414
+ }
415
+ const next = (await response.json()) as {
416
+ access_token: string;
417
+ refresh_token?: string;
418
+ expires_in: number;
419
+ };
420
+ const rotated: LinearTokens = {
421
+ accessToken: next.access_token,
422
+ refreshToken: next.refresh_token ?? tokens.refreshToken,
423
+ expiresAtMs: Date.now() + next.expires_in * 1000,
424
+ };
425
+ await kv.put(TOKENS_KEY, rotated);
426
+ return rotated.accessToken;
427
+ }
428
+ ```
429
+
430
+ Then authorize GraphQL calls with `Bearer ${await
431
+ linearAccessToken(host.kv)}` in place of the static `apiKey` above.
432
+ Writes to `host.kv` await the durable sink and propagate errors, so a
433
+ failed save surfaces instead of silently losing the rotated refresh
434
+ token.
435
+
349
436
  To test without a public URL, save a payload from the webhook's
350
437
  delivery log (or the sample in
351
438
  [Linear's webhook docs](https://linear.app/developers/webhooks)) under
@@ -21,9 +21,8 @@ You need:
21
21
  - Node 22.13 or newer. Bun isn't supported.
22
22
  - The `agent-sdk` CLI. `npx @cursor/july init ./pr-approver` bootstraps
23
23
  it with no prior install: `init` scaffolds the project, runs
24
- `npm install`, and offers a Cursor sign-in, after which `npx agent-sdk`
25
- resolves from the project's own dependencies. See
26
- [Run the CLI](./README.md#run-the-cli) for other setups, such as a
24
+ `npm install`, links `agent-sdk` onto your PATH, and offers a Cursor
25
+ sign-in. See [Run the CLI](/#run-the-cli) for other setups, such as a
27
26
  monorepo source checkout.
28
27
  - A Cursor credential for model turns. Sign in once:
29
28
 
@@ -44,9 +43,9 @@ Want a working agent before the full tutorial? Four commands get you
44
43
  there:
45
44
 
46
45
  ```bash
47
- agent-sdk init ./pr-approver # scaffold + npm install + sign-in offer
46
+ npx @cursor/july init ./pr-approver # scaffold + npm install + PATH link + sign-in offer
48
47
  cd pr-approver
49
- agent-sdk dev # serve, and print the playground URL
48
+ agent-sdk dev # serve, and print the playground URL
50
49
  ```
51
50
 
52
51
  Open the playground URL and chat with the scaffold. Then, in a second
@@ -79,7 +78,7 @@ full guided workflow.
79
78
  Start with the built-in scaffold:
80
79
 
81
80
  ```bash
82
- agent-sdk init ./pr-approver
81
+ npx @cursor/july init ./pr-approver
83
82
  cd pr-approver
84
83
  agent-sdk dev
85
84
  ```
@@ -299,7 +298,7 @@ Call the tool directly first, on a real merged pull request:
299
298
 
300
299
  ```bash
301
300
  agent-sdk call inspect_pr --dir . \
302
- --input '{"prUrl":"https://github.com/react/react/pull/35623"}'
301
+ --input '{"prUrl":"https://github.com/facebook/react/pull/35623"}'
303
302
  ```
304
303
 
305
304
  `call` validates the input and runs `execute` without a model turn.
@@ -322,7 +321,7 @@ Now call it on the PR that added `experimental_useEvent` to React:
322
321
 
323
322
  ```bash
324
323
  agent-sdk call inspect_pr --dir . \
325
- --input '{"prUrl":"https://github.com/react/react/pull/25229"}'
324
+ --input '{"prUrl":"https://github.com/facebook/react/pull/25229"}'
326
325
  ```
327
326
 
328
327
  The rating flips to `large` and the patches disappear from the result.
@@ -11,7 +11,7 @@ page use `agent-sdk`. Run the CLI with Node 22.13 or newer. Don't run it
11
11
  with Bun; Bun corrupts tool-result streams from the Cursor SDK.
12
12
 
13
13
  The current release still uses `.agent-serve` for on-disk state. See the
14
- [rename table](../README.md#run-the-cli) for identifiers still moving to
14
+ [rename table](/#run-the-cli) for identifiers still moving to
15
15
  agent-sdk names.
16
16
 
17
17
  `agent-sdk help` prints the built-in summary. The Slack and GitHub packs
@@ -111,7 +111,7 @@ Unless `--state-root` is set, each mount uses
111
111
 
112
112
  | Flag | Meaning |
113
113
  | --- | --- |
114
- | `--port` | Listen on this port. `0` selects an available port. The default is `3000`. |
114
+ | `--port` | Listen on this port. `0` selects an available port. The default is `3000`. When the default is taken, serve tries the next free port and prints a notice; an explicit `--port` fails with a next-port hint instead. |
115
115
  | `--host` | Bind this host. The default is loopback-only `127.0.0.1`. |
116
116
  | `--dev` | Disable automatic schedule and reminder firing, admit unsigned loopback GitHub deliveries, widen playground session access on loopback, and start Vite HMR when available. |
117
117
  | `--mode` | Use `multi` for slugged routes and an index, or `single` for one agent at the unslugged `/v1/*`. The default is `multi`. |
@@ -733,14 +733,23 @@ See the [Host MCP OAuth guide](../guides/mcp-oauth.md).
733
733
  `secrets` manages environment secrets for a deployment.
734
734
 
735
735
  ```bash
736
- agent-sdk secrets set <slug> NAME[=VALUE]... [--team <id>] [--json]
736
+ agent-sdk secrets set <slug> NAME [NAME2 ...] [--team <id>] [--json]
737
737
  agent-sdk secrets list <slug> [--team <id>] [--json]
738
738
  agent-sdk secrets unset <slug> NAME [--team <id>] [--json]
739
739
  ```
740
740
 
741
- Omit `=VALUE` from `secrets set` to enter the value at a hidden TTY
742
- prompt. With piped stdin, provide one line for each missing value. Avoid
743
- values in command arguments because shells can record them in history.
741
+ Pass names only. On a TTY, `secrets set` prompts for each value with
742
+ hidden input (nothing echoes). When stdin is piped, provide one line per
743
+ name. Values never print on stdout.
744
+
745
+ Do not put values on the command line. `NAME=VALUE` in argv shows up in
746
+ shell history and in agent-captured terminals. The CLI refuses that form
747
+ unless you pass `--from-argv` (still warns). Prefer a file redirect when
748
+ a human is not at the prompt:
749
+
750
+ ```bash
751
+ agent-sdk secrets set weather-agent WEATHER_API_KEY < ./weather-api-key.txt
752
+ ```
744
753
 
745
754
  Secret names use `UPPER_SNAKE_CASE`, start with a letter, and contain at
746
755
  most 64 characters. Names beginning with `CURSOR_` are reserved. Values
@@ -9,8 +9,14 @@ A hook is an observe-only subscriber to the session event stream. Hooks
9
9
  run after each event is recorded and fanned out (file persistence flushes
10
10
  in the background). That makes them the home for audit logging, metrics,
11
11
  mirroring transcripts into your own store, and maintaining derived state.
12
- Handler errors are logged and never fatal. A hook can't modify events or
13
- block a turn.
12
+ Handler errors are logged and never fatal. A hook can't modify events,
13
+ inject context into the next turn, or block a turn.
14
+
15
+ For deterministic context composition before the model runs, use the
16
+ host path that already owns the wake: channel handlers (fetch, `callTool`,
17
+ `workspaceFiles`, and the message you pass to `send`), plus
18
+ `instructions.md`, skills, and `sandbox/workspace/` seed files. Hooks
19
+ observe what happened; they do not assemble the prompt.
14
20
 
15
21
  Author `agent/hooks/<name>.ts` with `defineHook` from
16
22
  `@cursor/july/hooks`:
package/docs/storage.md CHANGED
@@ -22,7 +22,10 @@ for one machine; it does not survive replacing the host.
22
22
 
23
23
  To keep the same framework storage across hosts, plug in a key-value
24
24
  backend with `agent/storage.ts`. You provide `put` / `get` / `delete` /
25
- `list`. The Agent SDK does the rest.
25
+ `list`, plus the `cas` group when the deployment uses coordination
26
+ features (conditional writes — see
27
+ [Conditional writes](#conditional-writes-the-cas-group)). The Agent SDK
28
+ does the rest.
26
29
 
27
30
  ```ts
28
31
  // agent/storage.ts
@@ -33,26 +36,33 @@ export default defineStorage({
33
36
  get: (key) => db.get(key),
34
37
  delete: (key) => db.delete(key),
35
38
  list: (prefix) => db.listByPrefix(prefix), // [{ key, value }], key order
39
+ cas: {
40
+ getWithVersion: (key) => db.getRow(key), // { value, version }
41
+ putIfAbsent: (key, value) => db.insertIfAbsent(key, value),
42
+ putIfVersion: (key, value, expected) => db.casUpdate(key, value, expected),
43
+ listKeys: (prefix) => db.listKeysByPrefix(prefix),
44
+ },
36
45
  });
37
46
  ```
38
47
 
39
48
  > [!NOTE]
40
49
  > Import paths here use `@cursor/july/storage`. On projects still
41
50
  > using `@anysphere/agent-serve`, swap the import. See
42
- > [Run the CLI](./README.md#run-the-cli) for the full rename table.
51
+ > [Run the CLI](/#run-the-cli) for the full rename table.
43
52
 
44
53
  ## Which fields to provide
45
54
 
46
55
  | Field | Required | Role |
47
56
  | --- | --- | --- |
48
57
  | `put` | Yes | Write or update a value |
58
+ | `cas` | For coordination | Conditional writes; see [Conditional writes](#conditional-writes-the-cas-group) |
49
59
  | `get` | For restore | Look up one key |
50
60
  | `list` | For restore | Return entries under a prefix, in key order |
51
61
  | `delete` | For cleanup | Remove a key |
52
62
  | `name` | No | Label surfaced on `GET /v1/info` diagnostics |
53
63
  | `policy` | No | Timing knobs; see [Policy](#policy) |
54
- | `evals` | No | Dedicated eval-runs table; see [Eval and A/B tables](#eval-and-ab-tables) |
55
- | `abs` | No | Dedicated A/B metrics table; see [Eval and A/B tables](#eval-and-ab-tables) |
64
+ | `evals` | No | Dedicated eval-runs table; see [Eval and A/B tables](#eval-and-a-b-tables) |
65
+ | `abs` | No | Dedicated A/B metrics table; see [Eval and A/B tables](#eval-and-a-b-tables) |
56
66
 
57
67
  A throwing `put` is logged and dropped. It never fails a turn. When
58
68
  resolving a missing continuation token, a throwing `get` fails the
@@ -142,8 +152,6 @@ survive replacing the host. For Cursor-managed hosting, prefer
142
152
  `@cursor/july/storage/cursor-hosted` so sessions and author KV share the
143
153
  platform Bugbot tables through a control-plane HTTP proxy (authenticated
144
154
  as the deployment pod credential — engines never receive a database URL).
145
- For self-hosted Postgres you own, use `@cursor/july/storage/postgres-kv`
146
- and supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` yourself.
147
155
 
148
156
  ```ts
149
157
  // agent/storage.ts — Cursor-managed hosting
@@ -161,4 +169,56 @@ Built-in helpers:
161
169
  | --- | --- |
162
170
  | `@cursor/july/storage/file-kv` | File-per-key under `.agent-serve/kv` |
163
171
  | `@cursor/july/storage/cursor-hosted` | Platform Bugbot `agent_serve_*` via control-plane proxy |
164
- | `@cursor/july/storage/postgres-kv` | Same tables; you supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` |
172
+
173
+ ## Bring your own backend
174
+
175
+ There is no built-in Postgres backend on purpose. Cursor's internal
176
+ `agent_serve_*` tables are owned by the backend and reachable only
177
+ through the hosted proxy, and this doc does not prescribe a schema —
178
+ what you back the KV with is your call. Any durable store works:
179
+
180
+ - **Local disk** — the built-in `fileKv` (single process only).
181
+ - **Object storage (S3-class)** — one object per key; conditional
182
+ writes map directly onto the contract (`putIfAbsent` = put with
183
+ `If-None-Match: *`, `putIfVersion` = put with `If-Match: <etag>`,
184
+ the ETag is the version token, `listKeys` is a prefix listing).
185
+ - **Redis, DynamoDB, a SQL table, …** — anything that can do an
186
+ atomic compare-and-set and a prefix listing.
187
+
188
+ Implement the `StorageConfig` methods (and the `cas` group when the
189
+ deployment uses coordination features) against that store. The
190
+ contract, defined at `@cursor/july/kv`: version tokens are opaque
191
+ strings that change on every successful write — including plain
192
+ `put`, so a stale token fences instead of clobbering; conditional
193
+ writes are atomic; `listKeys` returns every key under the prefix.
194
+ `@cursor/july/kv/memory` is a complete reference implementation to
195
+ compare behavior against.
196
+
197
+ ## Conditional writes (the `cas` group)
198
+
199
+ The `cas` group is compare-and-swap over the same keyspace:
200
+ `getWithVersion` / `putIfAbsent` / `putIfVersion` / `listKeys`, defined
201
+ backend-agnostically at `@cursor/july/kv`. Plain storage works without
202
+ it, so an existing backend keeps working across a platform upgrade.
203
+ Coordination features require it: they fail at startup, with a message
204
+ naming this group, when the backend lacks it. `defineStorage` rejects a
205
+ partial group — implement all four methods or none. Version tokens are
206
+ opaque strings that must change on every write (a counter column, a row
207
+ version, a content hash). The built-in backends both include it: `fileKv`
208
+ uses content-hash tokens (single-process correctness) and
209
+ `cursorHostedStorage` the control-plane proxy (a `version` counter on the
210
+ server).
211
+
212
+ ## Durable sessions (the session ledger)
213
+
214
+ Conditional writes are the substrate for the session ledger, which
215
+ ships behind `defineAgent({ serving: "ledger" })`: session identity,
216
+ one-writer-per-session leases with generation fencing, and a fenced
217
+ commit sequence, coordinated through the agent's storage backend by
218
+ default (its `cas` group) or an explicit `durableSessionStore`
219
+ override. The ledger has **no durable inbox** — an event arriving while
220
+ a session's lease is held waits in-process on the delivering pod, so
221
+ crash durability matches resident serving; what ledger mode adds is
222
+ cross-pod mutual exclusion, zombie fencing, and the committed head
223
+ sequence. A backend that implements `cas` needs no storage migration to
224
+ adopt it.
@@ -13,7 +13,7 @@ Start with four checks, in order:
13
13
  4. The session event stream (trace)
14
14
 
15
15
  Match your symptom below. Keep the commands as `agent-sdk`; see
16
- [Run the CLI](./README.md#run-the-cli) if you still need an alias.
16
+ [Run the CLI](/#run-the-cli) if you still need an alias.
17
17
 
18
18
  ## What if serve or the playground looks wrong?
19
19
 
@@ -23,7 +23,7 @@ Match your symptom below. Keep the commands as `agent-sdk`; see
23
23
  | Playground is blank or says there are no agents | The UI needs a running `serve` process. Building the playground assets alone is not enough. |
24
24
  | Edits to the playground don't show up | Use `serve --dev` and open the printed playground HMR URL (often port `5273`), not only the static `:3000` URL. |
25
25
  | Sessions exist on disk but the playground list is empty | The list shows sessions for the authenticated caller. In `--dev` on loopback the list is wider. Otherwise open `/<slug>/playground?sessionId=ses_…` or inspect `sessions/` under your state root. |
26
- | Port 3000 or 5273 is already in use | Find and stop the listener (`lsof -iTCP:3000 -sTCP:LISTEN` on macOS/Linux), and any leftover Vite or webhook-forwarder processes. |
26
+ | Port 3000 or 5273 is already in use | For the default serve port, the CLI tries the next free port and prints a notice. Pass `--port` to pick one, or `--port 0` for any free port. Stop leftover Vite or webhook-forwarder processes if you need the original port. |
27
27
 
28
28
  ## What if a model turn goes wrong?
29
29
 
@@ -75,6 +75,14 @@ Match your symptom below. Keep the commands as `agent-sdk`; see
75
75
  See [Host MCP OAuth](./guides/mcp-oauth.md) and
76
76
  [`skills/mcp-auth/SKILL.md`](../skills/mcp-auth/SKILL.md).
77
77
 
78
+ ## What if a secret showed up in a terminal transcript?
79
+
80
+ | What you see | What to do |
81
+ | --- | --- |
82
+ | `secrets set … NAME=VALUE` in an agent-captured terminal or shell history | Rotate the secret at the provider. Set it again with names only: `agent-sdk secrets set <slug> NAME` (hidden prompt) or pipe/redirect the value. `NAME=VALUE` requires `--from-argv` and still leaks into argv. |
83
+ | Alias token printed during first deploy or `rotate-token` | Treat it as exposed if the transcript left your machine. Run `agent-sdk rotate-token <slug>`, store the new token outside agent transcripts, and update callers. |
84
+ | Someone verified a secret with `echo` / `printenv` | Rotate it. Confirm presence with `agent-sdk secrets list <slug>` (names only), then redeploy and test the feature. |
85
+
78
86
  ## What if schedules, reminders, or approvals stall?
79
87
 
80
88
  | What you see | What to do |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
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": {
@@ -141,6 +141,24 @@
141
141
  "import": "./dist/artifacts.js",
142
142
  "default": "./dist/artifacts.js"
143
143
  },
144
+ "./kv": {
145
+ "anysphere-source": "./src/kv.ts",
146
+ "types": "./dist/kv.d.ts",
147
+ "import": "./dist/kv.js",
148
+ "default": "./dist/kv.js"
149
+ },
150
+ "./kv/memory": {
151
+ "anysphere-source": "./src/kv/memory-kv.ts",
152
+ "types": "./dist/kv/memory-kv.d.ts",
153
+ "import": "./dist/kv/memory-kv.js",
154
+ "default": "./dist/kv/memory-kv.js"
155
+ },
156
+ "./ledger": {
157
+ "anysphere-source": "./src/ledger.ts",
158
+ "types": "./dist/ledger.d.ts",
159
+ "import": "./dist/ledger.js",
160
+ "default": "./dist/ledger.js"
161
+ },
144
162
  "./storage": {
145
163
  "anysphere-source": "./src/storage.ts",
146
164
  "types": "./dist/storage.d.ts",
@@ -153,12 +171,6 @@
153
171
  "import": "./dist/storage-backends/file-kv.js",
154
172
  "default": "./dist/storage-backends/file-kv.js"
155
173
  },
156
- "./storage/postgres-kv": {
157
- "anysphere-source": "./src/storage-backends/postgres-kv.ts",
158
- "types": "./dist/storage-backends/postgres-kv.d.ts",
159
- "import": "./dist/storage-backends/postgres-kv.js",
160
- "default": "./dist/storage-backends/postgres-kv.js"
161
- },
162
174
  "./storage/cursor-hosted": {
163
175
  "anysphere-source": "./src/storage-backends/cursor-hosted.ts",
164
176
  "types": "./dist/storage-backends/cursor-hosted.d.ts",
@@ -182,6 +194,7 @@
182
194
  "docs:dev": "vitepress dev docs",
183
195
  "docs:build": "vitepress build docs",
184
196
  "docs:preview": "vitepress preview docs",
197
+ "docs:verify-links": "node scripts/verify-docs-links.mjs",
185
198
  "fix": "biome check --write --unsafe src",
186
199
  "test": "vitest run --passWithNoTests",
187
200
  "type-check": "tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
@@ -144,7 +144,7 @@ a demo `agent/tools/echo.ts`, and empty capability folders
144
144
  (`skills/`, `channels/`, `evals/`, …) each with a `.gitkeep`; existing
145
145
  files are left alone (`exist`) and missing ones are filled in. It then
146
146
  runs `npm install`, and when the host is unsigned runs `login` and waits
147
- before printing `cd` (when needed) and bare `dev`. Then shape it to the
147
+ before printing `cd` (when needed) and `agent-sdk dev`. Then shape it to the
148
148
  plan:
149
149
 
150
150
  1. `tsconfig.json` — init writes a strict, `noEmit` config covering `agent/`
@@ -136,8 +136,8 @@ Usage:
136
136
  ${CLI} stop <slug> [--team <id>] [--json] [--no-wait]
137
137
  ${CLI} rotate-token <slug> [--team <id>]
138
138
  ${CLI} rotate-pod-credential <slug> [--team <id>]
139
- ${CLI} secrets set <slug> NAME[=VALUE]... | list <slug> | unset <slug> NAME
140
- [--team <id>] [--json]
139
+ ${CLI} secrets set <slug> NAME... | list <slug> | unset <slug> NAME
140
+ [--team <id>] [--json] [--from-argv]
141
141
  ${CLI} slack <subcommand> …
142
142
  ${CLI} github <subcommand> …
143
143
 
@@ -257,9 +257,9 @@ Commands:
257
257
  rotate-pod-credential
258
258
  Rotate a deployment's pod credential (prints the masked key);
259
259
  the running pod keeps the old one until the next deploy.
260
- secrets Manage deployment secrets: set (upsert by name; NAMEs without
261
- =VALUE are prompted for with hidden input, or read from piped
262
- stdin), list (names + createdAt; never values), unset.
260
+ secrets Manage deployment secrets: set (names only by default: hidden
261
+ prompt or piped stdin; NAME=VALUE requires --from-argv), list
262
+ (names + createdAt; never values), unset.
263
263
  slack Slack channel pack: create/destroy/icon (Cursor Add to Slack
264
264
  provisioning), init, manifests, doctor (see slack help).
265
265
  github GitHub webhook pack: forward live deliveries, events, doctor
@@ -276,7 +276,9 @@ Agent loop:
276
276
  Options:
277
277
  --version, -V Print the installed version and exit.
278
278
  --dir Project root (defaults to the current directory).
279
- --port Port to listen on (default 3000).
279
+ --port Port to listen on (default 3000). When the default is
280
+ taken, serve tries the next free port and prints a notice.
281
+ An explicit --port fails with a next-port hint instead.
280
282
  --host Host to bind (default 127.0.0.1).
281
283
  --mode serve: "multi" (default, slugged /<dir>/v1/*) or "single"
282
284
  (unslugged /v1/*). Multi also exposes host /v1/health for
@@ -402,6 +404,9 @@ Options:
402
404
  --allow-domain deploy: egress domain the engine pod may reach
403
405
  (repeatable; unioned with hosting.egressDomains from the
404
406
  agent manifest). Repo-backed deploys only.
407
+ --from-argv secrets set: allow NAME=VALUE on argv (leaks into shell
408
+ history and agent-captured terminals). Default is names
409
+ only with a hidden prompt or piped stdin.
405
410
  --org github forward: organization login (org-level hooks).
406
411
  --channel github: select one channel id when several are declared.
407
412
  --secret github forward: webhook signing secret (defaults to
@@ -535,6 +540,7 @@ async function main(argv: string[]): Promise<number> {
535
540
  all: { type: "boolean", default: false },
536
541
  "cursor-events-repo": { type: "string", multiple: true },
537
542
  "allow-domain": { type: "string", multiple: true },
543
+ "from-argv": { type: "boolean", default: false },
538
544
  strict: { type: "boolean", default: false },
539
545
  junit: { type: "string" },
540
546
  artifacts: { type: "string" },
@@ -1148,8 +1154,13 @@ async function main(argv: string[]): Promise<number> {
1148
1154
  }
1149
1155
  const serveDir = serveDirResult;
1150
1156
  const port =
1151
- values.port === undefined ? 3000 : Number.parseInt(values.port, 10);
1152
- if (Number.isNaN(port) || port < 0 || port > 65535) {
1157
+ values.port === undefined
1158
+ ? undefined
1159
+ : Number.parseInt(values.port, 10);
1160
+ if (
1161
+ port !== undefined &&
1162
+ (Number.isNaN(port) || port < 0 || port > 65535)
1163
+ ) {
1153
1164
  process.stderr.write(`invalid --port: ${values.port}\n`);
1154
1165
  return 1;
1155
1166
  }
@@ -1303,12 +1314,14 @@ function deployOptionsFromValues(values: {
1303
1314
  json?: boolean;
1304
1315
  "no-wait"?: boolean;
1305
1316
  "api-key"?: string;
1317
+ "from-argv"?: boolean;
1306
1318
  }): DeployCliOptions {
1307
1319
  return {
1308
- ...(values.team === undefined ? {} : { team: values.team }),
1320
+ team: values.team,
1309
1321
  json: values.json === true,
1310
1322
  noWait: values["no-wait"] === true,
1311
- ...(values["api-key"] === undefined ? {} : { apiKey: values["api-key"] }),
1323
+ apiKey: values["api-key"],
1324
+ fromArgv: values["from-argv"] === true ? true : undefined,
1312
1325
  };
1313
1326
  }
1314
1327
 
@@ -1484,7 +1497,10 @@ function printServeBanner(
1484
1497
  if (playgroundUrl !== undefined) {
1485
1498
  process.stdout.write(` playground ${c.cyan(playgroundUrl)}\n`);
1486
1499
  }
1487
-
1500
+ // `/` is only mounted when the playground host home is enabled.
1501
+ if (!opts.noPlayground) {
1502
+ process.stdout.write(` home ${c.cyan(handle.url + "/")}\n`);
1503
+ }
1488
1504
  // Dev is playground-first (Vite proxies the API). Keep API/route detail for
1489
1505
  // plain `serve`, where operators often curl the HTTP surface directly.
1490
1506
  if (!opts.dev) {
@@ -204,25 +204,30 @@ export async function setThreadStatus(args: {
204
204
  return { ok: false, error: "missing_channel_or_thread" };
205
205
  }
206
206
  const status = truncateTypingStatus(args.status);
207
- const loadingRaw =
208
- args.loadingMessages !== undefined && args.loadingMessages.length > 0
209
- ? args.loadingMessages
210
- : status.length > 0
211
- ? [
207
+ const body: Record<string, unknown> = {
208
+ channel_id: args.channelId,
209
+ thread_ts: args.threadTs,
210
+ status,
211
+ };
212
+ // Empty status clears Slack's assistant chip ("App is working…").
213
+ // Sending loading_messages with it keeps the chip rotating.
214
+ if (status.length > 0) {
215
+ const loadingRaw =
216
+ args.loadingMessages !== undefined && args.loadingMessages.length > 0
217
+ ? args.loadingMessages
218
+ : [
212
219
  status,
213
220
  ...SLACK_DEFAULT_LOADING_MESSAGES.filter((m) => m !== status),
214
- ]
215
- : [];
216
- const loading_messages = truncateLoadingMessages(loadingRaw);
221
+ ];
222
+ const loading_messages = truncateLoadingMessages(loadingRaw);
223
+ if (loading_messages.length > 0) {
224
+ body.loading_messages = loading_messages;
225
+ }
226
+ }
217
227
  return callSlackApiSoft({
218
228
  botToken: args.botToken,
219
229
  operation: "assistant.threads.setStatus",
220
- body: {
221
- channel_id: args.channelId,
222
- thread_ts: args.threadTs,
223
- status,
224
- ...(loading_messages.length > 0 ? { loading_messages } : {}),
225
- },
230
+ body,
226
231
  });
227
232
  }
228
233