@cursor/july 0.1.92 → 0.1.93

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/README.md +117 -162
  2. package/dist/channels/deployments/deployments-channel.d.ts +7 -0
  3. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  4. package/dist/channels/deployments/deployments-channel.js +26 -2
  5. package/dist/channels/deployments/types.d.ts +8 -0
  6. package/dist/channels/deployments/types.d.ts.map +1 -1
  7. package/dist/channels/github/github-channel.d.ts +3 -0
  8. package/dist/channels/github/github-channel.d.ts.map +1 -1
  9. package/dist/channels/github/github-channel.js +28 -56
  10. package/dist/continuation.d.ts +1 -1
  11. package/dist/continuation.js +1 -1
  12. package/dist/docs/404.html +2 -2
  13. package/dist/docs/ab.html +8 -8
  14. package/dist/docs/ab.md +7 -13
  15. package/dist/docs/assets/{ab.md.CVzWxLoB.js → ab.md.DJo5r4R-.js} +4 -4
  16. package/dist/docs/assets/{ab.md.CVzWxLoB.lean.js → ab.md.DJo5r4R-.lean.js} +1 -1
  17. package/dist/docs/assets/{app.Bci6CM9E.js → app.CjWU-x0z.js} +1 -1
  18. package/dist/docs/assets/building-with-agents.md.DI4mEzlt.js +13 -0
  19. package/dist/docs/assets/{building-with-agents.md.DH8A_cHA.lean.js → building-with-agents.md.DI4mEzlt.lean.js} +1 -1
  20. package/dist/docs/assets/chunks/@localSearchIndexroot.ChpIC3Zy.js +1 -0
  21. package/dist/docs/assets/chunks/{VPLocalSearchBox.BCPT6xA-.js → VPLocalSearchBox.Cxy8ySFQ.js} +1 -1
  22. package/dist/docs/assets/chunks/{theme.BEA8BF3c.js → theme.Dvq1Bktu.js} +2 -2
  23. package/dist/docs/assets/concepts.md.F6AiPorA.js +1 -0
  24. package/dist/docs/assets/{concepts.md.CRfU3bVg.lean.js → concepts.md.F6AiPorA.lean.js} +1 -1
  25. package/dist/docs/assets/{deployment.md.DX_hc3ze.js → deployment.md.DoLFAzfm.js} +6 -6
  26. package/dist/docs/assets/{evals.md.a0SMN6r9.js → evals.md.lfJoEVc8.js} +6 -6
  27. package/dist/docs/assets/{evals.md.a0SMN6r9.lean.js → evals.md.lfJoEVc8.lean.js} +1 -1
  28. package/dist/docs/assets/{example-agents_approval-buddy.md.DNL83puR.js → example-agents_approval-buddy.md.DmezILPg.js} +1 -1
  29. package/dist/docs/assets/{example-agents_benny.md.C40vHRLc.js → example-agents_benny.md.B0kwY7D_.js} +2 -4
  30. package/dist/docs/assets/{example-agents_benny.md.C40vHRLc.lean.js → example-agents_benny.md.B0kwY7D_.lean.js} +1 -1
  31. package/dist/docs/assets/{example-agents_codebase-wiki.md.Dftj_tPp.js → example-agents_codebase-wiki.md.BBNw9Ekr.js} +3 -3
  32. package/dist/docs/assets/{example-agents_codebase-wiki.md.Dftj_tPp.lean.js → example-agents_codebase-wiki.md.BBNw9Ekr.lean.js} +1 -1
  33. package/dist/docs/assets/{example-agents_concierge.md.MrKpQndp.js → example-agents_concierge.md.BzB2b20R.js} +2 -3
  34. package/dist/docs/assets/example-agents_index.md.ChBp0AX6.js +2 -0
  35. package/dist/docs/assets/example-agents_index.md.ChBp0AX6.lean.js +1 -0
  36. package/dist/docs/assets/{example-agents_knowledge-base.md.DqKqHQ9u.js → example-agents_knowledge-base.md.CrA85ig-.js} +1 -1
  37. package/dist/docs/assets/{example-agents_security-reviewer.md.Bai6D0Ee.js → example-agents_security-reviewer.md.74pPpWYj.js} +1 -1
  38. package/dist/docs/assets/{example-agents_weather-agent.md.lVEAbWFf.js → example-agents_weather-agent.md.CaGpmw3Y.js} +2 -2
  39. package/dist/docs/assets/{guides_agent-to-agent.md.BCeVdJRJ.js → guides_agent-to-agent.md.B3JIaAqz.js} +1 -1
  40. package/dist/docs/assets/{guides_cloud-runtime.md.BSMLIBHr.js → guides_cloud-runtime.md.BnvjPiia.js} +2 -2
  41. package/dist/docs/assets/{guides_cloud-runtime.md.BSMLIBHr.lean.js → guides_cloud-runtime.md.BnvjPiia.lean.js} +1 -1
  42. package/dist/docs/assets/{guides_convert-automation.md.D06eIzea.js → guides_convert-automation.md.Bboisykk.js} +1 -1
  43. package/dist/docs/assets/{guides_github.md.Cdt1s2QC.js → guides_github.md.DqJhuaN1.js} +5 -5
  44. package/dist/docs/assets/{guides_github.md.Cdt1s2QC.lean.js → guides_github.md.DqJhuaN1.lean.js} +1 -1
  45. package/dist/docs/assets/{guides_mcp-oauth.md.Du0f7pGU.js → guides_mcp-oauth.md.CJvrXtkN.js} +2 -2
  46. package/dist/docs/assets/{guides_slack.md.DiUmk_Oi.js → guides_slack.md.mqeNKs84.js} +2 -2
  47. package/dist/docs/assets/{guides_webhooks.md.BpnIdO0i.js → guides_webhooks.md.DKdA43Qm.js} +2 -2
  48. package/dist/docs/assets/{hillclimbing.md.ywF3yDAd.js → hillclimbing.md.DhESf3OO.js} +1 -1
  49. package/dist/docs/assets/{index.md.BAaMXLFd.js → index.md.B-lVR4wT.js} +3 -3
  50. package/dist/docs/assets/{index.md.BAaMXLFd.lean.js → index.md.B-lVR4wT.lean.js} +1 -1
  51. package/dist/docs/assets/{quickstart.md.DsrarzEg.js → quickstart.md.BrmfrrIr.js} +1 -1
  52. package/dist/docs/assets/{reference_agent-config.md.Bqylgw50.js → reference_agent-config.md.Cp_x38Nl.js} +3 -3
  53. package/dist/docs/assets/{reference_channels.md.DQZjCnyh.js → reference_channels.md.Cd2f2iyV.js} +2 -2
  54. package/dist/docs/assets/{reference_channels.md.DQZjCnyh.lean.js → reference_channels.md.Cd2f2iyV.lean.js} +1 -1
  55. package/dist/docs/assets/{reference_cli.md.B7GkAJRC.js → reference_cli.md.D9KESDsD.js} +10 -11
  56. package/dist/docs/assets/{reference_cli.md.B7GkAJRC.lean.js → reference_cli.md.D9KESDsD.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_connections.md.DYidrb-j.js → reference_connections.md.DB6SsN6U.js} +3 -3
  58. package/dist/docs/assets/reference_hooks.md.BxN87gCw.js +14 -0
  59. package/dist/docs/assets/{reference_hooks.md.B9FSgdDe.lean.js → reference_hooks.md.BxN87gCw.lean.js} +1 -1
  60. package/dist/docs/assets/reference_http-api.md.C68BERYr.js +11 -0
  61. package/dist/docs/assets/reference_http-api.md.C68BERYr.lean.js +1 -0
  62. package/dist/docs/assets/{reference_instructions.md.DhNCOl7r.js → reference_instructions.md.CR7XSsGk.js} +3 -3
  63. package/dist/docs/assets/{reference_instructions.md.DhNCOl7r.lean.js → reference_instructions.md.CR7XSsGk.lean.js} +1 -1
  64. package/dist/docs/assets/reference_playground.md.DnX5nL-B.js +1 -0
  65. package/dist/docs/assets/reference_playground.md.DnX5nL-B.lean.js +1 -0
  66. package/dist/docs/assets/{reference_project-layout.md.CwkSbEWT.js → reference_project-layout.md.WN9nwJht.js} +2 -2
  67. package/dist/docs/assets/{reference_prompt.md.DZUMtLPD.js → reference_prompt.md.DnaD5dNK.js} +1 -1
  68. package/dist/docs/assets/{reference_schedules.md.DNipebiG.js → reference_schedules.md.DI_JrHgq.js} +1 -1
  69. package/dist/docs/assets/reference_sessions.md.D0mIh4KK.js +1 -0
  70. package/dist/docs/assets/{reference_sessions.md.tUFzz98S.lean.js → reference_sessions.md.D0mIh4KK.lean.js} +1 -1
  71. package/dist/docs/assets/{reference_skills.md.B5ZEuHfG.js → reference_skills.md.BFW9retM.js} +1 -1
  72. package/dist/docs/assets/{reference_tools.md.wpaJtHn6.js → reference_tools.md.DuKvkYWG.js} +4 -4
  73. package/dist/docs/assets/{reference_tools.md.wpaJtHn6.lean.js → reference_tools.md.DuKvkYWG.lean.js} +1 -1
  74. package/dist/docs/assets/scaffolding-agents.md.D7UUkWw0.js +1 -0
  75. package/dist/docs/assets/{scaffolding-agents.md.CRDDUtYJ.lean.js → scaffolding-agents.md.D7UUkWw0.lean.js} +1 -1
  76. package/dist/docs/assets/{storage.md.JbjlHWZ6.js → storage.md.BOHeqk2M.js} +5 -5
  77. package/dist/docs/assets/{storage.md.JbjlHWZ6.lean.js → storage.md.BOHeqk2M.lean.js} +1 -1
  78. package/dist/docs/assets/{templates_agentic-owners.md.DSJSIpWU.js → templates_agentic-owners.md.DqtPdm6f.js} +2 -2
  79. package/dist/docs/assets/{templates_pr-autofixer.md.1HAR3RXE.js → templates_pr-autofixer.md.R4K_qytS.js} +2 -2
  80. package/dist/docs/assets/{templates_pr-autofixer.md.1HAR3RXE.lean.js → templates_pr-autofixer.md.R4K_qytS.lean.js} +1 -1
  81. package/dist/docs/assets/troubleshooting.md.vCWwvqcJ.js +1 -0
  82. package/dist/docs/assets/{troubleshooting.md.DYECCZiJ.lean.js → troubleshooting.md.vCWwvqcJ.lean.js} +1 -1
  83. package/dist/docs/building-with-agents.html +7 -7
  84. package/dist/docs/building-with-agents.md +5 -11
  85. package/dist/docs/concepts.html +5 -8
  86. package/dist/docs/concepts.md +12 -17
  87. package/dist/docs/deployment.html +11 -11
  88. package/dist/docs/deployment.md +8 -10
  89. package/dist/docs/evals.html +10 -10
  90. package/dist/docs/evals.md +16 -37
  91. package/dist/docs/example-agents/approval-buddy.html +5 -5
  92. package/dist/docs/example-agents/approval-buddy.md +1 -1
  93. package/dist/docs/example-agents/benny.html +5 -7
  94. package/dist/docs/example-agents/benny.md +4 -13
  95. package/dist/docs/example-agents/bugbot.html +4 -4
  96. package/dist/docs/example-agents/codebase-wiki.html +6 -6
  97. package/dist/docs/example-agents/codebase-wiki.md +5 -8
  98. package/dist/docs/example-agents/codeowners-review.html +4 -4
  99. package/dist/docs/example-agents/concierge.html +7 -8
  100. package/dist/docs/example-agents/concierge.md +2 -3
  101. package/dist/docs/example-agents/index.html +6 -6
  102. package/dist/docs/example-agents/index.md +5 -8
  103. package/dist/docs/example-agents/knowledge-base.html +6 -6
  104. package/dist/docs/example-agents/knowledge-base.md +2 -2
  105. package/dist/docs/example-agents/oncall.html +4 -4
  106. package/dist/docs/example-agents/security-reviewer.html +7 -7
  107. package/dist/docs/example-agents/security-reviewer.md +5 -5
  108. package/dist/docs/example-agents/slack-agent.html +4 -4
  109. package/dist/docs/example-agents/weather-agent.html +7 -7
  110. package/dist/docs/example-agents/weather-agent.md +4 -3
  111. package/dist/docs/guides/agent-to-agent.html +5 -5
  112. package/dist/docs/guides/agent-to-agent.md +1 -1
  113. package/dist/docs/guides/cloud-runtime.html +6 -6
  114. package/dist/docs/guides/cloud-runtime.md +8 -25
  115. package/dist/docs/guides/convert-automation.html +6 -6
  116. package/dist/docs/guides/convert-automation.md +3 -3
  117. package/dist/docs/guides/github.html +9 -9
  118. package/dist/docs/guides/github.md +11 -23
  119. package/dist/docs/guides/human-in-the-loop.html +4 -4
  120. package/dist/docs/guides/mcp-oauth.html +6 -6
  121. package/dist/docs/guides/mcp-oauth.md +4 -4
  122. package/dist/docs/guides/opentelemetry.html +4 -4
  123. package/dist/docs/guides/slack.html +7 -7
  124. package/dist/docs/guides/slack.md +4 -4
  125. package/dist/docs/guides/webhooks.html +6 -6
  126. package/dist/docs/guides/webhooks.md +3 -3
  127. package/dist/docs/hashmap.json +1 -1
  128. package/dist/docs/hillclimbing.html +6 -6
  129. package/dist/docs/hillclimbing.md +1 -1
  130. package/dist/docs/index.html +6 -6
  131. package/dist/docs/index.md +2 -10
  132. package/dist/docs/llms-full.txt +300 -850
  133. package/dist/docs/llms.txt +2 -3
  134. package/dist/docs/quickstart.html +5 -5
  135. package/dist/docs/quickstart.md +1 -1
  136. package/dist/docs/reference/agent-config.html +8 -8
  137. package/dist/docs/reference/agent-config.md +10 -15
  138. package/dist/docs/reference/artifacts.html +4 -4
  139. package/dist/docs/reference/channels.html +6 -6
  140. package/dist/docs/reference/channels.md +20 -31
  141. package/dist/docs/reference/cli.html +14 -15
  142. package/dist/docs/reference/cli.md +27 -37
  143. package/dist/docs/reference/connections.html +8 -8
  144. package/dist/docs/reference/connections.md +9 -14
  145. package/dist/docs/reference/hooks.html +6 -6
  146. package/dist/docs/reference/hooks.md +10 -14
  147. package/dist/docs/reference/http-api.html +7 -7
  148. package/dist/docs/reference/http-api.md +17 -37
  149. package/dist/docs/reference/instructions.html +6 -6
  150. package/dist/docs/reference/instructions.md +1 -1
  151. package/dist/docs/reference/playground.html +5 -5
  152. package/dist/docs/reference/playground.md +14 -19
  153. package/dist/docs/reference/project-layout.html +7 -7
  154. package/dist/docs/reference/project-layout.md +2 -2
  155. package/dist/docs/reference/prompt.html +6 -6
  156. package/dist/docs/reference/prompt.md +1 -1
  157. package/dist/docs/reference/schedules.html +6 -6
  158. package/dist/docs/reference/schedules.md +1 -2
  159. package/dist/docs/reference/sessions.html +5 -12
  160. package/dist/docs/reference/sessions.md +8 -19
  161. package/dist/docs/reference/skills.html +6 -6
  162. package/dist/docs/reference/skills.md +3 -3
  163. package/dist/docs/reference/subagents.html +4 -4
  164. package/dist/docs/reference/tools.html +8 -8
  165. package/dist/docs/reference/tools.md +12 -17
  166. package/dist/docs/scaffolding-agents.html +5 -5
  167. package/dist/docs/scaffolding-agents.md +4 -5
  168. package/dist/docs/storage.html +9 -9
  169. package/dist/docs/storage.md +37 -80
  170. package/dist/docs/templates/agentic-owners.html +7 -7
  171. package/dist/docs/templates/agentic-owners.md +2 -2
  172. package/dist/docs/templates/demo.html +4 -4
  173. package/dist/docs/templates/pr-autofixer.html +6 -6
  174. package/dist/docs/templates/pr-autofixer.md +3 -6
  175. package/dist/docs/templates/security-reviewer.html +4 -4
  176. package/dist/docs/templates/triage.html +4 -4
  177. package/dist/docs/troubleshooting.html +5 -5
  178. package/dist/docs/troubleshooting.md +6 -6
  179. package/dist/internal/authored-alias-hooks.d.ts +14 -11
  180. package/dist/internal/authored-alias-hooks.d.ts.map +1 -1
  181. package/dist/internal/authored-alias-hooks.js +14 -11
  182. package/dist/internal/authored-loaders.d.ts +7 -6
  183. package/dist/internal/authored-loaders.d.ts.map +1 -1
  184. package/dist/internal/authored-loaders.js +14 -10
  185. package/dist/internal/cli-deploy.d.ts +1 -1
  186. package/dist/internal/cli-deploy.js +5 -5
  187. package/dist/internal/continuation-channel.d.ts +6 -3
  188. package/dist/internal/continuation-channel.d.ts.map +1 -1
  189. package/dist/internal/continuation-channel.js +44 -40
  190. package/dist/internal/continuation-identity.d.ts +17 -16
  191. package/dist/internal/continuation-identity.d.ts.map +1 -1
  192. package/dist/internal/continuation-identity.js +109 -36
  193. package/dist/internal/deploy-manifest.d.ts +2 -2
  194. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  195. package/dist/internal/deploy-manifest.js +4 -9
  196. package/dist/internal/discovery.d.ts.map +1 -1
  197. package/dist/internal/discovery.js +3 -0
  198. package/dist/internal/distribution.d.ts +4 -3
  199. package/dist/internal/distribution.d.ts.map +1 -1
  200. package/dist/internal/distribution.js +4 -3
  201. package/dist/internal/hosted-delivery-protocol.d.ts +38 -0
  202. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -0
  203. package/dist/internal/hosted-delivery-protocol.js +70 -0
  204. package/dist/internal/hosted-delivery.d.ts +35 -0
  205. package/dist/internal/hosted-delivery.d.ts.map +1 -0
  206. package/dist/internal/hosted-delivery.js +226 -0
  207. package/dist/internal/http-channel.d.ts.map +1 -1
  208. package/dist/internal/http-channel.js +1 -1
  209. package/dist/internal/review-comments.d.ts +186 -63
  210. package/dist/internal/review-comments.d.ts.map +1 -1
  211. package/dist/internal/review-comments.js +350 -168
  212. package/dist/internal/server.d.ts.map +1 -1
  213. package/dist/internal/server.js +21 -3
  214. package/dist/internal/session-engine.d.ts +5 -0
  215. package/dist/internal/session-engine.d.ts.map +1 -1
  216. package/dist/internal/session-engine.js +13 -3
  217. package/dist/internal/shallow-clone.d.ts +8 -2
  218. package/dist/internal/shallow-clone.d.ts.map +1 -1
  219. package/dist/internal/shallow-clone.js +17 -10
  220. package/dist/playground/assets/{index-DDvyC2z6.js → index-D9MFzhNE.js} +1 -1
  221. package/dist/playground/index.html +1 -1
  222. package/dist/types.d.ts +9 -17
  223. package/dist/types.d.ts.map +1 -1
  224. package/docs/README.md +2 -10
  225. package/docs/ab.md +7 -13
  226. package/docs/building-with-agents.md +5 -11
  227. package/docs/concepts.md +12 -17
  228. package/docs/deployment.md +8 -10
  229. package/docs/evals.md +16 -37
  230. package/docs/example-agents/approval-buddy.md +1 -1
  231. package/docs/example-agents/benny.md +4 -13
  232. package/docs/example-agents/codebase-wiki.md +5 -8
  233. package/docs/example-agents/concierge.md +2 -3
  234. package/docs/example-agents/index.md +6 -9
  235. package/docs/example-agents/knowledge-base.md +2 -2
  236. package/docs/example-agents/security-reviewer.md +5 -5
  237. package/docs/example-agents/weather-agent.md +4 -3
  238. package/docs/guides/agent-to-agent.md +1 -1
  239. package/docs/guides/cloud-runtime.md +8 -25
  240. package/docs/guides/convert-automation.md +3 -3
  241. package/docs/guides/github.md +11 -23
  242. package/docs/guides/mcp-oauth.md +4 -4
  243. package/docs/guides/slack.md +4 -4
  244. package/docs/guides/webhooks.md +3 -3
  245. package/docs/hillclimbing.md +1 -1
  246. package/docs/quickstart.md +1 -1
  247. package/docs/reference/agent-config.md +10 -15
  248. package/docs/reference/channels.md +20 -31
  249. package/docs/reference/cli.md +27 -37
  250. package/docs/reference/connections.md +9 -14
  251. package/docs/reference/hooks.md +10 -14
  252. package/docs/reference/http-api.md +18 -38
  253. package/docs/reference/instructions.md +1 -1
  254. package/docs/reference/playground.md +14 -19
  255. package/docs/reference/project-layout.md +2 -2
  256. package/docs/reference/prompt.md +1 -1
  257. package/docs/reference/schedules.md +1 -2
  258. package/docs/reference/sessions.md +8 -19
  259. package/docs/reference/skills.md +3 -3
  260. package/docs/reference/tools.md +12 -17
  261. package/docs/scaffolding-agents.md +4 -5
  262. package/docs/storage.md +37 -80
  263. package/docs/templates/agentic-owners.md +2 -2
  264. package/docs/templates/pr-autofixer.md +3 -6
  265. package/docs/troubleshooting.md +6 -6
  266. package/package.json +8 -1
  267. package/src/channels/deployments/deployments-channel.ts +32 -2
  268. package/src/channels/deployments/types.ts +8 -0
  269. package/src/channels/github/github-channel.ts +71 -21
  270. package/src/continuation.ts +1 -1
  271. package/src/internal/authored-alias-hooks.ts +14 -11
  272. package/src/internal/authored-loaders.ts +14 -10
  273. package/src/internal/cli-deploy.ts +5 -5
  274. package/src/internal/continuation-channel.ts +62 -45
  275. package/src/internal/continuation-identity.ts +123 -38
  276. package/src/internal/deploy-manifest.ts +5 -9
  277. package/src/internal/discovery.ts +3 -0
  278. package/src/internal/distribution.ts +4 -3
  279. package/src/internal/hosted-delivery-protocol.ts +114 -0
  280. package/src/internal/hosted-delivery.ts +327 -0
  281. package/src/internal/http-channel.ts +0 -2
  282. package/src/internal/review-comments.ts +542 -229
  283. package/src/internal/server.ts +29 -2
  284. package/src/internal/session-engine.ts +25 -1
  285. package/src/internal/shallow-clone.ts +30 -16
  286. package/src/types.ts +9 -17
  287. package/dist/docs/assets/building-with-agents.md.DH8A_cHA.js +0 -13
  288. package/dist/docs/assets/chunks/@localSearchIndexroot.Dv-Q0XtU.js +0 -1
  289. package/dist/docs/assets/concepts.md.CRfU3bVg.js +0 -4
  290. package/dist/docs/assets/example-agents_fsd.md.ZWHWWZPE.js +0 -15
  291. package/dist/docs/assets/example-agents_fsd.md.ZWHWWZPE.lean.js +0 -1
  292. package/dist/docs/assets/example-agents_index.md.QZ8mhr6n.js +0 -2
  293. package/dist/docs/assets/example-agents_index.md.QZ8mhr6n.lean.js +0 -1
  294. package/dist/docs/assets/reference_hooks.md.B9FSgdDe.js +0 -14
  295. package/dist/docs/assets/reference_http-api.md.CSHVobzG.js +0 -11
  296. package/dist/docs/assets/reference_http-api.md.CSHVobzG.lean.js +0 -1
  297. package/dist/docs/assets/reference_playground.md.Dfb92yQf.js +0 -1
  298. package/dist/docs/assets/reference_playground.md.Dfb92yQf.lean.js +0 -1
  299. package/dist/docs/assets/reference_sessions.md.tUFzz98S.js +0 -8
  300. package/dist/docs/assets/scaffolding-agents.md.CRDDUtYJ.js +0 -1
  301. package/dist/docs/assets/troubleshooting.md.DYECCZiJ.js +0 -1
  302. package/dist/docs/example-agents/fsd.html +0 -41
  303. package/dist/docs/example-agents/fsd.md +0 -329
  304. package/docs/example-agents/fsd.md +0 -334
  305. /package/dist/docs/assets/{deployment.md.DX_hc3ze.lean.js → deployment.md.DoLFAzfm.lean.js} +0 -0
  306. /package/dist/docs/assets/{example-agents_approval-buddy.md.DNL83puR.lean.js → example-agents_approval-buddy.md.DmezILPg.lean.js} +0 -0
  307. /package/dist/docs/assets/{example-agents_concierge.md.MrKpQndp.lean.js → example-agents_concierge.md.BzB2b20R.lean.js} +0 -0
  308. /package/dist/docs/assets/{example-agents_knowledge-base.md.DqKqHQ9u.lean.js → example-agents_knowledge-base.md.CrA85ig-.lean.js} +0 -0
  309. /package/dist/docs/assets/{example-agents_security-reviewer.md.Bai6D0Ee.lean.js → example-agents_security-reviewer.md.74pPpWYj.lean.js} +0 -0
  310. /package/dist/docs/assets/{example-agents_weather-agent.md.lVEAbWFf.lean.js → example-agents_weather-agent.md.CaGpmw3Y.lean.js} +0 -0
  311. /package/dist/docs/assets/{guides_agent-to-agent.md.BCeVdJRJ.lean.js → guides_agent-to-agent.md.B3JIaAqz.lean.js} +0 -0
  312. /package/dist/docs/assets/{guides_convert-automation.md.D06eIzea.lean.js → guides_convert-automation.md.Bboisykk.lean.js} +0 -0
  313. /package/dist/docs/assets/{guides_mcp-oauth.md.Du0f7pGU.lean.js → guides_mcp-oauth.md.CJvrXtkN.lean.js} +0 -0
  314. /package/dist/docs/assets/{guides_slack.md.DiUmk_Oi.lean.js → guides_slack.md.mqeNKs84.lean.js} +0 -0
  315. /package/dist/docs/assets/{guides_webhooks.md.BpnIdO0i.lean.js → guides_webhooks.md.DKdA43Qm.lean.js} +0 -0
  316. /package/dist/docs/assets/{hillclimbing.md.ywF3yDAd.lean.js → hillclimbing.md.DhESf3OO.lean.js} +0 -0
  317. /package/dist/docs/assets/{quickstart.md.DsrarzEg.lean.js → quickstart.md.BrmfrrIr.lean.js} +0 -0
  318. /package/dist/docs/assets/{reference_agent-config.md.Bqylgw50.lean.js → reference_agent-config.md.Cp_x38Nl.lean.js} +0 -0
  319. /package/dist/docs/assets/{reference_connections.md.DYidrb-j.lean.js → reference_connections.md.DB6SsN6U.lean.js} +0 -0
  320. /package/dist/docs/assets/{reference_project-layout.md.CwkSbEWT.lean.js → reference_project-layout.md.WN9nwJht.lean.js} +0 -0
  321. /package/dist/docs/assets/{reference_prompt.md.DZUMtLPD.lean.js → reference_prompt.md.DnaD5dNK.lean.js} +0 -0
  322. /package/dist/docs/assets/{reference_schedules.md.DNipebiG.lean.js → reference_schedules.md.DI_JrHgq.lean.js} +0 -0
  323. /package/dist/docs/assets/{reference_skills.md.B5ZEuHfG.lean.js → reference_skills.md.BFW9retM.lean.js} +0 -0
  324. /package/dist/docs/assets/{templates_agentic-owners.md.DSJSIpWU.lean.js → templates_agentic-owners.md.DqtPdm6f.lean.js} +0 -0
