@cursor/july 0.1.94 → 0.1.96

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 (332) hide show
  1. package/AGENTS.md +3 -4
  2. package/dist/channels/deployments/types.d.ts +1 -1
  3. package/dist/channels/deployments/types.d.ts.map +1 -1
  4. package/dist/channels/deployments/types.js +1 -0
  5. package/dist/channels/github/github-channel.d.ts.map +1 -1
  6. package/dist/channels/github/github-channel.js +14 -2
  7. package/dist/channels/github/types.d.ts +18 -3
  8. package/dist/channels/github/types.d.ts.map +1 -1
  9. package/dist/channels/origin/api.d.ts +59 -13
  10. package/dist/channels/origin/api.d.ts.map +1 -1
  11. package/dist/channels/origin/api.js +168 -27
  12. package/dist/channels/origin/binding.d.ts +2 -2
  13. package/dist/channels/origin/checks.d.ts +6 -6
  14. package/dist/channels/origin/checks.js +6 -6
  15. package/dist/channels/origin/cursor-account.d.ts +5 -3
  16. package/dist/channels/origin/cursor-account.d.ts.map +1 -1
  17. package/dist/channels/origin/cursor-account.js +5 -3
  18. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  19. package/dist/channels/origin/origin-channel.js +11 -3
  20. package/dist/channels/origin/origin-webhook.d.ts +11 -1
  21. package/dist/channels/origin/origin-webhook.d.ts.map +1 -1
  22. package/dist/channels/origin/origin-webhook.js +23 -3
  23. package/dist/channels/origin/types.d.ts +8 -0
  24. package/dist/channels/origin/types.d.ts.map +1 -1
  25. package/dist/channels/slack/api.d.ts +9 -0
  26. package/dist/channels/slack/api.d.ts.map +1 -1
  27. package/dist/channels/slack/api.js +36 -5
  28. package/dist/channels/slack/defaults.d.ts.map +1 -1
  29. package/dist/channels/slack/defaults.js +17 -46
  30. package/dist/channels/slack/dispatch.d.ts +7 -1
  31. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  32. package/dist/channels/slack/dispatch.js +47 -23
  33. package/dist/channels/slack/inbound.d.ts +8 -0
  34. package/dist/channels/slack/inbound.d.ts.map +1 -1
  35. package/dist/channels/slack/inbound.js +32 -0
  36. package/dist/channels/slack/live-delivery.d.ts +26 -29
  37. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  38. package/dist/channels/slack/live-delivery.js +172 -73
  39. package/dist/channels/slack/manifest.d.ts.map +1 -1
  40. package/dist/channels/slack/manifest.js +5 -1
  41. package/dist/channels/slack/nudge-carrier.d.ts +2 -0
  42. package/dist/channels/slack/nudge-carrier.d.ts.map +1 -1
  43. package/dist/channels/slack/nudge-carrier.js +48 -1
  44. package/dist/channels/slack/pr-ref.d.ts +7 -1
  45. package/dist/channels/slack/pr-ref.d.ts.map +1 -1
  46. package/dist/channels/slack/pr-ref.js +42 -23
  47. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  48. package/dist/channels/slack/slack-channel.js +2 -2
  49. package/dist/docs/404.html +2 -2
  50. package/dist/docs/ab.html +4 -4
  51. package/dist/docs/assets/{app.CFDEas4I.js → app.jXleCmOc.js} +1 -1
  52. package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +1 -0
  53. package/dist/docs/assets/chunks/{VPLocalSearchBox.B1IIYpYS.js → VPLocalSearchBox.8LhyglIT.js} +1 -1
  54. package/dist/docs/assets/chunks/{theme.Ct4NSiLm.js → theme.9ged-pr2.js} +2 -2
  55. package/dist/docs/assets/index.md.CI93Ltlb.js +5 -0
  56. package/dist/docs/assets/{index.md.DRakGHFe.lean.js → index.md.CI93Ltlb.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.js → reference_cli.md.CCnG-nJx.js} +4 -2
  58. package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.lean.js → reference_cli.md.CCnG-nJx.lean.js} +1 -1
  59. package/dist/docs/assets/{reference_skills.md.8son6Hjm.js → reference_skills.md.BFW9retM.js} +3 -3
  60. package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.js → templates_agentic-owners.md.BZSH4N9z.js} +1 -1
  61. package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.js +2 -0
  62. package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.lean.js +1 -0
  63. package/dist/docs/assets/{templates_demo.md.DhFcWN6j.js → templates_demo.md.BeQX9V3H.js} +1 -1
  64. package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.js → templates_pr-autofixer.md.x5zl6-GT.js} +2 -2
  65. package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.lean.js → templates_pr-autofixer.md.x5zl6-GT.lean.js} +1 -1
  66. package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.js +4 -0
  67. package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.lean.js +1 -0
  68. package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.js → templates_security-reviewer.md.lshxbCLK.js} +2 -2
  69. package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.lean.js → templates_security-reviewer.md.lshxbCLK.lean.js} +1 -1
  70. package/dist/docs/assets/{templates_triage.md.CVlpctKS.js → templates_triage.md.Co4UNzkZ.js} +3 -3
  71. package/dist/docs/assets/{templates_triage.md.CVlpctKS.lean.js → templates_triage.md.Co4UNzkZ.lean.js} +1 -1
  72. package/dist/docs/building-with-agents.html +4 -4
  73. package/dist/docs/concepts.html +4 -4
  74. package/dist/docs/deployment.html +4 -4
  75. package/dist/docs/evals.html +4 -4
  76. package/dist/docs/guides/agent-to-agent.html +4 -4
  77. package/dist/docs/guides/cloud-runtime.html +4 -4
  78. package/dist/docs/guides/convert-automation.html +4 -4
  79. package/dist/docs/guides/github.html +4 -4
  80. package/dist/docs/guides/human-in-the-loop.html +4 -4
  81. package/dist/docs/guides/mcp-oauth.html +4 -4
  82. package/dist/docs/guides/opentelemetry.html +4 -4
  83. package/dist/docs/guides/slack.html +4 -4
  84. package/dist/docs/guides/webhooks.html +4 -4
  85. package/dist/docs/hashmap.json +1 -1
  86. package/dist/docs/hillclimbing.html +4 -4
  87. package/dist/docs/index.html +6 -6
  88. package/dist/docs/index.md +5 -1
  89. package/dist/docs/llms-full.txt +189 -26
  90. package/dist/docs/llms.txt +2 -0
  91. package/dist/docs/quickstart.html +4 -4
  92. package/dist/docs/reference/agent-config.html +4 -4
  93. package/dist/docs/reference/artifacts.html +4 -4
  94. package/dist/docs/reference/channels.html +4 -4
  95. package/dist/docs/reference/cli.html +8 -6
  96. package/dist/docs/reference/cli.md +4 -1
  97. package/dist/docs/reference/connections.html +4 -4
  98. package/dist/docs/reference/hooks.html +4 -4
  99. package/dist/docs/reference/http-api.html +4 -4
  100. package/dist/docs/reference/instructions.html +4 -4
  101. package/dist/docs/reference/playground.html +4 -4
  102. package/dist/docs/reference/project-layout.html +4 -4
  103. package/dist/docs/reference/prompt.html +4 -4
  104. package/dist/docs/reference/schedules.html +4 -4
  105. package/dist/docs/reference/sessions.html +4 -4
  106. package/dist/docs/reference/skills.html +7 -7
  107. package/dist/docs/reference/subagents.html +4 -4
  108. package/dist/docs/reference/tools.html +4 -4
  109. package/dist/docs/scaffolding-agents.html +4 -4
  110. package/dist/docs/storage.html +4 -4
  111. package/dist/docs/templates/agentic-owners.html +7 -7
  112. package/dist/docs/templates/agentic-owners.md +1 -1
  113. package/dist/docs/templates/code-wiki.html +28 -0
  114. package/dist/docs/templates/code-wiki.md +91 -0
  115. package/dist/docs/templates/demo.html +6 -6
  116. package/dist/docs/templates/demo.md +3 -2
  117. package/dist/docs/templates/pr-autofixer.html +6 -6
  118. package/dist/docs/templates/pr-autofixer.md +7 -13
  119. package/dist/docs/templates/security-help.html +30 -0
  120. package/dist/docs/templates/security-help.md +65 -0
  121. package/dist/docs/templates/security-reviewer.html +6 -6
  122. package/dist/docs/templates/security-reviewer.md +1 -2
  123. package/dist/docs/templates/triage.html +7 -7
  124. package/dist/docs/templates/triage.md +2 -6
  125. package/dist/docs/troubleshooting.html +4 -4
  126. package/dist/files-backends/cursor-hosted.d.ts +26 -0
  127. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  128. package/dist/files-backends/cursor-hosted.js +32 -4
  129. package/dist/files.d.ts +2 -0
  130. package/dist/files.d.ts.map +1 -1
  131. package/dist/files.js +5 -0
  132. package/dist/internal/advertise-tools.d.ts +2 -0
  133. package/dist/internal/advertise-tools.d.ts.map +1 -1
  134. package/dist/internal/advertise-tools.js +15 -1
  135. package/dist/internal/bounded.d.ts +7 -0
  136. package/dist/internal/bounded.d.ts.map +1 -0
  137. package/dist/internal/bounded.js +32 -0
  138. package/dist/internal/cli-deploy.d.ts.map +1 -1
  139. package/dist/internal/cli-deploy.js +142 -7
  140. package/dist/internal/conversation-mirror.d.ts +85 -0
  141. package/dist/internal/conversation-mirror.d.ts.map +1 -0
  142. package/dist/internal/conversation-mirror.js +312 -0
  143. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  144. package/dist/internal/cursor/backend-client.js +22 -48
  145. package/dist/internal/cursor/session-token.d.ts +45 -0
  146. package/dist/internal/cursor/session-token.d.ts.map +1 -0
  147. package/dist/internal/cursor/session-token.js +99 -0
  148. package/dist/internal/cursor-agent-template.d.ts +1 -1
  149. package/dist/internal/cursor-agent-template.d.ts.map +1 -1
  150. package/dist/internal/cursor-agent-template.js +1 -0
  151. package/dist/internal/deferred-channel-session.d.ts +20 -0
  152. package/dist/internal/deferred-channel-session.d.ts.map +1 -0
  153. package/dist/internal/deferred-channel-session.js +62 -0
  154. package/dist/internal/deploy-client.d.ts +13 -1
  155. package/dist/internal/deploy-client.d.ts.map +1 -1
  156. package/dist/internal/deploy-client.js +11 -1
  157. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  158. package/dist/internal/deploy-manifest.js +11 -5
  159. package/dist/internal/discovery.d.ts.map +1 -1
  160. package/dist/internal/discovery.js +22 -2
  161. package/dist/internal/framework-file-storage.d.ts +61 -0
  162. package/dist/internal/framework-file-storage.d.ts.map +1 -0
  163. package/dist/internal/framework-file-storage.js +204 -0
  164. package/dist/internal/framework-storage-selection.d.ts +38 -16
  165. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  166. package/dist/internal/framework-storage-selection.js +78 -17
  167. package/dist/internal/hosted-admission-context.d.ts +20 -0
  168. package/dist/internal/hosted-admission-context.d.ts.map +1 -0
  169. package/dist/internal/hosted-admission-context.js +31 -0
  170. package/dist/internal/hosted-delivery-protocol.d.ts +5 -0
  171. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  172. package/dist/internal/hosted-delivery-protocol.js +33 -1
  173. package/dist/internal/hosted-delivery.d.ts +11 -2
  174. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  175. package/dist/internal/hosted-delivery.js +79 -35
  176. package/dist/internal/hosted-managed-automation.d.ts +30 -0
  177. package/dist/internal/hosted-managed-automation.d.ts.map +1 -0
  178. package/dist/internal/hosted-managed-automation.js +58 -0
  179. package/dist/internal/init-project.d.ts.map +1 -1
  180. package/dist/internal/init-project.js +2 -0
  181. package/dist/internal/install-cursor-skills.d.ts.map +1 -1
  182. package/dist/internal/install-cursor-skills.js +1 -3
  183. package/dist/internal/managed-delivery.d.ts +80 -0
  184. package/dist/internal/managed-delivery.d.ts.map +1 -0
  185. package/dist/internal/managed-delivery.js +86 -0
  186. package/dist/internal/review-comments.d.ts.map +1 -1
  187. package/dist/internal/review-comments.js +10 -98
  188. package/dist/internal/runtime-dispatch-runner.d.ts +5 -1
  189. package/dist/internal/runtime-dispatch-runner.d.ts.map +1 -1
  190. package/dist/internal/runtime-dispatch-runner.js +7 -0
  191. package/dist/internal/scm/pr-url.d.ts +14 -0
  192. package/dist/internal/scm/pr-url.d.ts.map +1 -0
  193. package/dist/internal/scm/pr-url.js +65 -0
  194. package/dist/internal/sdk-runner.d.ts +25 -0
  195. package/dist/internal/sdk-runner.d.ts.map +1 -1
  196. package/dist/internal/sdk-runner.js +70 -0
  197. package/dist/internal/server.d.ts.map +1 -1
  198. package/dist/internal/server.js +11 -2
  199. package/dist/internal/session-engine.d.ts +13 -0
  200. package/dist/internal/session-engine.d.ts.map +1 -1
  201. package/dist/internal/session-engine.js +71 -3
  202. package/dist/internal/steering.d.ts +15 -0
  203. package/dist/internal/steering.d.ts.map +1 -0
  204. package/dist/internal/steering.js +18 -0
  205. package/dist/playground/assets/index-B1DDvBeC.css +1 -0
  206. package/dist/playground/assets/{index-B3JCyigB.js → index-Cx9CclK_.js} +47 -47
  207. package/dist/playground/index.html +2 -2
  208. package/dist/storage-backends/cursor-hosted.d.ts +8 -0
  209. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  210. package/dist/storage-backends/cursor-hosted.js +4 -3
  211. package/dist/types.d.ts +2 -1
  212. package/dist/types.d.ts.map +1 -1
  213. package/docs/README.md +5 -1
  214. package/docs/reference/cli.md +4 -1
  215. package/docs/templates/agentic-owners.md +1 -1
  216. package/docs/templates/code-wiki.md +96 -0
  217. package/docs/templates/demo.md +3 -2
  218. package/docs/templates/pr-autofixer.md +7 -13
  219. package/docs/templates/security-help.md +70 -0
  220. package/docs/templates/security-reviewer.md +1 -2
  221. package/docs/templates/triage.md +2 -6
  222. package/package.json +2 -2
  223. package/skills/ab/SKILL.md +13 -97
  224. package/skills/create-agent/SKILL.md +123 -223
  225. package/skills/debug/SKILL.md +34 -68
  226. package/skills/evals/SKILL.md +55 -180
  227. package/skills/framework-map/SKILL.md +83 -145
  228. package/skills/github/SKILL.md +53 -111
  229. package/skills/hillclimb/SKILL.md +40 -163
  230. package/skills/mcp-auth/SKILL.md +48 -107
  231. package/skills/otel/SKILL.md +22 -68
  232. package/skills/setup-slack/SKILL.md +77 -208
  233. package/src/channels/deployments/types.ts +1 -0
  234. package/src/channels/github/github-channel.ts +23 -8
  235. package/src/channels/github/types.ts +19 -2
  236. package/src/channels/origin/api.ts +242 -30
  237. package/src/channels/origin/binding.ts +2 -2
  238. package/src/channels/origin/checks.ts +6 -6
  239. package/src/channels/origin/cursor-account.ts +5 -3
  240. package/src/channels/origin/origin-channel.ts +13 -1
  241. package/src/channels/origin/origin-webhook.ts +27 -3
  242. package/src/channels/origin/types.ts +8 -0
  243. package/src/channels/slack/api.ts +41 -5
  244. package/src/channels/slack/defaults.ts +15 -49
  245. package/src/channels/slack/dispatch.ts +56 -22
  246. package/src/channels/slack/inbound.ts +43 -0
  247. package/src/channels/slack/live-delivery.ts +235 -114
  248. package/src/channels/slack/manifest.ts +5 -1
  249. package/src/channels/slack/nudge-carrier.ts +48 -1
  250. package/src/channels/slack/pr-ref.ts +56 -25
  251. package/src/channels/slack/slack-channel.ts +2 -0
  252. package/src/files-backends/cursor-hosted.ts +41 -2
  253. package/src/files.ts +11 -0
  254. package/src/internal/advertise-tools.ts +18 -2
  255. package/src/internal/bounded.ts +28 -0
  256. package/src/internal/cli-deploy.ts +183 -7
  257. package/src/internal/conversation-mirror.ts +406 -0
  258. package/src/internal/cursor/backend-client.ts +34 -58
  259. package/src/internal/cursor/session-token.ts +115 -0
  260. package/src/internal/cursor-agent-template.ts +1 -0
  261. package/src/internal/deferred-channel-session.ts +61 -0
  262. package/src/internal/deploy-client.ts +24 -1
  263. package/src/internal/deploy-manifest.ts +10 -5
  264. package/src/internal/discovery.ts +25 -2
  265. package/src/internal/framework-file-storage.ts +222 -0
  266. package/src/internal/framework-storage-selection.ts +101 -19
  267. package/src/internal/hosted-admission-context.ts +37 -0
  268. package/src/internal/hosted-delivery-protocol.ts +44 -1
  269. package/src/internal/hosted-delivery.ts +105 -40
  270. package/src/internal/hosted-managed-automation.ts +72 -0
  271. package/src/internal/init-project.ts +2 -0
  272. package/src/internal/install-cursor-skills.ts +1 -4
  273. package/src/internal/managed-delivery.ts +125 -0
  274. package/src/internal/review-comments.ts +10 -113
  275. package/src/internal/runtime-dispatch-runner.ts +10 -0
  276. package/src/internal/scm/pr-url.ts +95 -0
  277. package/src/internal/sdk-runner.ts +103 -0
  278. package/src/internal/server.ts +12 -0
  279. package/src/internal/session-engine.ts +76 -4
  280. package/src/internal/steering.ts +17 -0
  281. package/src/storage-backends/cursor-hosted.ts +10 -1
  282. package/src/types.ts +2 -1
  283. package/templates/agentic-owners/README.md +1 -1
  284. package/templates/agentic-owners/agent/agent.ts +0 -10
  285. package/templates/agentic-owners/agent/channels/github.ts +5 -14
  286. package/templates/agentic-owners/agent/lib/config.ts +0 -8
  287. package/templates/agentic-owners/agent/lib/review.ts +2 -15
  288. package/templates/agentic-owners/agent/tools/record_review.ts +2 -4
  289. package/templates/code-wiki/README.md +34 -0
  290. package/templates/code-wiki/agent/agent.ts +8 -0
  291. package/templates/code-wiki/agent/channels/github.ts +144 -0
  292. package/templates/code-wiki/agent/instructions.md +39 -0
  293. package/templates/code-wiki/agent/lib/repos.ts +5 -0
  294. package/templates/code-wiki/agent/lib/wiki.ts +22 -0
  295. package/templates/code-wiki/agent/tools/apply_wiki.ts +219 -0
  296. package/templates/code-wiki/evals/evals.config.ts +5 -0
  297. package/templates/code-wiki/evals/wiki.eval.ts +132 -0
  298. package/templates/code-wiki/init.json +25 -0
  299. package/templates/code-wiki/package.json +18 -0
  300. package/templates/code-wiki/tsconfig.json +12 -0
  301. package/templates/demo/agent/agent.ts +0 -10
  302. package/templates/pr-autofixer/README.md +0 -2
  303. package/templates/pr-autofixer/agent/agent.ts +0 -11
  304. package/templates/pr-autofixer/agent/channels/slack.ts +0 -7
  305. package/templates/pr-autofixer/agent/lib/pr-state.ts +5 -17
  306. package/templates/pr-autofixer/agent/lib/repos.ts +0 -1
  307. package/templates/security-help/README.md +2 -2
  308. package/templates/security-help/agent/agent.ts +1 -2
  309. package/templates/security-help/agent/channels/slack.ts +0 -3
  310. package/templates/security-help/agent/instructions.md +9 -10
  311. package/templates/security-help/agent/skills/access-request.md +1 -1
  312. package/templates/security-help/agent/skills/faq.md +31 -0
  313. package/templates/security-help/agent/skills/security-playbooks.md +1 -1
  314. package/templates/security-help/package.json +1 -2
  315. package/templates/security-reviewer/agent/agent.ts +0 -10
  316. package/templates/triage/agent/agent.ts +0 -10
  317. package/templates/triage/agent/channels/queue.ts +1 -1
  318. package/templates/triage/agent/channels/webhook.ts +1 -3
  319. package/dist/docs/assets/chunks/@localSearchIndexroot.DU3U2Ij2.js +0 -1
  320. package/dist/docs/assets/index.md.DRakGHFe.js +0 -5
  321. package/dist/internal/deployment-scm-event-stream.d.ts +0 -116
  322. package/dist/internal/deployment-scm-event-stream.d.ts.map +0 -1
  323. package/dist/internal/deployment-scm-event-stream.js +0 -474
  324. package/dist/playground/assets/index-D9N7-q97.css +0 -1
  325. package/src/internal/deployment-scm-event-stream.ts +0 -593
  326. package/templates/security-help/agent/knowledge/faq/approvals.md +0 -5
  327. package/templates/security-help/agent/knowledge/faq/channels.md +0 -6
  328. package/templates/security-help/agent/knowledge/faq/phishing.md +0 -10
  329. package/templates/security-help/agent/skills/security-first-pass.md +0 -15
  330. /package/dist/docs/assets/{reference_skills.md.8son6Hjm.lean.js → reference_skills.md.BFW9retM.lean.js} +0 -0
  331. /package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.lean.js → templates_agentic-owners.md.BZSH4N9z.lean.js} +0 -0
  332. /package/dist/docs/assets/{templates_demo.md.DhFcWN6j.lean.js → templates_demo.md.BeQX9V3H.lean.js} +0 -0
@@ -4,12 +4,16 @@
4
4
  * Cursor-account channels do not use this path — they reply with a single
5
5
  * final `chat.postMessage` and put thinking on `assistant.threads.setStatus`
6
6
  * (no message edits).
7
+ *
8
+ * Assistant-pane threads only render stream APIs. Start lazily with the
9
+ * first real markdown/chunks plus recipient ids — empty startStream is
10
+ * rejected or never attaches.
7
11
  */
8
12
 
9
13
  import type { ChannelEventContext } from "../../types.js";
10
14
  import { appendChatStream, startChatStream, stopChatStream } from "./api.js";
11
15
  import type { SlackLog } from "./log.js";
12
- import { computeStreamAppend } from "./stream.js";
16
+ import { computeStreamAppend, type StreamTaskChunk } from "./stream.js";
13
17
  import type { SlackBotToken, SlackChannelState } from "./types.js";
14
18
 
15
19
  type SlackChannel = ChannelEventContext<SlackChannelState>;
@@ -19,96 +23,195 @@ export interface LiveDeliveryDeps {
19
23
  getLog: () => SlackLog;
20
24
  }
21
25
 
26
+ type OpenArgs = {
27
+ channel: SlackChannel;
28
+ channelId: string;
29
+ threadTs: string;
30
+ sessionId: string;
31
+ markdown_text?: string;
32
+ chunks?: StreamTaskChunk[];
33
+ };
34
+
35
+ type StreamArgs = {
36
+ channel: SlackChannel;
37
+ channelId: string;
38
+ sessionId: string;
39
+ };
40
+
22
41
  /**
23
42
  * How the default handlers deliver an in-progress reply. Methods write live
24
43
  * delivery state (`streamTs`, `streamChannelId`, `lastPostedText`,
25
44
  * `streamingUnavailable`) on `channel.state` directly.
26
45
  */