@@ -85,77 +85,111 @@ export type ScmReviewTarget = {
85
85
  prNumber: number;
86
86
  };
87
87
 
88
+ /** Optional structured logger for the read/resolve seams (`[asr]`-style). */
89
+ export type ReviewCommentLog = (message: string) => void;
90
+
88
91
  /**
89
- * One existing inline review comment, provider-neutral.
92
+ * One inline review-thread comment from a PR-wide listing, provider-neutral.
90
93
  *
91
- * Field names follow Bugbot's `ExistingInlineComment`
92
- * (`backend/server/src/bugbot/review/applyBugbotPRReview.ts`), which is the
93
- * shape its resolution judge consumes after normalizing each provider's
94
- * `getPRComments` output so the same review logic reads the same way here.
94
+ * This is the Bugbot listing shape (`minimizePreviousBugBotComments` /
95
+ * `getBugBotComments` normalize each provider's `getPRComments` output the
96
+ * same way): a re-review lists everything on the PR and filters on its own
97
+ * marker, rather than fetching ids it recorded elsewhere.
95
98
  */
96
- export type ExistingReviewComment = {
97
- id: number;
99
+ export type ListedReviewComment = {
100
+ /**
101
+ * Provider-native comment id, stringified for provider neutrality
102
+ * (GitHub REST database id in decimal; Origin `cmt_…`).
103
+ */
104
+ commentId: string;
105
+ /** GitHub REST numeric comment id, when the provider exposes one. */
106
+ numericCommentId?: number;
107
+ /**
108
+ * Id of the thread this comment opens, in the form the provider's thread
109
+ * resolver accepts (GitHub GraphQL thread node id; Origin `cth_…`).
110
+ */
111
+ threadId: string;
112
+ /** Whether the owning thread is already resolved. */
113
+ resolved: boolean;
98
114
  file: string;
99
115
  startLine: number;
100
116
  endLine: number;
101
117
  body: string;
118
+ /**
119
+ * Head SHA the comment was filed against (GitHub `originalCommit`; Origin
120
+ * the thread version's head SHA), when the provider exposes it. This is
121
+ * the commit a follow-up judge diffs forward from.
122
+ */
123
+ commitSha?: string;
124
+ authorLogin?: string;
125
+ /**
126
+ * Whether the SCM attributes the comment to a machine identity — a
127
+ * GitHub App bot (GraphQL author `__typename: "Bot"`) or an Origin
128
+ * app/service-account actor. Human accounts are `false`; `undefined`
129
+ * when the provider did not return an author (e.g. deleted account).
130
+ * Callers deciding whether a comment is their own must require `true`
131
+ * AND pin the specific identity (login / app id): body content is
132
+ * forgeable by any PR participant, and other bots/apps also comment.
133
+ */
134
+ authorIsBot?: boolean;
135
+ /**
136
+ * Origin app actor id (`app_…`) when the comment was authored by an app
137
+ * installation. Callers compare it against their own configured app id
138
+ * to decide the comment is theirs, not merely some app's.
139
+ */
140
+ authorAppId?: string;
102
141
  };
103
142
 
104
- export type GetReviewCommentsArgs = {
143
+ export type ListReviewCommentsArgs = {
105
144
  target: ScmReviewTarget;
106
- /** Comment ids to fetch; ids the SCM no longer has are omitted. */
107
- commentIds: readonly number[];
108
145
  signal?: AbortSignal;
109
146
  };
110
147
 
111
148
  /**
112
149
  * Read side of the review-comment seam, mirroring
113
150
  * {@link ReviewCommentPublisherImplementation}. Bugbot's equivalent is
114
- * `IGitProvider.getPRComments`.
115
- *
116
- * By id rather than by listing: callers only ever want comments they
117
- * recorded creating, and a busy PR's comment list runs to thousands, where
118
- * any page cap silently drops exactly the older comments a re-review needs.
151
+ * `IGitProvider.getPRComments`: list the PR's inline review threads (their
152
+ * opening comments) so a re-review can find its own previous comments.
119
153
  */
120
- export type ReviewCommentReaderImplementation = {
121
- getReviewComments: (
122
- args: GetReviewCommentsArgs
123
- ) => Promise<ExistingReviewComment[]>;
154
+ export type ReviewCommentListerImplementation = {
155
+ listReviewComments: (
156
+ args: ListReviewCommentsArgs
157
+ ) => Promise<ListedReviewComment[]>;
124
158
  };
125
159
 
126
160
  export type ResolveReviewThreadsArgs = {
127
161
  target: ScmReviewTarget;
128
- /** REST comment ids whose threads should be resolved. */
129
- commentIds: readonly number[];
162
+ /** Provider-native thread ids (from {@link ListedReviewComment.threadId}). */
163
+ threadIds: readonly string[];
130
164
  signal?: AbortSignal;
131
165
  };
132
166
 
133
167
  /**
134
- * Outcome of resolving review threads for a set of comment ids. Callers
135
- * treat this as best-effort actuation: a failed resolve must not fail the
136
- * review that already posted.
168
+ * Outcome of resolving a set of review threads. Callers treat this as
169
+ * best-effort actuation: a failed resolve must not fail the review that
170
+ * already posted.
137
171
  */
138
172
  export type ResolveReviewThreadsResult = {
139
173
  requested: number;
140
174
  resolved: number;
141
- alreadyResolved: number;
142
- notFound: number;
143
175
  failed: number;
144
176
  detail: string;
145
177
  };
146
178
 
147
179
  /**
148
180
  * Write side of thread resolution, the counterpart of
149
- * {@link ReviewCommentReaderImplementation}. Bugbot's equivalent is
181
+ * {@link ReviewCommentListerImplementation}. Bugbot's equivalent is
150
182
  * `IGitProvider.resolveReviewThreadForComment` / `resolveThreads`.
151
183
  *
152
184
  * GitHub only exposes resolve via GraphQL (`resolveReviewThread`). REST
153
185
  * has no equivalent. The mutation runs as whatever identity minted the
154
186
  * bound GitHub client — for `githubChannel({ cursorAccount })` that is
155
187
  * the Cursor installation, the same identity that posted the comments.
188
+ * Origin exposes resolve on the public REST API
189
+ * (`PATCH …/pulls/threads/{thread_id}`).
156
190
  */
157
191
  export type ReviewThreadResolverImplementation = {
158
- resolveReviewThreadsForComments: (
192
+ resolveReviewThreads: (
159
193
  args: ResolveReviewThreadsArgs
160
194
  ) => Promise<ResolveReviewThreadsResult>;
161
195
  };
@@ -169,23 +203,25 @@ export function resolveScmReviewTarget(
169
203
  }
170
204
 
171
205
  /**
172
- * Reader for a resolved target, mirroring Bugbot's `createPrGitProvider`
206
+ * Lister for a resolved target, mirroring Bugbot's `createPrGitProvider`
173
207
  * dispatch. Returns undefined when the host cannot serve that provider (no
174
- * GitHub client bound), so callers degrade instead of throwing.
208
+ * GitHub/Origin client bound), so callers degrade instead of throwing.
175
209
  */
176
- export function createReviewCommentReader(args: {
210
+ export function createReviewCommentLister(args: {
177
211
  target: ScmReviewTarget;
178
212
  github?: HostGitHubClient;
179
- }): ReviewCommentReaderImplementation | undefined {
213
+ origin?: HostOriginReviewClient;
214
+ log?: ReviewCommentLog;
215
+ }): ReviewCommentListerImplementation | undefined {
180
216
  switch (args.target.provider) {
181
217
  case "github":
182
218
  return args.github === undefined
183
219
  ? undefined
184
- : createHostGithubReviewCommentReader(args.github);
220
+ : createHostGithubReviewCommentLister(args.github, args.log);
185
221
  case "origin":
186
- // Public Origin REST lists discussion comments, not diff-anchored
187
- // review threads. Stay undefined until a reader can fetch by id.
188
- return undefined;
222
+ return args.origin === undefined
223
+ ? undefined
224
+ : createHostOriginReviewCommentLister(args.origin, args.log);
189
225
  }
190
226
  }
191
227
 
@@ -196,33 +232,39 @@ export function createReviewCommentReader(args: {
196
232
  export function createReviewThreadResolver(args: {
197
233
  target: ScmReviewTarget;
198
234
  github?: HostGitHubClient;
235
+ origin?: HostOriginReviewClient;
236
+ log?: ReviewCommentLog;
199
237
  }): ReviewThreadResolverImplementation | undefined {
200
238
  switch (args.target.provider) {
201
239
  case "github":
202
240
  return args.github === undefined
203
241
  ? undefined
204
- : createHostGithubReviewThreadResolver(args.github);
242
+ : createHostGithubReviewThreadResolver(args.github, args.log);
205
243
  case "origin":
206
- return undefined;
244
+ return args.origin === undefined
245
+ ? undefined
246
+ : createHostOriginReviewThreadResolver(args.origin, args.log);
207
247
  }
208
248
  }
209
249
 
210
250
  /**
211
- * Resolve the review threads that own `commentIds` on `prUrl`.
251
+ * Resolve `threadIds` on `prUrl`'s PR.
212
252
  *
213
253
  * No-ops (never throws) when the PR is on an SCM this host cannot write,
214
- * no client is bound, or `commentIds` is empty. Same degrade-don't-fail
215
- * posture as {@link createReviewCommentReader}.
254
+ * no client is bound, or `threadIds` is empty. Same degrade-don't-fail
255
+ * posture as {@link createReviewCommentLister}.
216
256
  */
217
- export async function resolveReviewThreadsForComments(args: {
257
+ export async function resolveReviewThreads(args: {
218
258
  prUrl: string;
219
- commentIds: readonly number[];
259
+ threadIds: readonly string[];
220
260
  github?: HostGitHubClient;
261
+ origin?: HostOriginReviewClient;
221
262
  resolver?: ReviewThreadResolverImplementation;
222
263
  signal?: AbortSignal;
264
+ log?: ReviewCommentLog;
223
265
  }): Promise<ResolveReviewThreadsResult> {
224
- if (args.commentIds.length === 0) {
225
- return skippedResolve("no comments to resolve");
266
+ if (args.threadIds.length === 0) {
267
+ return skippedResolve("no threads to resolve");
226
268
  }
227
269
  const target = resolveScmReviewTarget(args.prUrl);
228
270
  if (target === undefined) {
@@ -233,17 +275,17 @@ export async function resolveReviewThreadsForComments(args: {
233
275
  createReviewThreadResolver({
234
276
  target,
235
277
  github: args.github,
278
+ origin: args.origin,
279
+ log: args.log,
236
280
  });
237
281
  if (resolver === undefined) {
238
282
  return skippedResolve(
239
- target.provider === "github"
240
- ? "no GitHub client bound to resolve threads"
241
- : `no thread resolver for ${target.provider}`
283
+ `no ${target.provider} client bound to resolve threads`
242
284
  );
243
285
  }
244
- return resolver.resolveReviewThreadsForComments({
286
+ return resolver.resolveReviewThreads({
245
287
  target,
246
- commentIds: args.commentIds,
288
+ threadIds: args.threadIds,
247
289
  signal: args.signal,
248
290
  });
249
291
  }
@@ -251,52 +293,30 @@ export async function resolveReviewThreadsForComments(args: {
251
293
  /** Cap on pages pulled per PR; GitHub serves 100 comments per page. */
252
294
  const REVIEW_COMMENTS_MAX_PAGES = 5;
253
295
  const REVIEW_COMMENTS_PAGE_SIZE = 100;
254
- /** Cap on review-thread pages when mapping comment ids to GraphQL thread ids. */
296
+ /** Cap on review-thread pages when listing a PR's inline threads. */
255
297
  const REVIEW_THREADS_MAX_PAGES = 20;
256
298
  const REVIEW_THREADS_PAGE_SIZE = 100;
257
-
258
- /**
259
- * Reads inline review comments through the host GitHub client (Cursor
260
- * account lease, App installation token, or PAT).
261
- */
262
- export function createHostGithubReviewCommentReader(
263
- github: HostGitHubClient
264
- ): ReviewCommentReaderImplementation {
265
- return {
266
- async getReviewComments({ target, commentIds, signal }) {
267
- const [owner, repo] = target.repo.split("/", 2);
268
- if (owner === undefined || repo === undefined || repo === "") {
269
- return [];
270
- }
271
- const octokit = await github.getOctokit();
272
- const comments: ExistingReviewComment[] = [];
273
- for (const commentId of commentIds) {
274
- signal?.throwIfAborted();
275
- try {
276
- const response = await octokit.rest.pulls.getReviewComment({
277
- owner,
278
- repo,
279
- comment_id: commentId,
280
- });
281
- const normalized = normalizeGithubReviewComment(response.data);
282
- if (normalized !== undefined) {
283
- comments.push(normalized);
284
- }
285
- } catch {
286
- // Deleted or otherwise unreadable: that finding simply has no
287
- // prior text to judge, which is the same as it not being there.
288
- }
289
- }
290
- return comments;
291
- },
292
- };
293
- }
299
+ /** Cap on Origin comment pages pulled per PR (100 comments per page). */
300
+ const ORIGIN_COMMENTS_MAX_PAGES = 20;
301
+ const ORIGIN_COMMENTS_PAGE_SIZE = 100;
302
+
303
+ type GithubThreadCommentNode = {
304
+ databaseId?: number | null;
305
+ body?: string | null;
306
+ author?: { login?: string | null; __typename?: string | null } | null;
307
+ originalCommit?: { oid?: string | null } | null;
308
+ };
294
309
 
295
310
  type GithubReviewThreadNode = {
296
311
  id?: string;
297
312
  isResolved?: boolean;
313
+ path?: string | null;
314
+ line?: number | null;
315
+ originalLine?: number | null;
316
+ startLine?: number | null;
317
+ originalStartLine?: number | null;
298
318
  comments?: {
299
- nodes?: Array<{ databaseId?: number | null } | null>;
319
+ nodes?: Array<GithubThreadCommentNode | null>;
300
320
  };
301
321
  };
302
322
 
@@ -318,112 +338,185 @@ type GithubResolveThreadMutation = {
318
338
  };
319
339
 
320
340
  /**
321
- * Resolves inline review threads through the host GitHub client (Cursor
322
- * account lease, App installation token, or PAT). GitHub GraphQL has no
323
- * comment→thread field, so this pages the PR's review threads and matches
324
- * each REST comment id by `databaseId` — the same approach as Bugbot's
325
- * `resolveReviewThreadForComment`.
341
+ * Repo-scope header for GraphQL calls through the Cursor github-proxy.
342
+ *
343
+ * The proxy authorizes each call from `x-cursor-github-repos`, which the
344
+ * channel Octokit derives from REST URLs (`/repos/{owner}/{repo}/…`).
345
+ * GraphQL posts to `/graphql` — no repo in the URL — so without this
346
+ * explicit header every GraphQL call is rejected in proxy mode. Harmless
347
+ * against api.github.com, which ignores unknown headers.
326
348
  */
327
- export function createHostGithubReviewThreadResolver(
328
- github: HostGitHubClient
329
- ): ReviewThreadResolverImplementation {
349
+ function githubGraphqlScopeHeaders(repo: string): Record<string, string> {
350
+ return { "x-cursor-github-repos": repo.toLowerCase() };
351
+ }
352
+
353
+ /**
354
+ * GitHub review thread → {@link ListedReviewComment} (its opening comment).
355
+ *
356
+ * Prefers the `original*` line fields — the position as of the review the
357
+ * thread belongs to. `line` drifts as the PR gains commits, so a caller
358
+ * correlating a comment with the review that produced it needs the original
359
+ * position, the same fields Bugbot carries on `PullRequestCommentOutput`.
360
+ */
361
+ export function listedCommentFromGithubThread(
362
+ thread: GithubReviewThreadNode
363
+ ): ListedReviewComment | undefined {
364
+ const opener = thread.comments?.nodes?.[0];
365
+ if (
366
+ thread.id === undefined ||
367
+ thread.id === "" ||
368
+ opener === null ||
369
+ opener === undefined ||
370
+ opener.databaseId === undefined ||
371
+ opener.databaseId === null ||
372
+ typeof opener.body !== "string" ||
373
+ typeof thread.path !== "string" ||
374
+ thread.path === ""
375
+ ) {
376
+ return undefined;
377
+ }
378
+ // Take both lines from the same position set. Mixing them (original end
379
+ // line with the drifted current start line) would describe a range that
380
+ // never existed in either revision.
381
+ const original = thread.originalLine ?? undefined;
382
+ const endLine = original ?? thread.line ?? undefined;
383
+ if (endLine === undefined) {
384
+ return undefined;
385
+ }
386
+ const startLine =
387
+ (original === undefined ? thread.startLine : thread.originalStartLine) ??
388
+ endLine;
389
+ const commitSha = opener.originalCommit?.oid ?? undefined;
390
+ const authorLogin = opener.author?.login ?? undefined;
391
+ const authorType = opener.author?.__typename ?? undefined;
330
392
  return {
331
- async resolveReviewThreadsForComments({ target, commentIds, signal }) {
332
- const uniqueIds = [...new Set(commentIds.filter((id) => id > 0))];
333
- if (uniqueIds.length === 0) {
334
- return skippedResolve("no comments to resolve");
335
- }
393
+ commentId: String(opener.databaseId),
394
+ numericCommentId: opener.databaseId,
395
+ threadId: thread.id,
396
+ resolved: thread.isResolved === true,
397
+ file: thread.path,
398
+ startLine,
399
+ endLine,
400
+ body: opener.body,
401
+ ...(commitSha === undefined ? {} : { commitSha }),
402
+ ...(authorLogin === undefined ? {} : { authorLogin }),
403
+ ...(authorType === undefined ? {} : { authorIsBot: authorType === "Bot" }),
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Lists the PR's inline review threads (their opening comments) through the
409
+ * host GitHub client (Cursor account lease, App installation token, or
410
+ * PAT). One paged GraphQL query yields, per thread: the resolvable thread
411
+ * node id, its resolution state, the opener's body/position, and the commit
412
+ * it was filed against — everything a Bugbot-style re-review needs.
413
+ */
414
+ export function createHostGithubReviewCommentLister(
415
+ github: HostGitHubClient,
416
+ log?: ReviewCommentLog
417
+ ): ReviewCommentListerImplementation {
418
+ return {
419
+ async listReviewComments({ target, signal }) {
336
420
  const [owner, repo] = target.repo.split("/", 2);
337
421
  if (owner === undefined || repo === undefined || repo === "") {
338
- return skippedResolve("unparseable owner/repo from PR URL");
422
+ log?.(`github list: unparseable owner/repo "${target.repo}"`);
423
+ return [];
339
424
  }
340
-
341
- try {
342
- const octokit = await github.getOctokit();
343
- const wanted = new Set(uniqueIds);
344
- const matched = new Map<
345
- number,
346
- { threadId: string; isResolved: boolean }
347
- >();
348
-
349
- let cursor: string | null = null;
350
- for (let page = 0; page < REVIEW_THREADS_MAX_PAGES; page++) {
351
- signal?.throwIfAborted();
352
- const response = (await octokit.graphql(REVIEW_THREADS_FOR_PR_QUERY, {
425
+ const octokit = await github.getOctokit();
426
+ const comments: ListedReviewComment[] = [];
427
+ let threadCount = 0;
428
+ let cursor: string | null = null;
429
+ for (let page = 0; page < REVIEW_THREADS_MAX_PAGES; page++) {
430
+ signal?.throwIfAborted();
431
+ const response = (await octokit.graphql(
432
+ REVIEW_THREAD_COMMENTS_FOR_PR_QUERY,
433
+ {
353
434
  owner,
354
435
  repo,
355
436
  number: target.prNumber,
356
437
  cursor,
357
- })) as GithubReviewThreadsQuery;
358
- const reviewThreads = response.repository?.pullRequest?.reviewThreads;
359
- for (const thread of reviewThreads?.nodes ?? []) {
360
- if (thread?.id === undefined || thread.id === "") {
361
- continue;
362
- }
363
- for (const comment of thread.comments?.nodes ?? []) {
364
- const databaseId = comment?.databaseId;
365
- if (
366
- databaseId !== undefined &&
367
- databaseId !== null &&
368
- wanted.has(databaseId) &&
369
- !matched.has(databaseId)
370
- ) {
371
- matched.set(databaseId, {
372
- threadId: thread.id,
373
- isResolved: thread.isResolved === true,
374
- });
375
- }
376
- }
438
+ headers: githubGraphqlScopeHeaders(target.repo),
377
439
  }
378
- if (matched.size === wanted.size) {
379
- break;
440
+ )) as GithubReviewThreadsQuery;
441
+ const reviewThreads = response.repository?.pullRequest?.reviewThreads;
442
+ for (const thread of reviewThreads?.nodes ?? []) {
443
+ if (thread === null || thread === undefined) {
444
+ continue;
380
445
  }
381
- const pageInfo = reviewThreads?.pageInfo;
382
- if (pageInfo?.hasNextPage !== true || !pageInfo.endCursor) {
383
- break;
446
+ threadCount += 1;
447
+ const listed = listedCommentFromGithubThread(thread);
448
+ if (listed !== undefined) {
449
+ comments.push(listed);
384
450
  }
385
- cursor = pageInfo.endCursor;
386
451
  }
452
+ const pageInfo = reviewThreads?.pageInfo;
453
+ if (pageInfo?.hasNextPage !== true || !pageInfo.endCursor) {
454
+ break;
455
+ }
456
+ cursor = pageInfo.endCursor;
457
+ }
458
+ log?.(
459
+ `github list: ${target.repo}#${target.prNumber} has ${threadCount} review thread(s), ${comments.length} with a normalizable opening comment`
460
+ );
461
+ return comments;
462
+ },
463
+ };
464
+ }
387
465
 
388
- const toResolve = [
389
- ...new Map(
390
- [...matched.values()]
391
- .filter((thread) => !thread.isResolved)
392
- .map((thread) => [thread.threadId, thread.threadId])
393
- ).values(),
394
- ];
395
- let resolved = 0;
396
- let failed = 0;
397
- for (const threadId of toResolve) {
466
+ /**
467
+ * Resolves inline review threads by GraphQL thread node id through the host
468
+ * GitHub client (Cursor account lease, App installation token, or PAT)
469
+ * the id the lister already returned, so no comment→thread mapping pass is
470
+ * needed. Same mutation as Bugbot's `resolveReviewThreadForComment`.
471
+ */
472
+ export function createHostGithubReviewThreadResolver(
473
+ github: HostGitHubClient,
474
+ log?: ReviewCommentLog
475
+ ): ReviewThreadResolverImplementation {
476
+ return {
477
+ async resolveReviewThreads({ target, threadIds, signal }) {
478
+ const uniqueIds = [...new Set(threadIds.filter((id) => id !== ""))];
479
+ if (uniqueIds.length === 0) {
480
+ return skippedResolve("no threads to resolve");
481
+ }
482
+ let resolved = 0;
483
+ let failed = 0;
484
+ try {
485
+ const octokit = await github.getOctokit();
486
+ for (const threadId of uniqueIds) {
398
487
  signal?.throwIfAborted();
399
488
  try {
400
489
  const mutation = (await octokit.graphql(
401
490
  RESOLVE_REVIEW_THREAD_MUTATION,
402
- { threadId }
491
+ {
492
+ threadId,
493
+ headers: githubGraphqlScopeHeaders(target.repo),
494
+ }
403
495
  )) as GithubResolveThreadMutation;
404
496
  if (mutation.resolveReviewThread?.thread?.isResolved === true) {
405
497
  resolved += 1;
498
+ log?.(`github resolve: thread ${threadId} resolved`);
406
499
  } else {
407
500
  failed += 1;
501
+ log?.(
502
+ `github resolve: thread ${threadId} mutation did not mark the thread resolved`
503
+ );
504
+ }
505
+ } catch (error) {
506
+ if (signal?.aborted) {
507
+ throw error;
408
508
  }
409
- } catch {
410
509
  failed += 1;
510
+ log?.(
511
+ `github resolve: thread ${threadId} failed: ${error instanceof Error ? error.message : String(error)}`
512
+ );
411
513
  }
412
514
  }
413
-
414
- const alreadyResolved = new Set(
415
- [...matched.values()]
416
- .filter((thread) => thread.isResolved)
417
- .map((thread) => thread.threadId)
418
- ).size;
419
- const notFound = wanted.size - matched.size;
420
515
  return {
421
516
  requested: uniqueIds.length,
422
517
  resolved,
423
- alreadyResolved,
424
- notFound,
425
518
  failed,
426
- detail: `resolved ${resolved} of ${toResolve.length} open thread(s) for ${uniqueIds.length} comment(s) via host GitHub (${alreadyResolved} already resolved, ${notFound} not found, ${failed} failed)`,
519
+ detail: `resolved ${resolved} of ${uniqueIds.length} thread(s) on ${target.repo}#${target.prNumber} via host GitHub (${failed} failed)`,
427
520
  };
428
521
  } catch (error) {
429
522
  if (signal?.aborted) {
@@ -431,10 +524,8 @@ export function createHostGithubReviewThreadResolver(
431
524
  }
432
525
  return {
433
526
  requested: uniqueIds.length,
434
- resolved: 0,
435
- alreadyResolved: 0,
436
- notFound: 0,
437
- failed: uniqueIds.length,
527
+ resolved,
528
+ failed: uniqueIds.length - resolved,
438
529
  detail: `host GitHub thread resolve failed: ${error instanceof Error ? error.message : String(error)}`,
439
530
  };
440
531
  }
@@ -442,8 +533,8 @@ export function createHostGithubReviewThreadResolver(
442
533
  };
443
534
  }
444
535
 
445
- const REVIEW_THREADS_FOR_PR_QUERY = `
446
- query ReviewThreadsForPr(
536
+ const REVIEW_THREAD_COMMENTS_FOR_PR_QUERY = `
537
+ query ReviewThreadCommentsForPr(
447
538
  $owner: String!
448
539
  $repo: String!
449
540
  $number: Int!
@@ -455,8 +546,18 @@ query ReviewThreadsForPr(
455
546
  nodes {
456
547
  id
457
548
  isResolved
458
- comments(first: ${REVIEW_THREADS_PAGE_SIZE}) {
459
- nodes { databaseId }
549
+ path
550
+ line
551
+ originalLine
552
+ startLine
553
+ originalStartLine
554
+ comments(first: 1) {
555
+ nodes {
556
+ databaseId
557
+ body
558
+ author { login __typename }
559
+ originalCommit { oid }
560
+ }
460
561
  }
461
562
  }
462
563
  pageInfo { hasNextPage endCursor }
@@ -474,46 +575,241 @@ mutation ResolveReviewThread($threadId: ID!) {
474
575
  }
475
576
  `;
476
577
 
477
- type GithubReviewCommentLike = {
478
- id: number;
479
- path: string;
480
- body: string;
481
- line?: number | null;
482
- start_line?: number | null;
483
- original_line?: number | null;
484
- original_start_line?: number | null;
578
+ const GITHUB_VIEWER_LOGIN_QUERY = `
579
+ query ViewerLogin {
580
+ viewer { login }
581
+ }
582
+ `;
583
+
584
+ /**
585
+ * The login the bound GitHub client itself acts as — the identity its posts
586
+ * appear under. Works for installation tokens too (GraphQL `viewer` returns
587
+ * the app's bot, e.g. `cursor[bot]`), so a reader can trust comments whose
588
+ * author is provably the same identity it posts with, across every
589
+ * environment's app (prod/staging/cloud-dev/custom) without hardcoding.
590
+ * `repo` scopes the call through the Cursor github-proxy (the minted token
591
+ * — and so the viewer — is per-repo there). Best-effort: `undefined` when
592
+ * the lookup fails.
593
+ */
594
+ export async function fetchGithubSelfLogin(
595
+ github: HostGitHubClient,
596
+ args: { repo: string; signal?: AbortSignal; log?: ReviewCommentLog }
597
+ ): Promise<string | undefined> {
598
+ try {
599
+ const octokit = await github.getOctokit();
600
+ args.signal?.throwIfAborted();
601
+ const response = (await octokit.graphql(GITHUB_VIEWER_LOGIN_QUERY, {
602
+ headers: githubGraphqlScopeHeaders(args.repo),
603
+ })) as {
604
+ viewer?: { login?: string | null } | null;
605
+ };
606
+ const login = response.viewer?.login;
607
+ return typeof login === "string" && login !== "" ? login : undefined;
608
+ } catch (error) {
609
+ if (args.signal?.aborted) {
610
+ throw error;
611
+ }
612
+ args.log?.(
613
+ `github viewer lookup failed: ${error instanceof Error ? error.message : String(error)}`
614
+ );
615
+ return undefined;
616
+ }
617
+ }
618
+
619
+ type OriginThreadJson = {
620
+ id?: string;
621
+ path?: string;
622
+ startLine?: number;
623
+ endLine?: number;
624
+ resolvedAt?: string;
625
+ version?: { headSha?: string };
485
626
  };
486
627
 
487
628
  /**
488
- * GitHub review comment {@link ExistingReviewComment}.
489
- *
490
- * Prefers the `original_*` fields — the path/lines/commit as of the review
491
- * the comment belongs to. `line` drifts as the PR gains commits, so a
492
- * caller correlating a comment with the review that produced it needs the
493
- * original position, the same fields Bugbot carries on
494
- * `PullRequestCommentOutput`.
629
+ * Public `OriginActor` proto3 JSON: a `user` / `app` / `serviceAccount`
630
+ * oneof (see `origin_actor.proto`).
495
631
  */
496
- export function normalizeGithubReviewComment(
497
- comment: GithubReviewCommentLike
498
- ): ExistingReviewComment | undefined {
499
- // Take both lines from the same position set. Mixing them (original end
500
- // line with the drifted current start line) would describe a range that
501
- // never existed in either revision.
502
- const original = comment.original_line ?? undefined;
503
- const endLine = original ?? comment.line ?? undefined;
504
- if (endLine === undefined || endLine === null) {
632
+ type OriginActorJson = {
633
+ user?: { id?: string; email?: string };
634
+ app?: { id?: string; slug?: string };
635
+ serviceAccount?: { id?: string };
636
+ };
637
+
638
+ type OriginCommentJson = {
639
+ id?: string;
640
+ body?: string;
641
+ thread?: OriginThreadJson;
642
+ author?: OriginActorJson;
643
+ };
644
+
645
+ type OriginListCommentsJson = {
646
+ comments?: Array<OriginCommentJson | null>;
647
+ nextPageToken?: string;
648
+ };
649
+
650
+ /**
651
+ * Origin public comment → {@link ListedReviewComment}. Only line-anchored
652
+ * threads qualify (general-discussion and file-level threads carry no
653
+ * position a review finding can be pinned to).
654
+ */
655
+ export function listedCommentFromOriginComment(
656
+ comment: OriginCommentJson
657
+ ): ListedReviewComment | undefined {
658
+ const thread = comment.thread;
659
+ if (
660
+ typeof comment.id !== "string" ||
661
+ comment.id === "" ||
662
+ typeof comment.body !== "string" ||
663
+ thread === undefined ||
664
+ typeof thread.id !== "string" ||
665
+ thread.id === "" ||
666
+ typeof thread.path !== "string" ||
667
+ thread.path === "" ||
668
+ typeof thread.startLine !== "number" ||
669
+ thread.startLine <= 0
670
+ ) {
505
671
  return undefined;
506
672
  }
507
- const startLine =
508
- (original === undefined
509
- ? comment.start_line
510
- : comment.original_start_line) ?? endLine;
673
+ const startLine = thread.startLine;
674
+ const endLine =
675
+ typeof thread.endLine === "number" && thread.endLine >= startLine
676
+ ? thread.endLine
677
+ : startLine;
678
+ const commitSha = thread.version?.headSha;
679
+ const author = comment.author;
680
+ const authorLogin = author?.app?.slug ?? author?.user?.email;
681
+ // App and service-account actors are machine identities; only a `user`
682
+ // actor is a human PR participant.
683
+ const authorIsBot =
684
+ author === undefined
685
+ ? undefined
686
+ : author.app !== undefined || author.serviceAccount !== undefined;
687
+ const authorAppId = author?.app?.id;
511
688
  return {
512
- id: comment.id,
513
- file: comment.path,
689
+ commentId: comment.id,
690
+ threadId: thread.id,
691
+ resolved: typeof thread.resolvedAt === "string" && thread.resolvedAt !== "",
692
+ file: thread.path,
514
693
  startLine,
515
694
  endLine,
516
695
  body: comment.body,
696
+ ...(typeof commitSha === "string" && commitSha !== "" ? { commitSha } : {}),
697
+ ...(typeof authorLogin === "string" && authorLogin !== ""
698
+ ? { authorLogin }
699
+ : {}),
700
+ ...(authorIsBot === undefined ? {} : { authorIsBot }),
701
+ ...(typeof authorAppId === "string" && authorAppId !== ""
702
+ ? { authorAppId }
703
+ : {}),
704
+ };
705
+ }
706
+
707
+ /**
708
+ * Lists the PR's inline review threads through the Origin public REST API
709
+ * (`GET …/pulls/{n}/comments`, paged). The flattened comment list is
710
+ * ordered by creation time, so the first comment seen per thread is the
711
+ * thread opener; replies are dropped.
712
+ */
713
+ export function createHostOriginReviewCommentLister(
714
+ origin: HostOriginReviewClient,
715
+ log?: ReviewCommentLog
716
+ ): ReviewCommentListerImplementation {
717
+ return {
718
+ async listReviewComments({ target, signal }) {
719
+ const [owner, repo] = target.repo.split("/", 2);
720
+ if (owner === undefined || repo === undefined || repo === "") {
721
+ log?.(`origin list: unparseable owner/repo "${target.repo}"`);
722
+ return [];
723
+ }
724
+ const basePath = `/v1/origin/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${target.prNumber}/comments`;
725
+ const comments: ListedReviewComment[] = [];
726
+ const seenThreads = new Set<string>();
727
+ let totalComments = 0;
728
+ let pageToken = "";
729
+ for (let page = 0; page < ORIGIN_COMMENTS_MAX_PAGES; page++) {
730
+ signal?.throwIfAborted();
731
+ const query =
732
+ pageToken === ""
733
+ ? `?pageSize=${ORIGIN_COMMENTS_PAGE_SIZE}`
734
+ : `?pageSize=${ORIGIN_COMMENTS_PAGE_SIZE}&pageToken=${encodeURIComponent(pageToken)}`;
735
+ const response = (await origin.request({
736
+ method: "GET",
737
+ path: `${basePath}${query}`,
738
+ })) as OriginListCommentsJson | null | undefined;
739
+ for (const comment of response?.comments ?? []) {
740
+ if (comment === null || comment === undefined) {
741
+ continue;
742
+ }
743
+ totalComments += 1;
744
+ const listed = listedCommentFromOriginComment(comment);
745
+ if (listed === undefined || seenThreads.has(listed.threadId)) {
746
+ // Not line-anchored, or a reply on a thread whose opener was
747
+ // already taken (the list is oldest-first).
748
+ continue;
749
+ }
750
+ seenThreads.add(listed.threadId);
751
+ comments.push(listed);
752
+ }
753
+ pageToken = response?.nextPageToken ?? "";
754
+ if (pageToken === "") {
755
+ break;
756
+ }
757
+ }
758
+ log?.(
759
+ `origin list: ${target.repo}#${target.prNumber} has ${totalComments} comment(s), ${comments.length} line-anchored thread opener(s)`
760
+ );
761
+ return comments;
762
+ },
763
+ };
764
+ }
765
+
766
+ /**
767
+ * Resolves Origin comment threads through the public REST API
768
+ * (`PATCH …/pulls/threads/{thread_id}` with `resolved: true`).
769
+ */
770
+ export function createHostOriginReviewThreadResolver(
771
+ origin: HostOriginReviewClient,
772
+ log?: ReviewCommentLog
773
+ ): ReviewThreadResolverImplementation {
774
+ return {
775
+ async resolveReviewThreads({ target, threadIds, signal }) {
776
+ const uniqueIds = [...new Set(threadIds.filter((id) => id !== ""))];
777
+ if (uniqueIds.length === 0) {
778
+ return skippedResolve("no threads to resolve");
779
+ }
780
+ const [owner, repo] = target.repo.split("/", 2);
781
+ if (owner === undefined || repo === undefined || repo === "") {
782
+ return skippedResolve("unparseable owner/repo from PR URL");
783
+ }
784
+ let resolved = 0;
785
+ let failed = 0;
786
+ for (const threadId of uniqueIds) {
787
+ signal?.throwIfAborted();
788
+ try {
789
+ await origin.request({
790
+ method: "PATCH",
791
+ path: `/v1/origin/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/threads/${encodeURIComponent(threadId)}`,
792
+ body: { resolved: true },
793
+ });
794
+ resolved += 1;
795
+ log?.(`origin resolve: thread ${threadId} resolved`);
796
+ } catch (error) {
797
+ if (signal?.aborted) {
798
+ throw error;
799
+ }
800
+ failed += 1;
801
+ log?.(
802
+ `origin resolve: thread ${threadId} failed: ${error instanceof Error ? error.message : String(error)}`
803
+ );
804
+ }
805
+ }
806
+ return {
807
+ requested: uniqueIds.length,
808
+ resolved,
809
+ failed,
810
+ detail: `resolved ${resolved} of ${uniqueIds.length} thread(s) on ${target.repo}#${target.prNumber} via host Origin (${failed} failed)`,
811
+ };
812
+ },
517
813
  };
518
814
  }
519
815
 
@@ -606,9 +902,12 @@ export function createReviewCommentPublisher(args: {
606
902
  /**
607
903
  * Posts a COMMENT-verdict Origin review through the public REST API.
608
904
  *
609
- * Public Origin REST has no diff-anchored review comments (those are an
610
- * internal CreateCommentThread RPC). Inline comments in the request are
611
- * folded into the review body so findings still land on the PR.
905
+ * Inline comments post as diff-anchored threads via the review's
906
+ * `comments` field (`ReviewCommentInput` with an `InlineCommentAnchor`).
907
+ * Origin rejects the whole request with INVALID_ARGUMENT when any anchor
908
+ * is off the reviewed version's diff — the caller's body-only retry (the
909
+ * same fallback GitHub's 422 takes) then folds the findings into the
910
+ * review body.
612
911
  */
613
912
  export function createHostOriginReviewPublisher(
614
913
  origin: HostOriginReviewClient
@@ -879,15 +1178,20 @@ async function postReviewViaHostOrigin(
879
1178
  path: `/v1/origin/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${target.prNumber}/reviews`,
880
1179
  body: {
881
1180
  verdict: "comment",
882
- body: originReviewBody(args),
1181
+ body: args.message,
1182
+ comments: args.comments.map(toOriginReviewCommentInput),
883
1183
  },
884
1184
  });
1185
+ const inlineCommentCount = args.comments.length;
885
1186
  return {
886
1187
  posted: true,
887
1188
  publisher: "host_origin",
888
- mode: "review_body_only",
889
- inlineCommentCount: 0,
890
- detail: `posted Origin review on ${target.repo}#${target.prNumber}`,
1189
+ mode:
1190
+ inlineCommentCount > 0
1191
+ ? "review_with_inline_comments"
1192
+ : "review_body_only",
1193
+ inlineCommentCount,
1194
+ detail: `posted Origin review on ${target.repo}#${target.prNumber} with ${inlineCommentCount} inline comment(s)`,
891
1195
  };
892
1196
  } catch (error) {
893
1197
  if (args.signal?.aborted) {
@@ -899,32 +1203,41 @@ async function postReviewViaHostOrigin(
899
1203
  }
900
1204
  }
901
1205
 
902
- /**
903
- * Origin's public CreatePullRequestReview has a body, not inline comments.
904
- * Fold attempted anchors into the summary so findings are not dropped.
905
- */
906
- function originReviewBody(args: ReviewCommentPostArgs): string {
907
- if (args.comments.length === 0) {
908
- return args.message;
909
- }
910
- const sections = args.comments.map((comment) => {
911
- const location =
912
- comment.start_line !== undefined &&
913
- comment.start_line > 0 &&
914
- comment.start_line !== comment.line
915
- ? `${comment.file}:${comment.start_line}-${comment.line}`
916
- : `${comment.file}:${comment.line}`;
917
- return `### \`${location}\`\n\n${comment.body}`;
918
- });
919
- return [args.message, ...sections].join("\n\n");
1206
+ type OriginReviewCommentInput = {
1207
+ body: string;
1208
+ inline: {
1209
+ path: string;
1210
+ side: "right";
1211
+ startLine: number;
1212
+ endLine?: number;
1213
+ };
1214
+ };
1215
+
1216
+ /** Provider-neutral inline comment Origin `ReviewCommentInput`. */
1217
+ function toOriginReviewCommentInput(
1218
+ comment: ReviewInlineComment
1219
+ ): OriginReviewCommentInput {
1220
+ const startLine =
1221
+ comment.start_line !== undefined &&
1222
+ comment.start_line > 0 &&
1223
+ comment.start_line < comment.line
1224
+ ? comment.start_line
1225
+ : comment.line;
1226
+ return {
1227
+ body: comment.body,
1228
+ inline: {
1229
+ path: comment.file,
1230
+ side: "right",
1231
+ startLine,
1232
+ ...(startLine === comment.line ? {} : { endLine: comment.line }),
1233
+ },
1234
+ };
920
1235
  }
921
1236
 
922
1237
  function skippedResolve(detail: string): ResolveReviewThreadsResult {
923
1238
  return {
924
1239
  requested: 0,
925
1240
  resolved: 0,
926
- alreadyResolved: 0,
927
- notFound: 0,
928
1241
  failed: 0,
929
1242
  detail,
930
1243
  };