27
46
  export interface LiveDeliveryStrategy {
28
- /** True when tool-call thinking steps can be appended to the live message. */
29
47
  readonly inlineToolSteps: boolean;
30
- /** Open the live message (stream start). */
31
- start(args: {
32
- channel: SlackChannel;
33
- channelId: string;
34
- threadTs: string;
35
- sessionId: string;
36
- }): Promise<void>;
37
- /** Push the latest cumulative text to the live message (throttled). */
38
- append(args: {
39
- channel: SlackChannel;
40
- channelId: string;
41
- streamTs: string;
42
- text: string;
43
- sessionId: string;
44
- }): Promise<void>;
48
+ /** Open the live message with the first real markdown or task chunks. */
49
+ open(args: OpenArgs): Promise<boolean>;
50
+ append(args: StreamArgs & { text: string }): Promise<void>;
51
+ appendChunks(
52
+ args: StreamArgs & { chunks: StreamTaskChunk[] }
53
+ ): Promise<boolean>;
45
54
  /**
46
55
  * Flush the final reply text. `delivered` is false only when the caller must
47
56
  * post the full reply as a fresh message (finalize failed on partial text).
48
57
  */
49
- finalize(args: {
50
- channel: SlackChannel;
51
- channelId: string;
52
- streamTs: string;
53
- text: string;
54
- sessionId: string;
55
- }): Promise<{ delivered: boolean }>;
56
- /** Close the live message when the turn produced no final text. */
57
- finalizeEmpty(args: {
58
- channel: SlackChannel;
59
- channelId: string;
60
- streamTs: string;
61
- }): Promise<void>;
62
- /** Best-effort remove a live message left open after the turn ended. */
58
+ finalize(
59
+ args: StreamArgs & { text: string }
60
+ ): Promise<{ delivered: boolean }>;
61
+ finalizeEmpty(args: { channel: SlackChannel }): Promise<void>;
63
62
  cleanupOrphan(args: { channel: SlackChannel }): Promise<void>;
64
- /** Deliver the error text into the live message; `delivered` gates a fallback post. */
65
- fail(args: {
66
- channel: SlackChannel;
67
- channelId: string;
68
- streamTs: string;
69
- errorText: string;
70
- sessionId: string;
71
- }): Promise<{ delivered: boolean }>;
63
+ fail(
64
+ args: StreamArgs & { errorText: string }
65
+ ): Promise<{ delivered: boolean }>;
66
+ }
67
+
68
+ function streamRecipients(channel: SlackChannel): {
69
+ recipient_user_id: string | undefined;
70
+ recipient_team_id: string | undefined;
71
+ } {
72
+ const user = channel.state.triggeringUserId;
73
+ const team = channel.state.teamId;
74
+ return {
75
+ recipient_user_id: user === null || user === "" ? undefined : user,
76
+ recipient_team_id: team === null || team === "" ? undefined : team,
77
+ };
78
+ }
79
+
80
+ function activeStreamTs(channel: SlackChannel): string | null {
81
+ const ts = channel.state.streamTs;
82
+ return ts === null || ts === "" ? null : ts;
72
83
  }
73
84
 
74
85
  export function createStreamDeliveryStrategy(
75
86
  deps: LiveDeliveryDeps
76
87
  ): LiveDeliveryStrategy {
77
88
  const { botToken, getLog } = deps;
78
- return {
79
- inlineToolSteps: true,
80
89
 
81
- async start({ channel, channelId, threadTs, sessionId }) {
82
- const log = getLog();
83
- const started = await startChatStream({
84
- botToken,
90
+ const open: LiveDeliveryStrategy["open"] = async ({
91
+ channel,
92
+ channelId,
93
+ threadTs,
94
+ sessionId,
95
+ markdown_text,
96
+ chunks,
97
+ }) => {
98
+ if (activeStreamTs(channel) !== null) {
99
+ return true;
100
+ }
101
+ const hasMarkdown = markdown_text !== undefined && markdown_text !== "";
102
+ const hasChunks = chunks !== undefined && chunks.length > 0;
103
+ if (!hasMarkdown && !hasChunks) {
104
+ return false;
105
+ }
106
+ const log = getLog();
107
+ const started = await startChatStream({
108
+ botToken,
109
+ channel: channelId,
110
+ thread_ts: threadTs,
111
+ markdown_text: hasMarkdown ? markdown_text : undefined,
112
+ chunks: hasChunks ? chunks : undefined,
113
+ ...streamRecipients(channel),
114
+ });
115
+ if (!started.ok) {
116
+ log.warn("chat.startStream unavailable", {
117
+ error: started.error,
85
118
  channel: channelId,
86
119
  thread_ts: threadTs,
87
- markdown_text: "",
88
- });
89
- if (!started.ok) {
90
- log.warn("chat.startStream unavailable", {
91
- error: started.error,
92
- channel: channelId,
93
- thread_ts: threadTs,
94
- session: sessionId,
95
- });
96
- if (started.unsupported === true) {
97
- channel.state.streamingUnavailable = true;
98
- }
99
- return;
100
- }
101
- channel.state.streamTs = started.ts;
102
- channel.state.streamChannelId = started.channel;
103
- channel.state.lastPostedText = "";
104
- log.info("chat.startStream ok", {
105
- channel: started.channel,
106
- stream_ts: started.ts,
107
120
  session: sessionId,
108
121
  });
109
- },
122
+ if (started.unsupported === true) {
123
+ channel.state.streamingUnavailable = true;
124
+ }
125
+ return false;
126
+ }
127
+ channel.state.streamTs = started.ts;
128
+ channel.state.streamChannelId = started.channel;
129
+ channel.state.lastPostedText = hasMarkdown ? (markdown_text ?? "") : "";
130
+ log.info("chat.startStream ok", {
131
+ channel: started.channel,
132
+ stream_ts: started.ts,
133
+ session: sessionId,
134
+ });
135
+ return true;
136
+ };
137
+
138
+ const ensureOpen = async (
139
+ args: StreamArgs & {
140
+ markdown_text?: string;
141
+ chunks?: StreamTaskChunk[];
142
+ }
143
+ ): Promise<string | null> => {
144
+ const existing = activeStreamTs(args.channel);
145
+ if (existing !== null) {
146
+ return existing;
147
+ }
148
+ const threadTs = args.channel.state.threadTs;
149
+ if (threadTs === null || threadTs === "") {
150
+ return null;
151
+ }
152
+ const opened = await open({
153
+ channel: args.channel,
154
+ channelId: args.channelId,
155
+ threadTs,
156
+ sessionId: args.sessionId,
157
+ markdown_text: args.markdown_text,
158
+ chunks: args.chunks,
159
+ });
160
+ return opened ? activeStreamTs(args.channel) : null;
161
+ };
162
+
163
+ const stopWithSuffix = async (args: {
164
+ channel: SlackChannel;
165
+ channelId: string;
166
+ ts: string;
167
+ text: string;
168
+ }): Promise<{ ok: boolean; error?: string }> => {
169
+ const appendText = computeStreamAppend(
170
+ args.channel.state.lastPostedText,
171
+ args.text
172
+ );
173
+ const stopped = await stopChatStream({
174
+ botToken,
175
+ channel: args.channelId,
176
+ ts: args.ts,
177
+ markdown_text: appendText === "" ? undefined : appendText,
178
+ });
179
+ args.channel.state.streamTs = null;
180
+ if (stopped.ok) {
181
+ args.channel.state.lastPostedText = args.text;
182
+ args.channel.state.lastBotMessageTs = args.ts;
183
+ return { ok: true };
184
+ }
185
+ if (stopped.unsupported === true) {
186
+ args.channel.state.streamingUnavailable = true;
187
+ }
188
+ return { ok: false, error: stopped.error };
189
+ };
110
190
 
111
- async append({ channel, channelId, streamTs, text, sessionId }) {
191
+ const closeOrphan = async (channel: SlackChannel): Promise<void> => {
192
+ const orphanTs = activeStreamTs(channel);
193
+ const channelId = channel.state.streamChannelId ?? channel.state.channelId;
194
+ if (orphanTs === null || channelId === null || channelId === "") {
195
+ return;
196
+ }
197
+ await stopChatStream({ botToken, channel: channelId, ts: orphanTs });
198
+ channel.state.streamTs = null;
199
+ };
200
+
201
+ return {
202
+ inlineToolSteps: true,
203
+ open,
204
+
205
+ async append({ channel, channelId, text, sessionId }) {
206
+ const ts = await ensureOpen({
207
+ channel,
208
+ channelId,
209
+ sessionId,
210
+ markdown_text: text,
211
+ });
212
+ if (ts === null) {
213
+ return;
214
+ }
112
215
  const appendText = computeStreamAppend(
113
216
  channel.state.lastPostedText,
114
217
  text
@@ -119,14 +222,14 @@ export function createStreamDeliveryStrategy(
119
222
  const result = await appendChatStream({
120
223
  botToken,
121
224
  channel: channelId,
122
- ts: streamTs,
225
+ ts,
123
226
  markdown_text: appendText,
124
227
  });
125
228
  if (!result.ok) {
126
229
  getLog().warn("appendStream failed", {
127
230
  error: result.error,
128
231
  channel: channelId,
129
- stream_ts: streamTs,
232
+ stream_ts: ts,
130
233
  session: sessionId,
131
234
  });
132
235
  if (result.unsupported === true) {
@@ -137,26 +240,50 @@ export function createStreamDeliveryStrategy(
137
240
  channel.state.lastPostedText = text;
138
241
  },
139
242
 
140
- async finalize({ channel, channelId, streamTs, text, sessionId }) {
141
- const log = getLog();
142
- // Only send the unsent suffix — full text was already appended.
143
- const appendText = computeStreamAppend(
144
- channel.state.lastPostedText,
145
- text
146
- );
147
- const stopped = await stopChatStream({
243
+ async appendChunks({ channel, channelId, chunks, sessionId }) {
244
+ const hadStream = activeStreamTs(channel) !== null;
245
+ const ts = await ensureOpen({
246
+ channel,
247
+ channelId,
248
+ sessionId,
249
+ chunks,
250
+ });
251
+ if (ts === null) {
252
+ return false;
253
+ }
254
+ if (!hadStream) {
255
+ return true;
256
+ }
257
+ const result = await appendChatStream({
148
258
  botToken,
149
259
  channel: channelId,
150
- ts: streamTs,
151
- markdown_text: appendText === "" ? undefined : appendText,
260
+ ts,
261
+ chunks,
262
+ });
263
+ return result.ok;
264
+ },
265
+
266
+ async finalize({ channel, channelId, text, sessionId }) {
267
+ const log = getLog();
268
+ const ts = await ensureOpen({
269
+ channel,
270
+ channelId,
271
+ sessionId,
272
+ markdown_text: text,
273
+ });
274
+ if (ts === null) {
275
+ return { delivered: false };
276
+ }
277
+ const stopped = await stopWithSuffix({
278
+ channel,
279
+ channelId,
280
+ ts,
281
+ text,
152
282
  });
153
283
  if (stopped.ok) {
154
- channel.state.streamTs = null;
155
- channel.state.lastPostedText = text;
156
- channel.state.lastBotMessageTs = streamTs;
157
284
  log.info("reply delivered via stream", {
158
285
  channel: channelId,
159
- stream_ts: streamTs,
286
+ stream_ts: ts,
160
287
  session: sessionId,
161
288
  chars: text.length,
162
289
  });
@@ -165,52 +292,46 @@ export function createStreamDeliveryStrategy(
165
292
  log.warn("chat.stopStream failed; falling back to postMessage", {
166
293
  error: stopped.error,
167
294
  channel: channelId,
168
- stream_ts: streamTs,
295
+ stream_ts: ts,
169
296
  session: sessionId,
170
297
  });
171
- if (stopped.unsupported === true) {
172
- channel.state.streamingUnavailable = true;
173
- }
174
- channel.state.streamTs = null;
175
- // If we already streamed content, do not post the full reply again.
176
298
  const alreadyStreamed =
177
299
  typeof channel.state.lastPostedText === "string" &&
178
300
  channel.state.lastPostedText.length > 0;
179
301
  return { delivered: alreadyStreamed };
180
302
  },
181
303
 
182
- async finalizeEmpty({ channelId, streamTs }) {
183
- await stopChatStream({ botToken, channel: channelId, ts: streamTs });
304
+ async finalizeEmpty({ channel }) {
305
+ await closeOrphan(channel);
184
306
  },
185
307
 
186
308
  async cleanupOrphan({ channel }) {
187
- // message.completed / turn.failed already stop the stream. If the
188
- // turn ended without a final assistant message, close it here so
189
- // Slack does not leave the live reply in a streaming state.
190
- const streamTs = channel.state.streamTs;
191
- const channelId =
192
- channel.state.streamChannelId ?? channel.state.channelId;
193
- if (
194
- streamTs === null ||
195
- streamTs === "" ||
196
- channelId === null ||
197
- channelId === ""
198
- ) {
199
- return;
200
- }
201
- await stopChatStream({ botToken, channel: channelId, ts: streamTs });
202
- channel.state.streamTs = null;
309
+ await closeOrphan(channel);
203
310
  },
204
311
 
205
- async fail({ channel, channelId, streamTs, errorText }) {
206
- const stopped = await stopChatStream({
207
- botToken,
208
- channel: channelId,
209
- ts: streamTs,
312
+ async fail({ channel, channelId, errorText, sessionId }) {
313
+ const ts = await ensureOpen({
314
+ channel,
315
+ channelId,
316
+ sessionId,
210
317
  markdown_text: errorText,
211
318
  });
212
- channel.state.streamTs = null;
213
- return { delivered: stopped.ok };
319
+ if (ts === null) {
320
+ return { delivered: false };
321
+ }
322
+ const stopped = await stopWithSuffix({
323
+ channel,
324
+ channelId,
325
+ ts,
326
+ text: errorText,
327
+ });
328
+ if (stopped.ok) {
329
+ return { delivered: true };
330
+ }
331
+ const alreadyStreamed =
332
+ typeof channel.state.lastPostedText === "string" &&
333
+ channel.state.lastPostedText.length > 0;
334
+ return { delivered: alreadyStreamed };
214
335
  },
215
336
  };
216
337
  }
@@ -81,7 +81,11 @@ const BOT_SCOPES = [
81
81
  "users:read",
82
82
  ] as const;
83
83
 
84
- const BOT_EVENTS = ["app_mention", "message.im"] as const;
84
+ const BOT_EVENTS = [
85
+ "app_mention",
86
+ "message.im",
87
+ "assistant_thread_started",
88
+ ] as const;
85
89
 
86
90
  /** Slack's display_information.description bound. */
87
91
  const MAX_DESCRIPTION_LENGTH = 140;
@@ -3,13 +3,15 @@
3
3
  * the durable Slack nudge store at tool boundaries (MCP / builtin tools).
4
4
  */
5
5
 
6
- import { mkdir, readFile, writeFile } from "node:fs/promises";
6
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
 
9
9
  export const SLACK_NUDGE_ROOT_REL = ".agent-serve/slack-nudge-root";
10
10
  export const SLACK_NUDGE_CONTINUATION_REL =
11
11
  ".agent-serve/slack-nudge-continuation";
12
12
  export const SLACK_NUDGE_CARRIER_REL = ".agent-serve/slack-nudge-carrier.mjs";
13
+ /** Conversation allowed to claim nudges; subagents share the cwd and would otherwise take the parent's. */
14
+ export const SLACK_NUDGE_OWNER_REL = ".agent-serve/slack-nudge-owner";
13
15
 
14
16
  /** Carrier script body — reads nudge-root + continuation, drains pending. */
15
17
  export const SLACK_NUDGE_CARRIER_SCRIPT: string = `#!/usr/bin/env node
@@ -40,7 +42,46 @@ async function readText(rel) {
40
42
  }
41
43
  }
42
44
 
45
+ async function readStdin() {
46
+ const chunks = [];
47
+ const read = (async () => {
48
+ for await (const chunk of process.stdin) chunks.push(chunk);
49
+ })().catch(() => {});
50
+ await Promise.race([
51
+ read,
52
+ new Promise((resolve) => setTimeout(resolve, 250).unref?.()),
53
+ ]);
54
+ process.stdin.destroy();
55
+ return Buffer.concat(chunks).toString("utf8");
56
+ }
57
+
58
+ /** First caller pins ownership; a subagent cannot call a tool before its parent's Task call. */
59
+ async function ownsStore(caller) {
60
+ if (caller === "") return true;
61
+ const ownerPath = join(workspace, ${JSON.stringify(SLACK_NUDGE_OWNER_REL)});
62
+ try {
63
+ // wx is O_CREAT|O_EXCL, so creating the file is the claim: concurrent hooks cannot both win.
64
+ await writeFile(ownerPath, caller + "\\n", { encoding: "utf8", flag: "wx" });
65
+ return true;
66
+ } catch (error) {
67
+ if (error && error.code !== "EEXIST") return false;
68
+ }
69
+ // Claimed already — the only question left is whether we are the claimant.
70
+ const owner = await readText(${JSON.stringify(SLACK_NUDGE_OWNER_REL)});
71
+ return owner === "" || owner === caller;
72
+ }
73
+
43
74
  async function main() {
75
+ const raw = await readStdin();
76
+ let caller = "";
77
+ try {
78
+ const parsed = JSON.parse(raw);
79
+ if (parsed && typeof parsed.conversation_id === "string") {
80
+ caller = parsed.conversation_id.trim();
81
+ }
82
+ } catch {
83
+ // Fail open: an unparseable payload must not strand the follow-up.
84
+ }
44
85
  const root = await readText(${JSON.stringify(SLACK_NUDGE_ROOT_REL)});
45
86
  const continuation = await readText(
46
87
  ${JSON.stringify(SLACK_NUDGE_CONTINUATION_REL)}
@@ -49,6 +90,11 @@ async function main() {
49
90
  process.stdout.write("{}\\n");
50
91
  return;
51
92
  }
93
+ // Not ours: leave it pending for the parent or the turn-boundary drain.
94
+ if (!(await ownsStore(caller))) {
95
+ process.stdout.write("{}\\n");
96
+ return;
97
+ }
52
98
  const pendingDir = join(root, safeKey(continuation), "pending");
53
99
  const deliveredDir = join(root, safeKey(continuation), "delivered");
54
100
  let names = [];
@@ -157,6 +203,7 @@ export async function materializeSlackNudgeCarrier(args: {
157
203
  SLACK_NUDGE_CARRIER_SCRIPT,
158
204
  "utf8"
159
205
  );
206
+ await rm(join(args.harnessCwd, SLACK_NUDGE_OWNER_REL), { force: true });
160
207
 
161
208
  const hooksPath = join(args.harnessCwd, ".cursor", "hooks.json");
162
209
  let existing: HooksConfig = { version: 1, hooks: {} };
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Best-effort GitHub PR references from free-form chat (Slack, etc.).
2
+ * Best-effort PR references from free-form chat (Slack, etc.).
3
3
  */
4
4
 
5
+ import { type PrProvider, parsePrUrl } from "../../internal/scm/pr-url.js";
6
+
5
7
  export interface GithubPrRef {
6
8
  /** Normalized URL `https://github.com/owner/repo/pull/N`. */
7
9
  url: string;
@@ -12,39 +14,68 @@ export interface GithubPrRef {
12
14
  number: number;
13
15
  }
14
16
 
17
+ export type OriginPrRef = GithubPrRef;
18
+
19
+ const URL_IN_TEXT = /https?:\/\/[^\s>|]+/gi;
20
+
21
+ function githubRef(owner: string, repo: string, number: number): GithubPrRef {
22
+ return {
23
+ owner,
24
+ repo,
25
+ number,
26
+ url: `https://github.com/${owner}/${repo}/pull/${number}`,
27
+ label: `${owner}/${repo}#${number}`,
28
+ };
29
+ }
30
+
31
+ function firstUrlRef(
32
+ text: string,
33
+ provider: PrProvider
34
+ ): GithubPrRef | undefined {
35
+ const matches = text.match(URL_IN_TEXT);
36
+ if (matches === null) {
37
+ return undefined;
38
+ }
39
+ for (const raw of matches) {
40
+ const parsed = parsePrUrl(raw);
41
+ if (parsed?.provider === provider) {
42
+ return {
43
+ owner: parsed.owner,
44
+ repo: parsed.repo,
45
+ number: parsed.number,
46
+ url: parsed.url,
47
+ label: `${parsed.owner}/${parsed.repo}#${parsed.number}`,
48
+ };
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * Find the first Origin PR URL in `text`.
56
+ * Does not treat bare `owner/repo#N` as Origin.
57
+ */
58
+ export function extractOriginPrRef(text: string): OriginPrRef | undefined {
59
+ return firstUrlRef(text, "origin");
60
+ }
61
+
15
62
  /**
16
63
  * Find the first GitHub PR URL or `owner/repo#N` in `text`.
17
64
  * Returns `undefined` when none is present.
18
65
  */
19
66
  export function extractGithubPrRef(text: string): GithubPrRef | undefined {
20
- const urlMatch = text.match(
21
- /https?:\/\/(?:www\.)?github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)/i
22
- );
23
- if (urlMatch !== null) {
24
- const owner = urlMatch[1] as string;
25
- const repo = urlMatch[2] as string;
26
- const number = Number(urlMatch[3]);
27
- return {
28
- owner,
29
- repo,
30
- number,
31
- url: `https://github.com/${owner}/${repo}/pull/${number}`,
32
- label: `${owner}/${repo}#${number}`,
33
- };
67
+ const fromUrl = firstUrlRef(text, "github");
68
+ if (fromUrl !== undefined) {
69
+ return fromUrl;
34
70
  }
35
71
 
36
72
  const hashMatch = text.match(/\b([\w.-]+)\/([\w.-]+)#(\d+)\b/);
37
73
  if (hashMatch === null) {
38
74
  return undefined;
39
75
  }
40
- const owner = hashMatch[1] as string;
41
- const repo = hashMatch[2] as string;
42
- const number = Number(hashMatch[3]);
43
- return {
44
- owner,
45
- repo,
46
- number,
47
- url: `https://github.com/${owner}/${repo}/pull/${number}`,
48
- label: `${owner}/${repo}#${number}`,
49
- };
76
+ return githubRef(
77
+ hashMatch[1] as string,
78
+ hashMatch[2] as string,
79
+ Number(hashMatch[3])
80
+ );
50
81
  }
@@ -238,6 +238,8 @@ export function slackChannel(
238
238
  : { interruptContinuation: args.interruptContinuation }),
239
239
  ...(channelWatch === undefined ? {} : { channelWatch }),
240
240
  ...(botMentionGate === undefined ? {} : { botMentionGate }),
241
+ suggestedPrompts: options.suggestedPrompts,
242
+ suggestedPromptsTitle: options.suggestedPromptsTitle,
241
243
  };
242
244
  }
243
245