@cursor/july 0.1.1

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 (623) hide show
  1. package/AGENTS.md +290 -0
  2. package/LICENSE.md +3 -0
  3. package/README.md +1233 -0
  4. package/dist/ab.d.ts +290 -0
  5. package/dist/ab.d.ts.map +1 -0
  6. package/dist/ab.js +386 -0
  7. package/dist/bin/agent-serve.d.ts +15 -0
  8. package/dist/bin/agent-serve.d.ts.map +1 -0
  9. package/dist/bin/agent-serve.js +754 -0
  10. package/dist/channels/github/api.d.ts +101 -0
  11. package/dist/channels/github/api.d.ts.map +1 -0
  12. package/dist/channels/github/api.js +229 -0
  13. package/dist/channels/github/auth.d.ts +67 -0
  14. package/dist/channels/github/auth.d.ts.map +1 -0
  15. package/dist/channels/github/auth.js +239 -0
  16. package/dist/channels/github/binding.d.ts +73 -0
  17. package/dist/channels/github/binding.d.ts.map +1 -0
  18. package/dist/channels/github/binding.js +114 -0
  19. package/dist/channels/github/defaults.d.ts +24 -0
  20. package/dist/channels/github/defaults.d.ts.map +1 -0
  21. package/dist/channels/github/defaults.js +100 -0
  22. package/dist/channels/github/events.d.ts +18 -0
  23. package/dist/channels/github/events.d.ts.map +1 -0
  24. package/dist/channels/github/events.js +59 -0
  25. package/dist/channels/github/forward.d.ts +137 -0
  26. package/dist/channels/github/forward.d.ts.map +1 -0
  27. package/dist/channels/github/forward.js +271 -0
  28. package/dist/channels/github/github-channel.d.ts +127 -0
  29. package/dist/channels/github/github-channel.d.ts.map +1 -0
  30. package/dist/channels/github/github-channel.js +1688 -0
  31. package/dist/channels/github/index.d.ts +52 -0
  32. package/dist/channels/github/index.d.ts.map +1 -0
  33. package/dist/channels/github/index.js +51 -0
  34. package/dist/channels/github/replay.d.ts +115 -0
  35. package/dist/channels/github/replay.d.ts.map +1 -0
  36. package/dist/channels/github/replay.js +282 -0
  37. package/dist/channels/github/state.d.ts +26 -0
  38. package/dist/channels/github/state.d.ts.map +1 -0
  39. package/dist/channels/github/state.js +50 -0
  40. package/dist/channels/github/types.d.ts +385 -0
  41. package/dist/channels/github/types.d.ts.map +1 -0
  42. package/dist/channels/github/types.js +1 -0
  43. package/dist/channels/slack/api.d.ts +117 -0
  44. package/dist/channels/slack/api.d.ts.map +1 -0
  45. package/dist/channels/slack/api.js +437 -0
  46. package/dist/channels/slack/approvals.d.ts +32 -0
  47. package/dist/channels/slack/approvals.d.ts.map +1 -0
  48. package/dist/channels/slack/approvals.js +157 -0
  49. package/dist/channels/slack/assistant.d.ts +23 -0
  50. package/dist/channels/slack/assistant.d.ts.map +1 -0
  51. package/dist/channels/slack/assistant.js +67 -0
  52. package/dist/channels/slack/auth.d.ts +40 -0
  53. package/dist/channels/slack/auth.d.ts.map +1 -0
  54. package/dist/channels/slack/auth.js +71 -0
  55. package/dist/channels/slack/channel-watch.d.ts +46 -0
  56. package/dist/channels/slack/channel-watch.d.ts.map +1 -0
  57. package/dist/channels/slack/channel-watch.js +180 -0
  58. package/dist/channels/slack/constants.d.ts +17 -0
  59. package/dist/channels/slack/constants.d.ts.map +1 -0
  60. package/dist/channels/slack/constants.js +27 -0
  61. package/dist/channels/slack/credentials.d.ts +35 -0
  62. package/dist/channels/slack/credentials.d.ts.map +1 -0
  63. package/dist/channels/slack/credentials.js +88 -0
  64. package/dist/channels/slack/cursor-account.d.ts +76 -0
  65. package/dist/channels/slack/cursor-account.d.ts.map +1 -0
  66. package/dist/channels/slack/cursor-account.js +88 -0
  67. package/dist/channels/slack/defaults.d.ts +32 -0
  68. package/dist/channels/slack/defaults.d.ts.map +1 -0
  69. package/dist/channels/slack/defaults.js +380 -0
  70. package/dist/channels/slack/dispatch.d.ts +43 -0
  71. package/dist/channels/slack/dispatch.d.ts.map +1 -0
  72. package/dist/channels/slack/dispatch.js +185 -0
  73. package/dist/channels/slack/doctor.d.ts +24 -0
  74. package/dist/channels/slack/doctor.d.ts.map +1 -0
  75. package/dist/channels/slack/doctor.js +115 -0
  76. package/dist/channels/slack/event-id-dedupe.d.ts +10 -0
  77. package/dist/channels/slack/event-id-dedupe.d.ts.map +1 -0
  78. package/dist/channels/slack/event-id-dedupe.js +23 -0
  79. package/dist/channels/slack/inbound.d.ts +41 -0
  80. package/dist/channels/slack/inbound.d.ts.map +1 -0
  81. package/dist/channels/slack/inbound.js +166 -0
  82. package/dist/channels/slack/index.d.ts +45 -0
  83. package/dist/channels/slack/index.d.ts.map +1 -0
  84. package/dist/channels/slack/index.js +44 -0
  85. package/dist/channels/slack/init.d.ts +32 -0
  86. package/dist/channels/slack/init.d.ts.map +1 -0
  87. package/dist/channels/slack/init.js +82 -0
  88. package/dist/channels/slack/interactive.d.ts +19 -0
  89. package/dist/channels/slack/interactive.d.ts.map +1 -0
  90. package/dist/channels/slack/interactive.js +142 -0
  91. package/dist/channels/slack/links.d.ts +10 -0
  92. package/dist/channels/slack/links.d.ts.map +1 -0
  93. package/dist/channels/slack/links.js +19 -0
  94. package/dist/channels/slack/live-delivery.d.ts +75 -0
  95. package/dist/channels/slack/live-delivery.d.ts.map +1 -0
  96. package/dist/channels/slack/live-delivery.js +143 -0
  97. package/dist/channels/slack/log.d.ts +25 -0
  98. package/dist/channels/slack/log.d.ts.map +1 -0
  99. package/dist/channels/slack/log.js +47 -0
  100. package/dist/channels/slack/manifest.d.ts +57 -0
  101. package/dist/channels/slack/manifest.d.ts.map +1 -0
  102. package/dist/channels/slack/manifest.js +110 -0
  103. package/dist/channels/slack/pr-ref.d.ts +18 -0
  104. package/dist/channels/slack/pr-ref.d.ts.map +1 -0
  105. package/dist/channels/slack/pr-ref.js +36 -0
  106. package/dist/channels/slack/setup.d.ts +14 -0
  107. package/dist/channels/slack/setup.d.ts.map +1 -0
  108. package/dist/channels/slack/setup.js +86 -0
  109. package/dist/channels/slack/slack-channel.d.ts +15 -0
  110. package/dist/channels/slack/slack-channel.d.ts.map +1 -0
  111. package/dist/channels/slack/slack-channel.js +281 -0
  112. package/dist/channels/slack/socket-mode.d.ts +26 -0
  113. package/dist/channels/slack/socket-mode.d.ts.map +1 -0
  114. package/dist/channels/slack/socket-mode.js +116 -0
  115. package/dist/channels/slack/stream.d.ts +26 -0
  116. package/dist/channels/slack/stream.d.ts.map +1 -0
  117. package/dist/channels/slack/stream.js +39 -0
  118. package/dist/channels/slack/tool-approval-events.d.ts +33 -0
  119. package/dist/channels/slack/tool-approval-events.d.ts.map +1 -0
  120. package/dist/channels/slack/tool-approval-events.js +129 -0
  121. package/dist/channels/slack/types.d.ts +326 -0
  122. package/dist/channels/slack/types.d.ts.map +1 -0
  123. package/dist/channels/slack/types.js +4 -0
  124. package/dist/channels.d.ts +116 -0
  125. package/dist/channels.d.ts.map +1 -0
  126. package/dist/channels.js +294 -0
  127. package/dist/connections.d.ts +75 -0
  128. package/dist/connections.d.ts.map +1 -0
  129. package/dist/connections.js +75 -0
  130. package/dist/docs/404.html +22 -0
  131. package/dist/docs/ab.html +87 -0
  132. package/dist/docs/assets/ab.md.COdXkces.js +63 -0
  133. package/dist/docs/assets/ab.md.COdXkces.lean.js +1 -0
  134. package/dist/docs/assets/app.DqfFEmJd.js +1 -0
  135. package/dist/docs/assets/building-with-agents.md.CJCtZCyi.js +13 -0
  136. package/dist/docs/assets/building-with-agents.md.CJCtZCyi.lean.js +1 -0
  137. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +1 -0
  138. package/dist/docs/assets/chunks/VPLocalSearchBox.BaLEdS15.js +9 -0
  139. package/dist/docs/assets/chunks/framework.CAZyNGu9.js +19 -0
  140. package/dist/docs/assets/chunks/theme.CZRvu_0q.js +2 -0
  141. package/dist/docs/assets/concepts.md.Cfb9b-k1.js +4 -0
  142. package/dist/docs/assets/concepts.md.Cfb9b-k1.lean.js +1 -0
  143. package/dist/docs/assets/deployment.md.Dx1TYNk5.js +14 -0
  144. package/dist/docs/assets/deployment.md.Dx1TYNk5.lean.js +1 -0
  145. package/dist/docs/assets/evals.md.DPZ_MAnI.js +85 -0
  146. package/dist/docs/assets/evals.md.DPZ_MAnI.lean.js +1 -0
  147. package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.js +10 -0
  148. package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.lean.js +1 -0
  149. package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.js +9 -0
  150. package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.lean.js +1 -0
  151. package/dist/docs/assets/guides_github.md.DwbKhCeS.js +20 -0
  152. package/dist/docs/assets/guides_github.md.DwbKhCeS.lean.js +1 -0
  153. package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.js +28 -0
  154. package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.lean.js +1 -0
  155. package/dist/docs/assets/guides_slack.md.bv41fHfW.js +37 -0
  156. package/dist/docs/assets/guides_slack.md.bv41fHfW.lean.js +1 -0
  157. package/dist/docs/assets/guides_webhooks.md.hFTik3lf.js +50 -0
  158. package/dist/docs/assets/guides_webhooks.md.hFTik3lf.lean.js +1 -0
  159. package/dist/docs/assets/hillclimbing.md.DHNast08.js +4 -0
  160. package/dist/docs/assets/hillclimbing.md.DHNast08.lean.js +1 -0
  161. package/dist/docs/assets/index.md.BPKcj5AI.js +20 -0
  162. package/dist/docs/assets/index.md.BPKcj5AI.lean.js +1 -0
  163. package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  164. package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  165. package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  166. package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  167. package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  168. package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  169. package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  170. package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  171. package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  172. package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  173. package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  174. package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  175. package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  176. package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  177. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +35 -0
  178. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +1 -0
  179. package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.js +34 -0
  180. package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.lean.js +1 -0
  181. package/dist/docs/assets/reference_channels.md.D7JTR03W.js +53 -0
  182. package/dist/docs/assets/reference_channels.md.D7JTR03W.lean.js +1 -0
  183. package/dist/docs/assets/reference_cli.md.DA730zCu.js +33 -0
  184. package/dist/docs/assets/reference_cli.md.DA730zCu.lean.js +1 -0
  185. package/dist/docs/assets/reference_connections.md.C3vNH_DE.js +26 -0
  186. package/dist/docs/assets/reference_connections.md.C3vNH_DE.lean.js +1 -0
  187. package/dist/docs/assets/reference_hooks.md.BCEc3MyM.js +12 -0
  188. package/dist/docs/assets/reference_hooks.md.BCEc3MyM.lean.js +1 -0
  189. package/dist/docs/assets/reference_http-api.md.DBAahtdz.js +11 -0
  190. package/dist/docs/assets/reference_http-api.md.DBAahtdz.lean.js +1 -0
  191. package/dist/docs/assets/reference_instructions.md.BC05LEQ8.js +12 -0
  192. package/dist/docs/assets/reference_instructions.md.BC05LEQ8.lean.js +1 -0
  193. package/dist/docs/assets/reference_playground.md.CPZhfYaO.js +1 -0
  194. package/dist/docs/assets/reference_playground.md.CPZhfYaO.lean.js +1 -0
  195. package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.js +31 -0
  196. package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.lean.js +1 -0
  197. package/dist/docs/assets/reference_schedules.md.D7qijxLk.js +82 -0
  198. package/dist/docs/assets/reference_schedules.md.D7qijxLk.lean.js +1 -0
  199. package/dist/docs/assets/reference_sessions.md.DdjWabnN.js +8 -0
  200. package/dist/docs/assets/reference_sessions.md.DdjWabnN.lean.js +1 -0
  201. package/dist/docs/assets/reference_skills.md.VQnlBT3Q.js +15 -0
  202. package/dist/docs/assets/reference_skills.md.VQnlBT3Q.lean.js +1 -0
  203. package/dist/docs/assets/reference_subagents.md.CIRAVcPK.js +10 -0
  204. package/dist/docs/assets/reference_subagents.md.CIRAVcPK.lean.js +1 -0
  205. package/dist/docs/assets/reference_tools.md.DF5kwlt0.js +46 -0
  206. package/dist/docs/assets/reference_tools.md.DF5kwlt0.lean.js +1 -0
  207. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +1 -0
  208. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +1 -0
  209. package/dist/docs/assets/style.Bj-IXa5W.css +1 -0
  210. package/dist/docs/assets/troubleshooting.md.DkovjS7h.js +1 -0
  211. package/dist/docs/assets/troubleshooting.md.DkovjS7h.lean.js +1 -0
  212. package/dist/docs/building-with-agents.html +37 -0
  213. package/dist/docs/concepts.html +28 -0
  214. package/dist/docs/deployment.html +38 -0
  215. package/dist/docs/evals.html +109 -0
  216. package/dist/docs/guides/agent-to-agent.html +34 -0
  217. package/dist/docs/guides/cloud-runtime.html +33 -0
  218. package/dist/docs/guides/github.html +44 -0
  219. package/dist/docs/guides/human-in-the-loop.html +52 -0
  220. package/dist/docs/guides/slack.html +61 -0
  221. package/dist/docs/guides/webhooks.html +74 -0
  222. package/dist/docs/hashmap.json +1 -0
  223. package/dist/docs/hillclimbing.html +28 -0
  224. package/dist/docs/index.html +44 -0
  225. package/dist/docs/quickstart.html +59 -0
  226. package/dist/docs/reference/agent-config.html +58 -0
  227. package/dist/docs/reference/channels.html +77 -0
  228. package/dist/docs/reference/cli.html +57 -0
  229. package/dist/docs/reference/connections.html +50 -0
  230. package/dist/docs/reference/hooks.html +36 -0
  231. package/dist/docs/reference/http-api.html +35 -0
  232. package/dist/docs/reference/instructions.html +36 -0
  233. package/dist/docs/reference/playground.html +25 -0
  234. package/dist/docs/reference/project-layout.html +55 -0
  235. package/dist/docs/reference/schedules.html +106 -0
  236. package/dist/docs/reference/sessions.html +32 -0
  237. package/dist/docs/reference/skills.html +39 -0
  238. package/dist/docs/reference/subagents.html +34 -0
  239. package/dist/docs/reference/tools.html +70 -0
  240. package/dist/docs/scaffolding-agents.html +25 -0
  241. package/dist/docs/troubleshooting.html +25 -0
  242. package/dist/docs/vp-icons.css +0 -0
  243. package/dist/evals.d.ts +312 -0
  244. package/dist/evals.d.ts.map +1 -0
  245. package/dist/evals.js +254 -0
  246. package/dist/hooks.d.ts +23 -0
  247. package/dist/hooks.d.ts.map +1 -0
  248. package/dist/hooks.js +23 -0
  249. package/dist/index.d.ts +71 -0
  250. package/dist/index.d.ts.map +1 -0
  251. package/dist/index.js +101 -0
  252. package/dist/internal/ab-collector.d.ts +42 -0
  253. package/dist/internal/ab-collector.d.ts.map +1 -0
  254. package/dist/internal/ab-collector.js +153 -0
  255. package/dist/internal/ab-fold.d.ts +36 -0
  256. package/dist/internal/ab-fold.d.ts.map +1 -0
  257. package/dist/internal/ab-fold.js +171 -0
  258. package/dist/internal/ab-snapshot.d.ts +70 -0
  259. package/dist/internal/ab-snapshot.d.ts.map +1 -0
  260. package/dist/internal/ab-snapshot.js +208 -0
  261. package/dist/internal/approval-gate.d.ts +34 -0
  262. package/dist/internal/approval-gate.d.ts.map +1 -0
  263. package/dist/internal/approval-gate.js +85 -0
  264. package/dist/internal/auth.d.ts +8 -0
  265. package/dist/internal/auth.d.ts.map +1 -0
  266. package/dist/internal/auth.js +27 -0
  267. package/dist/internal/authored-alias-hooks.d.ts +31 -0
  268. package/dist/internal/authored-alias-hooks.d.ts.map +1 -0
  269. package/dist/internal/authored-alias-hooks.js +43 -0
  270. package/dist/internal/authored-loaders.d.ts +25 -0
  271. package/dist/internal/authored-loaders.d.ts.map +1 -0
  272. package/dist/internal/authored-loaders.js +129 -0
  273. package/dist/internal/bounded-int.d.ts +11 -0
  274. package/dist/internal/bounded-int.d.ts.map +1 -0
  275. package/dist/internal/bounded-int.js +13 -0
  276. package/dist/internal/brand.d.ts +6 -0
  277. package/dist/internal/brand.d.ts.map +1 -0
  278. package/dist/internal/brand.js +17 -0
  279. package/dist/internal/chat-client.d.ts +19 -0
  280. package/dist/internal/chat-client.d.ts.map +1 -0
  281. package/dist/internal/chat-client.js +327 -0
  282. package/dist/internal/cli-ax.d.ts +58 -0
  283. package/dist/internal/cli-ax.d.ts.map +1 -0
  284. package/dist/internal/cli-ax.js +345 -0
  285. package/dist/internal/cli-cursor.d.ts +20 -0
  286. package/dist/internal/cli-cursor.d.ts.map +1 -0
  287. package/dist/internal/cli-cursor.js +227 -0
  288. package/dist/internal/cli-github.d.ts +47 -0
  289. package/dist/internal/cli-github.d.ts.map +1 -0
  290. package/dist/internal/cli-github.js +644 -0
  291. package/dist/internal/cli-slack.d.ts +18 -0
  292. package/dist/internal/cli-slack.d.ts.map +1 -0
  293. package/dist/internal/cli-slack.js +144 -0
  294. package/dist/internal/cloud-merge.d.ts +23 -0
  295. package/dist/internal/cloud-merge.d.ts.map +1 -0
  296. package/dist/internal/cloud-merge.js +28 -0
  297. package/dist/internal/cron.d.ts +17 -0
  298. package/dist/internal/cron.d.ts.map +1 -0
  299. package/dist/internal/cron.js +60 -0
  300. package/dist/internal/cursor/account-mcp.d.ts +95 -0
  301. package/dist/internal/cursor/account-mcp.d.ts.map +1 -0
  302. package/dist/internal/cursor/account-mcp.js +342 -0
  303. package/dist/internal/cursor/backend-client.d.ts +118 -0
  304. package/dist/internal/cursor/backend-client.d.ts.map +1 -0
  305. package/dist/internal/cursor/backend-client.js +477 -0
  306. package/dist/internal/cursor/credentials.d.ts +112 -0
  307. package/dist/internal/cursor/credentials.d.ts.map +1 -0
  308. package/dist/internal/cursor/credentials.js +329 -0
  309. package/dist/internal/cursor-event-relay.d.ts +111 -0
  310. package/dist/internal/cursor-event-relay.d.ts.map +1 -0
  311. package/dist/internal/cursor-event-relay.js +181 -0
  312. package/dist/internal/cursor-relay-core.d.ts +113 -0
  313. package/dist/internal/cursor-relay-core.d.ts.map +1 -0
  314. package/dist/internal/cursor-relay-core.js +364 -0
  315. package/dist/internal/cursor-slack-relay.d.ts +94 -0
  316. package/dist/internal/cursor-slack-relay.d.ts.map +1 -0
  317. package/dist/internal/cursor-slack-relay.js +171 -0
  318. package/dist/internal/describe-error.d.ts +3 -0
  319. package/dist/internal/describe-error.d.ts.map +1 -0
  320. package/dist/internal/describe-error.js +4 -0
  321. package/dist/internal/dev-mode.d.ts +13 -0
  322. package/dist/internal/dev-mode.d.ts.map +1 -0
  323. package/dist/internal/dev-mode.js +17 -0
  324. package/dist/internal/discovery.d.ts +55 -0
  325. package/dist/internal/discovery.d.ts.map +1 -0
  326. package/dist/internal/discovery.js +1427 -0
  327. package/dist/internal/distribution.d.ts +47 -0
  328. package/dist/internal/distribution.d.ts.map +1 -0
  329. package/dist/internal/distribution.js +82 -0
  330. package/dist/internal/docs-site.d.ts +28 -0
  331. package/dist/internal/docs-site.d.ts.map +1 -0
  332. package/dist/internal/docs-site.js +169 -0
  333. package/dist/internal/duration.d.ts +8 -0
  334. package/dist/internal/duration.d.ts.map +1 -0
  335. package/dist/internal/duration.js +35 -0
  336. package/dist/internal/eval-run-store.d.ts +81 -0
  337. package/dist/internal/eval-run-store.d.ts.map +1 -0
  338. package/dist/internal/eval-run-store.js +396 -0
  339. package/dist/internal/eval-runner.d.ts +76 -0
  340. package/dist/internal/eval-runner.d.ts.map +1 -0
  341. package/dist/internal/eval-runner.js +336 -0
  342. package/dist/internal/event-log.d.ts +51 -0
  343. package/dist/internal/event-log.d.ts.map +1 -0
  344. package/dist/internal/event-log.js +167 -0
  345. package/dist/internal/event-mapper.d.ts +46 -0
  346. package/dist/internal/event-mapper.d.ts.map +1 -0
  347. package/dist/internal/event-mapper.js +226 -0
  348. package/dist/internal/frontmatter.d.ts +12 -0
  349. package/dist/internal/frontmatter.d.ts.map +1 -0
  350. package/dist/internal/frontmatter.js +61 -0
  351. package/dist/internal/github-fanout.d.ts +32 -0
  352. package/dist/internal/github-fanout.d.ts.map +1 -0
  353. package/dist/internal/github-fanout.js +122 -0
  354. package/dist/internal/handleAgentServeTrigger.d.ts +59 -0
  355. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -0
  356. package/dist/internal/handleAgentServeTrigger.js +115 -0
  357. package/dist/internal/host-platforms.d.ts +40 -0
  358. package/dist/internal/host-platforms.d.ts.map +1 -0
  359. package/dist/internal/host-platforms.js +113 -0
  360. package/dist/internal/http-channel.d.ts +54 -0
  361. package/dist/internal/http-channel.d.ts.map +1 -0
  362. package/dist/internal/http-channel.js +382 -0
  363. package/dist/internal/ids.d.ts +19 -0
  364. package/dist/internal/ids.d.ts.map +1 -0
  365. package/dist/internal/ids.js +34 -0
  366. package/dist/internal/init-project.d.ts +22 -0
  367. package/dist/internal/init-project.d.ts.map +1 -0
  368. package/dist/internal/init-project.js +125 -0
  369. package/dist/internal/json-dir-store.d.ts +32 -0
  370. package/dist/internal/json-dir-store.d.ts.map +1 -0
  371. package/dist/internal/json-dir-store.js +100 -0
  372. package/dist/internal/json-schema.d.ts +18 -0
  373. package/dist/internal/json-schema.d.ts.map +1 -0
  374. package/dist/internal/json-schema.js +53 -0
  375. package/dist/internal/log-ring.d.ts +23 -0
  376. package/dist/internal/log-ring.d.ts.map +1 -0
  377. package/dist/internal/log-ring.js +42 -0
  378. package/dist/internal/map-pool.d.ts +6 -0
  379. package/dist/internal/map-pool.d.ts.map +1 -0
  380. package/dist/internal/map-pool.js +41 -0
  381. package/dist/internal/mcp-endpoint.d.ts +59 -0
  382. package/dist/internal/mcp-endpoint.d.ts.map +1 -0
  383. package/dist/internal/mcp-endpoint.js +355 -0
  384. package/dist/internal/mcp-host.d.ts +28 -0
  385. package/dist/internal/mcp-host.d.ts.map +1 -0
  386. package/dist/internal/mcp-host.js +162 -0
  387. package/dist/internal/net.d.ts +5 -0
  388. package/dist/internal/net.d.ts.map +1 -0
  389. package/dist/internal/net.js +30 -0
  390. package/dist/internal/peer-connections.d.ts +44 -0
  391. package/dist/internal/peer-connections.d.ts.map +1 -0
  392. package/dist/internal/peer-connections.js +67 -0
  393. package/dist/internal/playground/static.d.ts +35 -0
  394. package/dist/internal/playground/static.d.ts.map +1 -0
  395. package/dist/internal/playground/static.js +162 -0
  396. package/dist/internal/playground/toolchain.d.ts +57 -0
  397. package/dist/internal/playground/toolchain.d.ts.map +1 -0
  398. package/dist/internal/playground/toolchain.js +226 -0
  399. package/dist/internal/reminder-runner.d.ts +62 -0
  400. package/dist/internal/reminder-runner.d.ts.map +1 -0
  401. package/dist/internal/reminder-runner.js +582 -0
  402. package/dist/internal/reminder-schedule.d.ts +22 -0
  403. package/dist/internal/reminder-schedule.d.ts.map +1 -0
  404. package/dist/internal/reminder-schedule.js +82 -0
  405. package/dist/internal/reminder-store.d.ts +58 -0
  406. package/dist/internal/reminder-store.d.ts.map +1 -0
  407. package/dist/internal/reminder-store.js +106 -0
  408. package/dist/internal/resolved-connections.d.ts +41 -0
  409. package/dist/internal/resolved-connections.d.ts.map +1 -0
  410. package/dist/internal/resolved-connections.js +73 -0
  411. package/dist/internal/review-comments.d.ts +50 -0
  412. package/dist/internal/review-comments.d.ts.map +1 -0
  413. package/dist/internal/review-comments.js +194 -0
  414. package/dist/internal/router.d.ts +33 -0
  415. package/dist/internal/router.d.ts.map +1 -0
  416. package/dist/internal/router.js +93 -0
  417. package/dist/internal/run-client.d.ts +49 -0
  418. package/dist/internal/run-client.d.ts.map +1 -0
  419. package/dist/internal/run-client.js +208 -0
  420. package/dist/internal/schedule-runner.d.ts +31 -0
  421. package/dist/internal/schedule-runner.d.ts.map +1 -0
  422. package/dist/internal/schedule-runner.js +126 -0
  423. package/dist/internal/sdk-runner.d.ts +41 -0
  424. package/dist/internal/sdk-runner.d.ts.map +1 -0
  425. package/dist/internal/sdk-runner.js +221 -0
  426. package/dist/internal/server.d.ts +25 -0
  427. package/dist/internal/server.d.ts.map +1 -0
  428. package/dist/internal/server.js +1374 -0
  429. package/dist/internal/session-engine.d.ts +287 -0
  430. package/dist/internal/session-engine.d.ts.map +1 -0
  431. package/dist/internal/session-engine.js +1377 -0
  432. package/dist/internal/session-store.d.ts +29 -0
  433. package/dist/internal/session-store.d.ts.map +1 -0
  434. package/dist/internal/session-store.js +140 -0
  435. package/dist/internal/session-urls.d.ts +10 -0
  436. package/dist/internal/session-urls.d.ts.map +1 -0
  437. package/dist/internal/session-urls.js +25 -0
  438. package/dist/internal/stream-progress.d.ts +14 -0
  439. package/dist/internal/stream-progress.d.ts.map +1 -0
  440. package/dist/internal/stream-progress.js +195 -0
  441. package/dist/internal/tool-result.d.ts +20 -0
  442. package/dist/internal/tool-result.d.ts.map +1 -0
  443. package/dist/internal/tool-result.js +45 -0
  444. package/dist/internal/trajectory.d.ts +56 -0
  445. package/dist/internal/trajectory.d.ts.map +1 -0
  446. package/dist/internal/trajectory.js +248 -0
  447. package/dist/internal/workspace.d.ts +42 -0
  448. package/dist/internal/workspace.d.ts.map +1 -0
  449. package/dist/internal/workspace.js +199 -0
  450. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  451. package/dist/playground/assets/index-1K-hG-7p.css +1 -0
  452. package/dist/playground/assets/index-FlWjhg3x.js +79 -0
  453. package/dist/playground/index.html +17 -0
  454. package/dist/reminders.d.ts +23 -0
  455. package/dist/reminders.d.ts.map +1 -0
  456. package/dist/reminders.js +22 -0
  457. package/dist/schedules.d.ts +23 -0
  458. package/dist/schedules.d.ts.map +1 -0
  459. package/dist/schedules.js +26 -0
  460. package/dist/skills.d.ts +22 -0
  461. package/dist/skills.d.ts.map +1 -0
  462. package/dist/skills.js +22 -0
  463. package/dist/tools.d.ts +92 -0
  464. package/dist/tools.d.ts.map +1 -0
  465. package/dist/tools.js +68 -0
  466. package/dist/types.d.ts +1609 -0
  467. package/dist/types.d.ts.map +1 -0
  468. package/dist/types.js +80 -0
  469. package/docs/README.md +174 -0
  470. package/docs/ab.md +355 -0
  471. package/docs/building-with-agents.md +129 -0
  472. package/docs/concepts.md +177 -0
  473. package/docs/deployment.md +218 -0
  474. package/docs/evals.md +383 -0
  475. package/docs/guides/agent-to-agent.md +118 -0
  476. package/docs/guides/cloud-runtime.md +122 -0
  477. package/docs/guides/github.md +172 -0
  478. package/docs/guides/human-in-the-loop.md +131 -0
  479. package/docs/guides/slack.md +245 -0
  480. package/docs/guides/webhooks.md +201 -0
  481. package/docs/hillclimbing.md +93 -0
  482. package/docs/quickstart.md +183 -0
  483. package/docs/reference/agent-config.md +154 -0
  484. package/docs/reference/channels.md +206 -0
  485. package/docs/reference/cli.md +257 -0
  486. package/docs/reference/connections.md +137 -0
  487. package/docs/reference/hooks.md +74 -0
  488. package/docs/reference/http-api.md +219 -0
  489. package/docs/reference/instructions.md +77 -0
  490. package/docs/reference/playground.md +67 -0
  491. package/docs/reference/project-layout.md +119 -0
  492. package/docs/reference/schedules.md +220 -0
  493. package/docs/reference/sessions.md +165 -0
  494. package/docs/reference/skills.md +79 -0
  495. package/docs/reference/subagents.md +74 -0
  496. package/docs/reference/tools.md +203 -0
  497. package/docs/scaffolding-agents.md +113 -0
  498. package/docs/troubleshooting.md +95 -0
  499. package/package.json +185 -0
  500. package/skills/ab/SKILL.md +139 -0
  501. package/skills/create-agent/SKILL.md +219 -0
  502. package/skills/debug/SKILL.md +71 -0
  503. package/skills/evals/SKILL.md +217 -0
  504. package/skills/framework-map/SKILL.md +142 -0
  505. package/skills/github/SKILL.md +144 -0
  506. package/skills/hillclimb/SKILL.md +162 -0
  507. package/skills/setup-slack/SKILL.md +180 -0
  508. package/src/ab.ts +675 -0
  509. package/src/bin/agent-serve.ts +949 -0
  510. package/src/channels/github/api.ts +374 -0
  511. package/src/channels/github/auth.ts +275 -0
  512. package/src/channels/github/binding.ts +223 -0
  513. package/src/channels/github/defaults.ts +111 -0
  514. package/src/channels/github/events.ts +72 -0
  515. package/src/channels/github/forward.ts +381 -0
  516. package/src/channels/github/github-channel.ts +2270 -0
  517. package/src/channels/github/index.ts +52 -0
  518. package/src/channels/github/replay.ts +387 -0
  519. package/src/channels/github/state.ts +95 -0
  520. package/src/channels/github/types.ts +490 -0
  521. package/src/channels/slack/api.ts +580 -0
  522. package/src/channels/slack/approvals.ts +198 -0
  523. package/src/channels/slack/assistant.ts +76 -0
  524. package/src/channels/slack/auth.ts +112 -0
  525. package/src/channels/slack/channel-watch.ts +219 -0
  526. package/src/channels/slack/constants.ts +30 -0
  527. package/src/channels/slack/credentials.ts +90 -0
  528. package/src/channels/slack/cursor-account.ts +166 -0
  529. package/src/channels/slack/defaults.ts +450 -0
  530. package/src/channels/slack/dispatch.ts +293 -0
  531. package/src/channels/slack/doctor.ts +135 -0
  532. package/src/channels/slack/event-id-dedupe.ts +30 -0
  533. package/src/channels/slack/inbound.ts +219 -0
  534. package/src/channels/slack/index.ts +45 -0
  535. package/src/channels/slack/init.ts +133 -0
  536. package/src/channels/slack/interactive.ts +194 -0
  537. package/src/channels/slack/links.ts +23 -0
  538. package/src/channels/slack/live-delivery.ts +201 -0
  539. package/src/channels/slack/log.ts +83 -0
  540. package/src/channels/slack/manifest.ts +179 -0
  541. package/src/channels/slack/pr-ref.ts +50 -0
  542. package/src/channels/slack/setup.ts +97 -0
  543. package/src/channels/slack/slack-channel.ts +374 -0
  544. package/src/channels/slack/socket-mode.ts +154 -0
  545. package/src/channels/slack/stream.ts +63 -0
  546. package/src/channels/slack/tool-approval-events.ts +136 -0
  547. package/src/channels/slack/types.ts +349 -0
  548. package/src/channels.ts +385 -0
  549. package/src/connections.ts +89 -0
  550. package/src/evals.ts +501 -0
  551. package/src/hooks.ts +33 -0
  552. package/src/index.ts +184 -0
  553. package/src/internal/ab-collector.ts +210 -0
  554. package/src/internal/ab-fold.ts +228 -0
  555. package/src/internal/ab-snapshot.ts +335 -0
  556. package/src/internal/approval-gate.ts +115 -0
  557. package/src/internal/auth.ts +39 -0
  558. package/src/internal/authored-alias-hooks.ts +69 -0
  559. package/src/internal/authored-loaders.ts +138 -0
  560. package/src/internal/bounded-int.ts +27 -0
  561. package/src/internal/brand.ts +29 -0
  562. package/src/internal/chat-client.ts +407 -0
  563. package/src/internal/cli-ax.ts +474 -0
  564. package/src/internal/cli-cursor.ts +275 -0
  565. package/src/internal/cli-github.ts +849 -0
  566. package/src/internal/cli-slack.ts +175 -0
  567. package/src/internal/cloud-merge.ts +41 -0
  568. package/src/internal/cron.ts +78 -0
  569. package/src/internal/cursor/account-mcp.ts +472 -0
  570. package/src/internal/cursor/backend-client.ts +614 -0
  571. package/src/internal/cursor/credentials.ts +401 -0
  572. package/src/internal/cursor-event-relay.ts +281 -0
  573. package/src/internal/cursor-relay-core.ts +444 -0
  574. package/src/internal/cursor-slack-relay.ts +242 -0
  575. package/src/internal/describe-error.ts +4 -0
  576. package/src/internal/dev-mode.ts +20 -0
  577. package/src/internal/discovery.ts +1709 -0
  578. package/src/internal/distribution.ts +97 -0
  579. package/src/internal/docs-site.ts +191 -0
  580. package/src/internal/duration.ts +39 -0
  581. package/src/internal/eval-run-store.ts +519 -0
  582. package/src/internal/eval-runner.ts +522 -0
  583. package/src/internal/event-log.ts +175 -0
  584. package/src/internal/event-mapper.ts +272 -0
  585. package/src/internal/frontmatter.ts +77 -0
  586. package/src/internal/github-fanout.ts +159 -0
  587. package/src/internal/handleAgentServeTrigger.ts +156 -0
  588. package/src/internal/host-platforms.ts +144 -0
  589. package/src/internal/http-channel.ts +546 -0
  590. package/src/internal/ids.ts +44 -0
  591. package/src/internal/init-project.ts +165 -0
  592. package/src/internal/json-dir-store.ts +109 -0
  593. package/src/internal/json-schema.ts +69 -0
  594. package/src/internal/log-ring.ts +59 -0
  595. package/src/internal/map-pool.ts +32 -0
  596. package/src/internal/mcp-endpoint.ts +518 -0
  597. package/src/internal/mcp-host.ts +211 -0
  598. package/src/internal/net.ts +31 -0
  599. package/src/internal/peer-connections.ts +100 -0
  600. package/src/internal/playground/static.ts +189 -0
  601. package/src/internal/playground/toolchain.ts +277 -0
  602. package/src/internal/reminder-runner.ts +756 -0
  603. package/src/internal/reminder-schedule.ts +106 -0
  604. package/src/internal/reminder-store.ts +145 -0
  605. package/src/internal/resolved-connections.ts +145 -0
  606. package/src/internal/review-comments.ts +268 -0
  607. package/src/internal/router.ts +145 -0
  608. package/src/internal/run-client.ts +320 -0
  609. package/src/internal/schedule-runner.ts +161 -0
  610. package/src/internal/sdk-runner.ts +315 -0
  611. package/src/internal/server.ts +1737 -0
  612. package/src/internal/session-engine.ts +1810 -0
  613. package/src/internal/session-store.ts +147 -0
  614. package/src/internal/session-urls.ts +40 -0
  615. package/src/internal/stream-progress.ts +254 -0
  616. package/src/internal/tool-result.ts +54 -0
  617. package/src/internal/trajectory.ts +306 -0
  618. package/src/internal/workspace.ts +223 -0
  619. package/src/reminders.ts +35 -0
  620. package/src/schedules.ts +37 -0
  621. package/src/skills.ts +27 -0
  622. package/src/tools.ts +127 -0
  623. package/src/types.ts +1848 -0
@@ -0,0 +1,2270 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { z } from "zod";
3
+ import {
4
+ allowAll,
5
+ defineChannel,
6
+ localDevStrict,
7
+ loopbackGitHubDev,
8
+ POST,
9
+ } from "../../channels.js";
10
+ import { isDevMode } from "../../internal/dev-mode.js";
11
+ import type {
12
+ AgentCloudOptions,
13
+ AuthContext,
14
+ ChannelDefinition,
15
+ ChannelEventHandlers,
16
+ ChannelHandlerArgs,
17
+ HostContext,
18
+ JsonValue,
19
+ } from "../../types.js";
20
+ import { resolveCredentialValue } from "./auth.js";
21
+ import {
22
+ buildGitHubBinding,
23
+ type GitHubBindingConfig,
24
+ type GitHubBindingState,
25
+ } from "./binding.js";
26
+ import { buildDefaultGitHubEvents } from "./defaults.js";
27
+ import { deriveGitHubWebhookEvents, GITHUB_CHANNEL_KIND } from "./events.js";
28
+ import {
29
+ emptyGitHubChannelState,
30
+ type GitHubChannelState,
31
+ githubBindingStateForEvent,
32
+ } from "./state.js";
33
+ import type {
34
+ GitHubChannelOptions,
35
+ GitHubCheckRunEvent,
36
+ GitHubCheckSuiteEvent,
37
+ GitHubCiAppRef,
38
+ GitHubCiPullRequestRef,
39
+ GitHubComment,
40
+ GitHubConversationKind,
41
+ GitHubCredentialValue,
42
+ GitHubDispatch,
43
+ GitHubEventContext,
44
+ GitHubIssueEvent,
45
+ GitHubPullRequestAction,
46
+ GitHubPullRequestEvent,
47
+ GitHubPullRequestPayload,
48
+ GitHubRawEvent,
49
+ GitHubRepositoryRef,
50
+ GitHubStatusEvent,
51
+ GitHubTaskDispatch,
52
+ GitHubTurnDispatch,
53
+ GitHubUser,
54
+ GitHubWorkflowRunEvent,
55
+ } from "./types.js";
56
+
57
+ /**
58
+ * Inbound context as produced by the payload parsers, before the GitHub API +
59
+ * thread handles are attached. {@link enrichEventContext} adds `github`,
60
+ * `thread`, and `host` to make a full {@link GitHubEventContext} for hooks.
61
+ */
62
+ type GitHubParsedContext = Omit<
63
+ GitHubEventContext,
64
+ "github" | "thread" | "host"
65
+ >;
66
+
67
+ const DEFAULT_PULL_REQUEST_ACTIONS: ReadonlySet<string> = new Set([
68
+ "opened",
69
+ "reopened",
70
+ "synchronize",
71
+ "ready_for_review",
72
+ ]);
73
+
74
+ const repositorySchema = z.object({
75
+ id: z.number().optional(),
76
+ full_name: z.string().min(1),
77
+ html_url: z.string().url(),
78
+ name: z.string().optional(),
79
+ owner: z
80
+ .object({
81
+ login: z.string().optional(),
82
+ })
83
+ .optional(),
84
+ });
85
+
86
+ const senderSchema = z
87
+ .object({
88
+ login: z.string().min(1).optional(),
89
+ id: z.number().optional(),
90
+ type: z.string().optional(),
91
+ })
92
+ .nullable()
93
+ .optional();
94
+
95
+ const installationSchema = z
96
+ .object({
97
+ id: z.number().optional(),
98
+ })
99
+ .nullable()
100
+ .optional();
101
+
102
+ const githubPullRequestPayloadSchema: z.ZodType<GitHubPullRequestPayload> =
103
+ z.object({
104
+ action: z.string().min(1),
105
+ repository: repositorySchema,
106
+ pull_request: z.object({
107
+ number: z.number().int().positive(),
108
+ html_url: z.string().url(),
109
+ title: z.string(),
110
+ body: z.string().nullable().optional(),
111
+ draft: z.boolean().optional(),
112
+ user: z
113
+ .object({
114
+ login: z.string().min(1).optional(),
115
+ id: z.number().optional(),
116
+ type: z.string().optional(),
117
+ })
118
+ .nullable()
119
+ .optional(),
120
+ head: z.object({
121
+ ref: z.string().min(1),
122
+ sha: z.string().min(1),
123
+ }),
124
+ base: z.object({
125
+ ref: z.string().min(1),
126
+ sha: z.string().optional(),
127
+ }),
128
+ }),
129
+ sender: senderSchema,
130
+ installation: installationSchema,
131
+ });
132
+
133
+ /**
134
+ * Derive the turn's auth from the actor that triggered the delivery — the
135
+ * standard `{ auth: defaultGitHubAuth(ctx) }` dispatch from an inbound hook.
136
+ */
137
+ export function defaultGitHubAuth(ctx: GitHubParsedContext): AuthContext {
138
+ const senderId = ctx.sender?.id;
139
+ return {
140
+ authenticator: "github-webhook",
141
+ principalId:
142
+ senderId !== undefined
143
+ ? `github:${senderId}`
144
+ : (ctx.senderLogin ?? "github"),
145
+ principalType: ctx.sender?.type === "Bot" ? "service" : "user",
146
+ attributes: {
147
+ conversation_kind: ctx.conversation.kind,
148
+ ...(ctx.deliveryId === undefined ? {} : { delivery_id: ctx.deliveryId }),
149
+ ...(ctx.installationId === undefined
150
+ ? {}
151
+ : { installation_id: String(ctx.installationId) }),
152
+ repository: ctx.repository.fullName,
153
+ ...(ctx.repository.id === undefined
154
+ ? {}
155
+ : { repository_id: String(ctx.repository.id) }),
156
+ ...(ctx.senderLogin === undefined ? {} : { user_login: ctx.senderLogin }),
157
+ ...(ctx.sender?.type === undefined ? {} : { user_type: ctx.sender.type }),
158
+ ...(ctx.repository.owner === ""
159
+ ? {}
160
+ : { issuer: `github:${ctx.repository.owner}` }),
161
+ ...(ctx.senderLogin === undefined ? {} : { subject: ctx.senderLogin }),
162
+ },
163
+ };
164
+ }
165
+
166
+ export function githubContinuationKey(ctx: GitHubParsedContext): string {
167
+ const kind = ctx.conversation.kind === "issue" ? "github-issue" : "github-pr";
168
+ return `${kind}:${ctx.repository.fullName}#${ctx.conversation.number}`;
169
+ }
170
+
171
+ /** @deprecated Prefer {@link githubContinuationKey}. */
172
+ export function githubPullRequestContinuationKey(
173
+ ctx: GitHubParsedContext
174
+ ): string {
175
+ return githubContinuationKey(ctx);
176
+ }
177
+
178
+ export function defaultGitHubTitle(ctx: GitHubParsedContext): string {
179
+ return `GitHub ${ctx.repository.fullName}#${ctx.conversation.number}`;
180
+ }
181
+
182
+ /** @deprecated Prefer {@link defaultGitHubTitle}. */
183
+ export function defaultGitHubPullRequestTitle(
184
+ ctx: GitHubParsedContext
185
+ ): string {
186
+ return defaultGitHubTitle(ctx);
187
+ }
188
+
189
+ export function defaultGitHubPullRequestMessage(
190
+ ctx: GitHubParsedContext,
191
+ pullRequest: GitHubPullRequestEvent
192
+ ): string {
193
+ return [
194
+ `GitHub pull request trigger: ${ctx.repository.fullName}#${pullRequest.number}`,
195
+ `Action: ${pullRequest.action}`,
196
+ `PR: ${pullRequest.url}`,
197
+ `Title: ${pullRequest.title}`,
198
+ `Head: ${pullRequest.headRef} (${pullRequest.headSha})`,
199
+ `Base: ${pullRequest.baseRef}`,
200
+ ...(pullRequest.authorLogin === undefined
201
+ ? []
202
+ : [`Author: ${pullRequest.authorLogin}`]),
203
+ ...(ctx.senderLogin === undefined ? [] : [`Sender: ${ctx.senderLogin}`]),
204
+ "",
205
+ "Handle this pull request event.",
206
+ ].join("\n");
207
+ }
208
+
209
+ /**
210
+ * Default dispatch gate when no `onPullRequest` hook is declared: the common
211
+ * review triggers start a turn as the delivery's actor.
212
+ *
213
+ * Eve has no default for `onPullRequest` (opt-in only). Agent-serve keeps this
214
+ * gate for backward compatibility with existing demos.
215
+ */
216
+ export function defaultGitHubPullRequestDispatch(
217
+ ctx: GitHubParsedContext,
218
+ pullRequest: GitHubPullRequestEvent
219
+ ): GitHubTurnDispatch | null {
220
+ if (!DEFAULT_PULL_REQUEST_ACTIONS.has(pullRequest.action)) {
221
+ return null;
222
+ }
223
+ return { auth: defaultGitHubAuth(ctx) };
224
+ }
225
+
226
+ /**
227
+ * Default comment hook (Eve-compatible): dispatch only when the comment
228
+ * `@mention`s {@link botName}.
229
+ */
230
+ export function defaultOnComment(
231
+ ctx: GitHubParsedContext,
232
+ comment: GitHubComment,
233
+ options: { botName?: string }
234
+ ): GitHubTurnDispatch | null {
235
+ if (
236
+ !shouldDispatchGitHubComment({
237
+ body: comment.body,
238
+ author: comment.author,
239
+ botName: options.botName,
240
+ })
241
+ ) {
242
+ return null;
243
+ }
244
+ return { auth: defaultGitHubAuth(ctx) };
245
+ }
246
+
247
+ function isTaskDispatch(
248
+ dispatch: GitHubDispatch
249
+ ): dispatch is GitHubTaskDispatch {
250
+ return "task" in dispatch;
251
+ }
252
+
253
+ export function shouldDispatchGitHubComment(args: {
254
+ body: string;
255
+ author?: GitHubUser;
256
+ botName?: string;
257
+ }): boolean {
258
+ const botName = args.botName?.trim();
259
+ if (botName === undefined || botName === "") {
260
+ return false;
261
+ }
262
+ if (isGitHubAppBotAuthor(args.author, botName)) {
263
+ return false;
264
+ }
265
+ const mention = new RegExp(`(^|\\W)@${escapeRegExp(botName)}\\b`, "i");
266
+ return mention.test(args.body);
267
+ }
268
+
269
+ export function extractGitHubCommentTrigger(args: {
270
+ body: string;
271
+ botName?: string;
272
+ }): { message: string } | undefined {
273
+ const botName = args.botName?.trim();
274
+ if (botName === undefined || botName === "") {
275
+ const trimmed = args.body.trim();
276
+ return trimmed === "" ? undefined : { message: trimmed };
277
+ }
278
+ const mention = new RegExp(`@${escapeRegExp(botName)}\\b[,:]?\\s*`, "i");
279
+ const stripped = args.body.replace(mention, "").trim();
280
+ if (stripped === "") {
281
+ return { message: args.body.trim() };
282
+ }
283
+ return { message: stripped };
284
+ }
285
+
286
+ export function parseGitHubPullRequestEvent(args: {
287
+ payload: unknown;
288
+ deliveryId?: string;
289
+ }): { ctx: GitHubParsedContext; pullRequest: GitHubPullRequestEvent } | null {
290
+ const parsed = githubPullRequestPayloadSchema.safeParse(args.payload);
291
+ if (!parsed.success) {
292
+ return null;
293
+ }
294
+ const payload = parsed.data;
295
+ const repository = toRepositoryRef(payload.repository);
296
+ const sender = toUser(payload.sender);
297
+ const installationId = payload.installation?.id;
298
+ const ctx: GitHubParsedContext = {
299
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
300
+ eventName: "pull_request",
301
+ repository,
302
+ conversation: {
303
+ kind: "pull_request",
304
+ number: payload.pull_request.number,
305
+ url: payload.pull_request.html_url,
306
+ },
307
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
308
+ ...(installationId === undefined ? {} : { installationId }),
309
+ };
310
+ const pullRequest: GitHubPullRequestEvent = {
311
+ action: payload.action as GitHubPullRequestAction,
312
+ number: payload.pull_request.number,
313
+ url: payload.pull_request.html_url,
314
+ title: payload.pull_request.title,
315
+ ...(payload.pull_request.body === undefined ||
316
+ payload.pull_request.body === null
317
+ ? {}
318
+ : { body: payload.pull_request.body }),
319
+ headRef: payload.pull_request.head.ref,
320
+ headSha: payload.pull_request.head.sha,
321
+ baseRef: payload.pull_request.base.ref,
322
+ ...(payload.pull_request.base.sha === undefined
323
+ ? {}
324
+ : { baseSha: payload.pull_request.base.sha }),
325
+ draft: payload.pull_request.draft ?? false,
326
+ ...(payload.pull_request.user?.login === undefined
327
+ ? {}
328
+ : { authorLogin: payload.pull_request.user.login }),
329
+ };
330
+ return { ctx, pullRequest };
331
+ }
332
+
333
+ export async function verifyGitHubWebhookSignature(args: {
334
+ body: string;
335
+ signatureHeader: string | null;
336
+ secret: GitHubCredentialValue | undefined;
337
+ }): Promise<"verified" | "missing_secret" | "missing_signature" | "mismatch"> {
338
+ const secret = await resolveCredentialValue(args.secret);
339
+ if (secret === undefined || secret === "") {
340
+ return "missing_secret";
341
+ }
342
+ if (args.signatureHeader === null) {
343
+ return "missing_signature";
344
+ }
345
+ const isVerified = verifyWebhookSha256Signature({
346
+ secret,
347
+ body: args.body,
348
+ signatureHeader: args.signatureHeader,
349
+ });
350
+ return isVerified ? "verified" : "mismatch";
351
+ }
352
+
353
+ function verifyWebhookSha256Signature(args: {
354
+ secret: string;
355
+ body: string;
356
+ signatureHeader: string;
357
+ }): boolean {
358
+ const prefix = "sha256=";
359
+ if (!args.signatureHeader.startsWith(prefix)) {
360
+ return false;
361
+ }
362
+
363
+ const receivedHex = args.signatureHeader.slice(prefix.length);
364
+ if (!/^[0-9a-f]+$/i.test(receivedHex) || receivedHex.length % 2 !== 0) {
365
+ return false;
366
+ }
367
+
368
+ const expected = createHmac("sha256", args.secret).update(args.body).digest();
369
+ const received = Buffer.from(receivedHex, "hex");
370
+ return (
371
+ received.length === expected.length && timingSafeEqual(received, expected)
372
+ );
373
+ }
374
+
375
+ /**
376
+ * GitHub webhook channel (Eve-compatible dispatch surface). Point a GitHub App
377
+ * or repository webhook at `/<agent>/v1/channels/github` and declare inbound
378
+ * hooks:
379
+ *
380
+ * ```ts
381
+ * import { defaultGitHubAuth, githubChannel } from "@anysphere/agent-serve/channels/github";
382
+ *
383
+ * export default githubChannel({
384
+ * botName: "my-agent",
385
+ * credentials: { webhookSecret: () => process.env.GITHUB_WEBHOOK_SECRET },
386
+ * // Replaces the @mention gate. Prefer typed Octokit via getOctokit().
387
+ * onComment: (ctx, comment) => ({ auth: defaultGitHubAuth(ctx) }),
388
+ * onPullRequest: (ctx, pr) =>
389
+ * pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null,
390
+ * onStatus: (ctx, status) =>
391
+ * status.state === "failure"
392
+ * ? {
393
+ * task: async () => {
394
+ * const octokit = await ctx.github.getOctokit();
395
+ * await octokit.rest.repos.createCommitStatus({
396
+ * owner: ctx.repository.owner,
397
+ * repo: ctx.repository.name,
398
+ * sha: status.sha,
399
+ * state: "pending",
400
+ * context: "agent/triage",
401
+ * });
402
+ * },
403
+ * }
404
+ * : null,
405
+ * onEvent: (ctx, event) =>
406
+ * event.eventName === "label" ? { auth: defaultGitHubAuth(ctx) } : null,
407
+ * });
408
+ * ```
409
+ */
410
+ /**
411
+ * Core GitHub webhook dispatch used by {@link githubChannel} routes and by
412
+ * {@link handleAgentServeTrigger} fan-out. Signature verification is the
413
+ * caller's responsibility (HTTP route verifies; internal fan-out skips).
414
+ */
415
+ export async function dispatchGitHubWebhook(input: {
416
+ options: GitHubChannelOptions;
417
+ eventName: string | null;
418
+ payload: unknown;
419
+ deliveryId?: string;
420
+ args: ChannelHandlerArgs;
421
+ }): Promise<Response> {
422
+ const { options, eventName, payload, deliveryId, args } = input;
423
+ const botName = options.botName ?? process.env.GITHUB_APP_SLUG;
424
+ const bindingConfig: GitHubBindingConfig = {
425
+ api: options.api,
426
+ credentials: options.credentials,
427
+ };
428
+
429
+ if (eventName === "ping") {
430
+ return Response.json({ ok: true, event: "ping" });
431
+ }
432
+
433
+ switch (eventName) {
434
+ case "pull_request":
435
+ return handlePullRequest({
436
+ options,
437
+ bindingConfig,
438
+ payload,
439
+ deliveryId,
440
+ args,
441
+ });
442
+ case "issue_comment":
443
+ return handleIssueComment({
444
+ options,
445
+ bindingConfig,
446
+ botName,
447
+ payload,
448
+ deliveryId,
449
+ args,
450
+ });
451
+ case "pull_request_review_comment":
452
+ return handleReviewComment({
453
+ options,
454
+ bindingConfig,
455
+ botName,
456
+ payload,
457
+ deliveryId,
458
+ args,
459
+ });
460
+ case "issues":
461
+ return handleIssue({
462
+ options,
463
+ bindingConfig,
464
+ payload,
465
+ deliveryId,
466
+ args,
467
+ });
468
+ case "check_suite":
469
+ return handleCheckSuite({
470
+ options,
471
+ bindingConfig,
472
+ payload,
473
+ deliveryId,
474
+ args,
475
+ });
476
+ case "check_run":
477
+ return handleCheckRun({
478
+ options,
479
+ bindingConfig,
480
+ payload,
481
+ deliveryId,
482
+ args,
483
+ });
484
+ case "workflow_run":
485
+ return handleWorkflowRun({
486
+ options,
487
+ bindingConfig,
488
+ payload,
489
+ deliveryId,
490
+ args,
491
+ });
492
+ case "status":
493
+ return handleStatus({
494
+ options,
495
+ bindingConfig,
496
+ payload,
497
+ deliveryId,
498
+ args,
499
+ });
500
+ default:
501
+ if (options.onEvent !== undefined && eventName !== null) {
502
+ return handleGenericEvent({
503
+ options,
504
+ bindingConfig,
505
+ eventName,
506
+ payload,
507
+ deliveryId,
508
+ args,
509
+ });
510
+ }
511
+ return Response.json({
512
+ ok: true,
513
+ skipped: true,
514
+ reason: "unsupported_event",
515
+ event: eventName,
516
+ });
517
+ }
518
+ }
519
+
520
+ /**
521
+ * HTTP entry for a single agent's GitHub channel: optional HMAC verify, then
522
+ * {@link dispatchGitHubWebhook}.
523
+ */
524
+ export async function handleGitHubWebhookRequest(input: {
525
+ options: GitHubChannelOptions;
526
+ request: Request;
527
+ args: ChannelHandlerArgs;
528
+ /** When true, skip HMAC even if a webhook secret is configured. */
529
+ skipSignatureVerification?: boolean;
530
+ }): Promise<Response> {
531
+ const bodyText = await input.request.text();
532
+ const webhookSecret =
533
+ input.options.credentials?.webhookSecret ?? envWebhookSecret();
534
+
535
+ if (webhookSecret !== undefined && input.skipSignatureVerification !== true) {
536
+ const signatureResult = await verifyGitHubWebhookSignature({
537
+ body: bodyText,
538
+ signatureHeader: input.request.headers.get("x-hub-signature-256"),
539
+ secret: webhookSecret,
540
+ });
541
+ if (signatureResult !== "verified") {
542
+ return Response.json(
543
+ { ok: false, error: signatureResult },
544
+ { status: 401 }
545
+ );
546
+ }
547
+ }
548
+
549
+ let payload: unknown;
550
+ try {
551
+ payload = JSON.parse(bodyText) as unknown;
552
+ } catch {
553
+ return Response.json({ ok: false, error: "invalid_json" }, { status: 400 });
554
+ }
555
+
556
+ return dispatchGitHubWebhook({
557
+ options: input.options,
558
+ eventName: input.request.headers.get("x-github-event"),
559
+ payload,
560
+ deliveryId: input.request.headers.get("x-github-delivery") ?? undefined,
561
+ args: input.args,
562
+ });
563
+ }
564
+
565
+ /** Options used to construct a github channel — for host-level fan-out. */
566
+ const githubChannelOptionsByDefinition = new WeakMap<
567
+ object,
568
+ GitHubChannelOptions
569
+ >();
570
+
571
+ /**
572
+ * Recover the {@link GitHubChannelOptions} used to build a channel definition,
573
+ * if it was created via {@link githubChannel}.
574
+ */
575
+ export function getGitHubChannelOptions(
576
+ definition: object
577
+ ): GitHubChannelOptions | undefined {
578
+ return githubChannelOptionsByDefinition.get(definition);
579
+ }
580
+
581
+ export function githubChannel(
582
+ options: GitHubChannelOptions = {}
583
+ ): ChannelDefinition<GitHubChannelState> {
584
+ const routePath = options.path ?? "/";
585
+ const webhookSecret =
586
+ options.credentials?.webhookSecret ?? envWebhookSecret();
587
+ // With a secret the HMAC is the request auth (allowAll). Without one, prod
588
+ // stays loopback-strict; dev admits unsigned deliveries from a loopback peer
589
+ // only (loopbackGitHubDev) — not the LAN. Admits `gh webhook forward`
590
+ // deliveries (loopback peer + GitHub forwarding/delivery headers) while
591
+ // rejecting loopback tunnels that relay unsigned traffic without
592
+ // `X-GitHub-Event`.
593
+ //
594
+ // Evaluate `isDevMode()` per request (not at channel construction): channel
595
+ // modules call `githubChannel()` at import, and discovery may cache the
596
+ // definition across serve() calls. `serve()` sets the flag before discovery,
597
+ // but request-time lookup stays correct if that ordering ever drifts.
598
+ const auth =
599
+ options.auth ??
600
+ (webhookSecret !== undefined
601
+ ? [allowAll()]
602
+ : [
603
+ (request, info) =>
604
+ (isDevMode() ? loopbackGitHubDev() : localDevStrict())(
605
+ request,
606
+ info
607
+ ),
608
+ ]);
609
+
610
+ const events: ChannelEventHandlers<GitHubChannelState> = {
611
+ ...buildDefaultGitHubEvents({ ...options }),
612
+ ...options.events,
613
+ };
614
+
615
+ const definition = defineChannel({
616
+ auth,
617
+ state: emptyGitHubChannelState(),
618
+ events,
619
+ meta: {
620
+ kind: GITHUB_CHANNEL_KIND,
621
+ githubWebhookEvents: deriveGitHubWebhookEvents(options),
622
+ },
623
+ onStart: options.onStart,
624
+ onStop: options.onStop,
625
+ routes: [
626
+ POST(routePath, {
627
+ // Webhook payloads vary by `X-GitHub-Event`; event-specific Zod
628
+ // parsing happens inside the dispatcher. Require a schema at the
629
+ // route boundary so /v1/info + Try always have something to show.
630
+ bodySchema: z.unknown(),
631
+ description: "GitHub webhook ingress",
632
+ handler: async (request, args) =>
633
+ handleGitHubWebhookRequest({ options, request, args }),
634
+ }),
635
+ ],
636
+ });
637
+ githubChannelOptionsByDefinition.set(definition, options);
638
+ return definition;
639
+ }
640
+
641
+ async function handlePullRequest(input: {
642
+ options: GitHubChannelOptions;
643
+ bindingConfig: GitHubBindingConfig;
644
+ payload: unknown;
645
+ deliveryId?: string;
646
+ args: ChannelHandlerArgs;
647
+ }): Promise<Response> {
648
+ const parsed = parseGitHubPullRequestEvent({
649
+ payload: input.payload,
650
+ deliveryId: input.deliveryId,
651
+ });
652
+ if (parsed === null) {
653
+ return Response.json(
654
+ { ok: false, error: "invalid_pull_request_payload" },
655
+ { status: 400 }
656
+ );
657
+ }
658
+ const { pullRequest } = parsed;
659
+ const installationId =
660
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
661
+ const binding = githubBindingStateForEvent({
662
+ kind: parsed.ctx.conversation.kind,
663
+ owner: parsed.ctx.repository.owner,
664
+ repo: parsed.ctx.repository.name,
665
+ number: parsed.ctx.conversation.number,
666
+ repositoryId: parsed.ctx.repository.id,
667
+ installationId,
668
+ });
669
+ const ctx = enrichEventContext({
670
+ bindingConfig: input.bindingConfig,
671
+ ctx: parsed.ctx,
672
+ bindingState: binding,
673
+ installationId,
674
+ host: input.args.host,
675
+ });
676
+ const dispatch =
677
+ input.options.onPullRequest === undefined
678
+ ? defaultGitHubPullRequestDispatch(ctx, pullRequest)
679
+ : await input.options.onPullRequest(ctx, pullRequest);
680
+ if (dispatch === null) {
681
+ return skipped("not_dispatched", { action: pullRequest.action });
682
+ }
683
+
684
+ if (isTaskDispatch(dispatch)) {
685
+ return acknowledgeTask(input.args, dispatch, {
686
+ action: pullRequest.action,
687
+ prUrl: pullRequest.url,
688
+ headSha: pullRequest.headSha,
689
+ });
690
+ }
691
+
692
+ const message = [
693
+ dispatch.message ?? defaultGitHubPullRequestMessage(ctx, pullRequest),
694
+ ...(dispatch.context ?? []),
695
+ ].join("\n");
696
+ const cloud = await resolveCloudOptions(
697
+ input.options.cloud,
698
+ ctx,
699
+ pullRequest
700
+ );
701
+ return startTurn({
702
+ args: input.args,
703
+ ctx,
704
+ message,
705
+ dispatch,
706
+ cloud,
707
+ state: sessionStateFromBinding({
708
+ binding,
709
+ deliveryId: ctx.deliveryId,
710
+ eventName: ctx.eventName,
711
+ headSha: pullRequest.headSha,
712
+ baseRef: pullRequest.baseRef,
713
+ headRef: pullRequest.headRef,
714
+ }),
715
+ });
716
+ }
717
+
718
+ async function handleIssueComment(input: {
719
+ options: GitHubChannelOptions;
720
+ bindingConfig: GitHubBindingConfig;
721
+ botName?: string;
722
+ payload: unknown;
723
+ deliveryId?: string;
724
+ args: ChannelHandlerArgs;
725
+ }): Promise<Response> {
726
+ const parsed = parseIssueCommentEvent({
727
+ payload: input.payload,
728
+ deliveryId: input.deliveryId,
729
+ });
730
+ if (parsed === null) {
731
+ return Response.json(
732
+ { ok: false, error: "invalid_issue_comment_payload" },
733
+ { status: 400 }
734
+ );
735
+ }
736
+ if (parsed.action !== "created") {
737
+ return skipped("not_dispatched", { action: parsed.action });
738
+ }
739
+ if (
740
+ isIgnoredInboundComment(
741
+ parsed.comment.body,
742
+ parsed.comment.author,
743
+ input.botName
744
+ )
745
+ ) {
746
+ return skipped("ignored_comment");
747
+ }
748
+
749
+ const installationId =
750
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
751
+ const binding = githubBindingStateForEvent({
752
+ kind: parsed.ctx.conversation.kind,
753
+ owner: parsed.ctx.repository.owner,
754
+ repo: parsed.ctx.repository.name,
755
+ number: parsed.ctx.conversation.number,
756
+ repositoryId: parsed.ctx.repository.id,
757
+ installationId,
758
+ triggeringCommentId: parsed.comment.id,
759
+ });
760
+ const ctx = enrichEventContext({
761
+ bindingConfig: input.bindingConfig,
762
+ ctx: parsed.ctx,
763
+ bindingState: binding,
764
+ installationId,
765
+ host: input.args.host,
766
+ });
767
+
768
+ const dispatch =
769
+ input.options.onComment === undefined
770
+ ? defaultOnComment(ctx, parsed.comment, {
771
+ botName: input.botName,
772
+ })
773
+ : await input.options.onComment(ctx, parsed.comment);
774
+ if (dispatch === null) {
775
+ return skipped("not_dispatched");
776
+ }
777
+ if (isTaskDispatch(dispatch)) {
778
+ return acknowledgeTask(input.args, dispatch, {
779
+ event: "issue_comment",
780
+ action: parsed.action,
781
+ });
782
+ }
783
+
784
+ const trigger = extractGitHubCommentTrigger({
785
+ body: parsed.comment.body,
786
+ botName: input.botName,
787
+ });
788
+ const message = [
789
+ dispatch.message ?? trigger?.message ?? parsed.comment.body.trim(),
790
+ ...(dispatch.context ?? []),
791
+ ].join("\n");
792
+
793
+ return startTurn({
794
+ args: input.args,
795
+ ctx,
796
+ message,
797
+ dispatch,
798
+ state: sessionStateFromBinding({
799
+ binding,
800
+ deliveryId: ctx.deliveryId,
801
+ eventName: ctx.eventName,
802
+ }),
803
+ });
804
+ }
805
+
806
+ async function handleReviewComment(input: {
807
+ options: GitHubChannelOptions;
808
+ bindingConfig: GitHubBindingConfig;
809
+ botName?: string;
810
+ payload: unknown;
811
+ deliveryId?: string;
812
+ args: ChannelHandlerArgs;
813
+ }): Promise<Response> {
814
+ const parsed = parseReviewCommentEvent({
815
+ payload: input.payload,
816
+ deliveryId: input.deliveryId,
817
+ });
818
+ if (parsed === null) {
819
+ return Response.json(
820
+ { ok: false, error: "invalid_review_comment_payload" },
821
+ { status: 400 }
822
+ );
823
+ }
824
+ if (parsed.action !== "created") {
825
+ return skipped("not_dispatched", { action: parsed.action });
826
+ }
827
+ if (
828
+ isIgnoredInboundComment(
829
+ parsed.comment.body,
830
+ parsed.comment.author,
831
+ input.botName
832
+ )
833
+ ) {
834
+ return skipped("ignored_comment");
835
+ }
836
+
837
+ const installationId =
838
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
839
+ const binding = githubBindingStateForEvent({
840
+ kind: parsed.ctx.conversation.kind,
841
+ owner: parsed.ctx.repository.owner,
842
+ repo: parsed.ctx.repository.name,
843
+ number: parsed.ctx.conversation.number,
844
+ repositoryId: parsed.ctx.repository.id,
845
+ installationId,
846
+ triggeringCommentId: parsed.comment.id,
847
+ reviewCommentId: parsed.comment.id,
848
+ reviewThreadRootCommentId: parsed.comment.inReplyToId ?? parsed.comment.id,
849
+ });
850
+ const ctx = enrichEventContext({
851
+ bindingConfig: input.bindingConfig,
852
+ ctx: parsed.ctx,
853
+ bindingState: binding,
854
+ installationId,
855
+ host: input.args.host,
856
+ });
857
+
858
+ const dispatch =
859
+ input.options.onComment === undefined
860
+ ? defaultOnComment(ctx, parsed.comment, {
861
+ botName: input.botName,
862
+ })
863
+ : await input.options.onComment(ctx, parsed.comment);
864
+ if (dispatch === null) {
865
+ return skipped("not_dispatched");
866
+ }
867
+ if (isTaskDispatch(dispatch)) {
868
+ return acknowledgeTask(input.args, dispatch, {
869
+ event: "pull_request_review_comment",
870
+ action: parsed.action,
871
+ });
872
+ }
873
+
874
+ const trigger = extractGitHubCommentTrigger({
875
+ body: parsed.comment.body,
876
+ botName: input.botName,
877
+ });
878
+ const message = [
879
+ dispatch.message ?? trigger?.message ?? parsed.comment.body.trim(),
880
+ ...(dispatch.context ?? []),
881
+ ].join("\n");
882
+
883
+ return startTurn({
884
+ args: input.args,
885
+ ctx,
886
+ message,
887
+ dispatch,
888
+ state: sessionStateFromBinding({
889
+ binding,
890
+ deliveryId: ctx.deliveryId,
891
+ eventName: ctx.eventName,
892
+ }),
893
+ });
894
+ }
895
+
896
+ async function handleIssue(input: {
897
+ options: GitHubChannelOptions;
898
+ bindingConfig: GitHubBindingConfig;
899
+ payload: unknown;
900
+ deliveryId?: string;
901
+ args: ChannelHandlerArgs;
902
+ }): Promise<Response> {
903
+ if (input.options.onIssue === undefined) {
904
+ return skipped("no_handler", { event: "issues" });
905
+ }
906
+ const parsed = parseIssueEvent({
907
+ payload: input.payload,
908
+ deliveryId: input.deliveryId,
909
+ });
910
+ if (parsed === null) {
911
+ return Response.json(
912
+ { ok: false, error: "invalid_issues_payload" },
913
+ { status: 400 }
914
+ );
915
+ }
916
+ const installationId =
917
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
918
+ const binding = githubBindingStateForEvent({
919
+ kind: parsed.ctx.conversation.kind,
920
+ owner: parsed.ctx.repository.owner,
921
+ repo: parsed.ctx.repository.name,
922
+ number: parsed.ctx.conversation.number,
923
+ repositoryId: parsed.ctx.repository.id,
924
+ installationId,
925
+ });
926
+ const ctx = enrichEventContext({
927
+ bindingConfig: input.bindingConfig,
928
+ ctx: parsed.ctx,
929
+ bindingState: binding,
930
+ installationId,
931
+ host: input.args.host,
932
+ });
933
+ const dispatch = await input.options.onIssue(ctx, parsed.issue);
934
+ if (dispatch === null) {
935
+ return skipped("not_dispatched", { action: parsed.issue.action });
936
+ }
937
+ if (isTaskDispatch(dispatch)) {
938
+ return acknowledgeTask(input.args, dispatch, {
939
+ event: "issues",
940
+ action: parsed.issue.action,
941
+ issueNumber: parsed.issue.number,
942
+ });
943
+ }
944
+ const message = [
945
+ dispatch.message ??
946
+ `Issue ${parsed.issue.action}: #${parsed.issue.number} ${parsed.issue.title}`,
947
+ ...(dispatch.context ?? []),
948
+ ].join("\n");
949
+ return startTurn({
950
+ args: input.args,
951
+ ctx,
952
+ message,
953
+ dispatch,
954
+ state: sessionStateFromBinding({
955
+ binding,
956
+ deliveryId: ctx.deliveryId,
957
+ eventName: ctx.eventName,
958
+ }),
959
+ });
960
+ }
961
+
962
+ async function handleCheckSuite(input: {
963
+ options: GitHubChannelOptions;
964
+ bindingConfig: GitHubBindingConfig;
965
+ payload: unknown;
966
+ deliveryId?: string;
967
+ args: ChannelHandlerArgs;
968
+ }): Promise<Response> {
969
+ if (input.options.onCheckSuite === undefined) {
970
+ return skipped("no_handler", { event: "check_suite" });
971
+ }
972
+ const parsed = parseCheckSuiteEvent({
973
+ payload: input.payload,
974
+ deliveryId: input.deliveryId,
975
+ });
976
+ if (parsed === null) {
977
+ return Response.json(
978
+ { ok: false, error: "invalid_check_suite_payload" },
979
+ { status: 400 }
980
+ );
981
+ }
982
+ const installationId =
983
+ readInstallationId(input.payload) ?? parsed.unanchoredCtx.installationId;
984
+ if (parsed.ctx === null) {
985
+ const dispatch = await input.options.onCheckSuite(
986
+ enrichCiContext({
987
+ bindingConfig: input.bindingConfig,
988
+ ctx: parsed.unanchoredCtx,
989
+ installationId,
990
+ host: input.args.host,
991
+ }),
992
+ parsed.checkSuite
993
+ );
994
+ if (dispatch !== null && isTaskDispatch(dispatch)) {
995
+ return acknowledgeTask(input.args, dispatch, {
996
+ event: "check_suite",
997
+ action: parsed.checkSuite.action,
998
+ checkSuiteId: parsed.checkSuite.checkSuiteId,
999
+ });
1000
+ }
1001
+ return skipped("no_pull_request", { event: "check_suite" });
1002
+ }
1003
+ const binding = githubBindingStateForEvent({
1004
+ kind: parsed.ctx.conversation.kind,
1005
+ owner: parsed.ctx.repository.owner,
1006
+ repo: parsed.ctx.repository.name,
1007
+ number: parsed.ctx.conversation.number,
1008
+ repositoryId: parsed.ctx.repository.id,
1009
+ installationId,
1010
+ });
1011
+ const ctx = enrichEventContext({
1012
+ bindingConfig: input.bindingConfig,
1013
+ ctx: parsed.ctx,
1014
+ bindingState: binding,
1015
+ installationId,
1016
+ host: input.args.host,
1017
+ });
1018
+ const dispatch = await input.options.onCheckSuite(ctx, parsed.checkSuite);
1019
+ if (dispatch === null) {
1020
+ return skipped("not_dispatched", { action: parsed.checkSuite.action });
1021
+ }
1022
+ if (isTaskDispatch(dispatch)) {
1023
+ return acknowledgeTask(input.args, dispatch, {
1024
+ event: "check_suite",
1025
+ action: parsed.checkSuite.action,
1026
+ checkSuiteId: parsed.checkSuite.checkSuiteId,
1027
+ });
1028
+ }
1029
+ const message = [
1030
+ dispatch.message ??
1031
+ formatCiMessage(
1032
+ "Check suite",
1033
+ parsed.checkSuite.checkSuiteId,
1034
+ parsed.checkSuite
1035
+ ),
1036
+ ...(dispatch.context ?? []),
1037
+ ].join("\n");
1038
+ return startTurn({
1039
+ args: input.args,
1040
+ ctx,
1041
+ message,
1042
+ dispatch,
1043
+ state: sessionStateFromBinding({
1044
+ binding,
1045
+ deliveryId: ctx.deliveryId,
1046
+ eventName: ctx.eventName,
1047
+ headSha: parsed.checkSuite.headSha,
1048
+ }),
1049
+ });
1050
+ }
1051
+
1052
+ async function handleCheckRun(input: {
1053
+ options: GitHubChannelOptions;
1054
+ bindingConfig: GitHubBindingConfig;
1055
+ payload: unknown;
1056
+ deliveryId?: string;
1057
+ args: ChannelHandlerArgs;
1058
+ }): Promise<Response> {
1059
+ if (input.options.onCheckRun === undefined) {
1060
+ return skipped("no_handler", { event: "check_run" });
1061
+ }
1062
+ const parsed = parseCheckRunEvent({
1063
+ payload: input.payload,
1064
+ deliveryId: input.deliveryId,
1065
+ });
1066
+ if (parsed === null) {
1067
+ return Response.json(
1068
+ { ok: false, error: "invalid_check_run_payload" },
1069
+ { status: 400 }
1070
+ );
1071
+ }
1072
+ const installationId =
1073
+ readInstallationId(input.payload) ?? parsed.unanchoredCtx.installationId;
1074
+ if (parsed.ctx === null) {
1075
+ const dispatch = await input.options.onCheckRun(
1076
+ enrichCiContext({
1077
+ bindingConfig: input.bindingConfig,
1078
+ ctx: parsed.unanchoredCtx,
1079
+ installationId,
1080
+ host: input.args.host,
1081
+ }),
1082
+ parsed.checkRun
1083
+ );
1084
+ if (dispatch !== null && isTaskDispatch(dispatch)) {
1085
+ return acknowledgeTask(input.args, dispatch, {
1086
+ event: "check_run",
1087
+ action: parsed.checkRun.action,
1088
+ checkRunId: parsed.checkRun.checkRunId,
1089
+ });
1090
+ }
1091
+ return skipped("no_pull_request", { event: "check_run" });
1092
+ }
1093
+ const binding = githubBindingStateForEvent({
1094
+ kind: parsed.ctx.conversation.kind,
1095
+ owner: parsed.ctx.repository.owner,
1096
+ repo: parsed.ctx.repository.name,
1097
+ number: parsed.ctx.conversation.number,
1098
+ repositoryId: parsed.ctx.repository.id,
1099
+ installationId,
1100
+ });
1101
+ const ctx = enrichEventContext({
1102
+ bindingConfig: input.bindingConfig,
1103
+ ctx: parsed.ctx,
1104
+ bindingState: binding,
1105
+ installationId,
1106
+ host: input.args.host,
1107
+ });
1108
+ const dispatch = await input.options.onCheckRun(ctx, parsed.checkRun);
1109
+ if (dispatch === null) {
1110
+ return skipped("not_dispatched", { action: parsed.checkRun.action });
1111
+ }
1112
+ if (isTaskDispatch(dispatch)) {
1113
+ return acknowledgeTask(input.args, dispatch, {
1114
+ event: "check_run",
1115
+ action: parsed.checkRun.action,
1116
+ checkRunId: parsed.checkRun.checkRunId,
1117
+ });
1118
+ }
1119
+ const message = [
1120
+ dispatch.message ??
1121
+ formatCiMessage("Check run", parsed.checkRun.checkRunId, parsed.checkRun),
1122
+ ...(dispatch.context ?? []),
1123
+ ].join("\n");
1124
+ return startTurn({
1125
+ args: input.args,
1126
+ ctx,
1127
+ message,
1128
+ dispatch,
1129
+ state: sessionStateFromBinding({
1130
+ binding,
1131
+ deliveryId: ctx.deliveryId,
1132
+ eventName: ctx.eventName,
1133
+ headSha: parsed.checkRun.headSha,
1134
+ }),
1135
+ });
1136
+ }
1137
+
1138
+ async function handleWorkflowRun(input: {
1139
+ options: GitHubChannelOptions;
1140
+ bindingConfig: GitHubBindingConfig;
1141
+ payload: unknown;
1142
+ deliveryId?: string;
1143
+ args: ChannelHandlerArgs;
1144
+ }): Promise<Response> {
1145
+ if (input.options.onWorkflowRun === undefined) {
1146
+ return skipped("no_handler", { event: "workflow_run" });
1147
+ }
1148
+ const parsed = parseWorkflowRunEvent({
1149
+ payload: input.payload,
1150
+ deliveryId: input.deliveryId,
1151
+ });
1152
+ if (parsed === null) {
1153
+ return Response.json(
1154
+ { ok: false, error: "invalid_workflow_run_payload" },
1155
+ { status: 400 }
1156
+ );
1157
+ }
1158
+ const installationId =
1159
+ readInstallationId(input.payload) ?? parsed.unanchoredCtx.installationId;
1160
+ if (parsed.ctx === null) {
1161
+ const dispatch = await input.options.onWorkflowRun(
1162
+ enrichCiContext({
1163
+ bindingConfig: input.bindingConfig,
1164
+ ctx: parsed.unanchoredCtx,
1165
+ installationId,
1166
+ host: input.args.host,
1167
+ }),
1168
+ parsed.workflowRun
1169
+ );
1170
+ if (dispatch !== null && isTaskDispatch(dispatch)) {
1171
+ return acknowledgeTask(input.args, dispatch, {
1172
+ event: "workflow_run",
1173
+ action: parsed.workflowRun.action,
1174
+ workflowRunId: parsed.workflowRun.workflowRunId,
1175
+ });
1176
+ }
1177
+ return skipped("no_pull_request", { event: "workflow_run" });
1178
+ }
1179
+ const binding = githubBindingStateForEvent({
1180
+ kind: parsed.ctx.conversation.kind,
1181
+ owner: parsed.ctx.repository.owner,
1182
+ repo: parsed.ctx.repository.name,
1183
+ number: parsed.ctx.conversation.number,
1184
+ repositoryId: parsed.ctx.repository.id,
1185
+ installationId,
1186
+ });
1187
+ const ctx = enrichEventContext({
1188
+ bindingConfig: input.bindingConfig,
1189
+ ctx: parsed.ctx,
1190
+ bindingState: binding,
1191
+ installationId,
1192
+ host: input.args.host,
1193
+ });
1194
+ const dispatch = await input.options.onWorkflowRun(ctx, parsed.workflowRun);
1195
+ if (dispatch === null) {
1196
+ return skipped("not_dispatched", { action: parsed.workflowRun.action });
1197
+ }
1198
+ if (isTaskDispatch(dispatch)) {
1199
+ return acknowledgeTask(input.args, dispatch, {
1200
+ event: "workflow_run",
1201
+ action: parsed.workflowRun.action,
1202
+ workflowRunId: parsed.workflowRun.workflowRunId,
1203
+ });
1204
+ }
1205
+ const message = [
1206
+ dispatch.message ??
1207
+ formatCiMessage(
1208
+ "Workflow run",
1209
+ parsed.workflowRun.workflowRunId,
1210
+ parsed.workflowRun
1211
+ ),
1212
+ ...(dispatch.context ?? []),
1213
+ ].join("\n");
1214
+ return startTurn({
1215
+ args: input.args,
1216
+ ctx,
1217
+ message,
1218
+ dispatch,
1219
+ state: sessionStateFromBinding({
1220
+ binding,
1221
+ deliveryId: ctx.deliveryId,
1222
+ eventName: ctx.eventName,
1223
+ headSha: parsed.workflowRun.headSha,
1224
+ }),
1225
+ });
1226
+ }
1227
+
1228
+ async function handleStatus(input: {
1229
+ options: GitHubChannelOptions;
1230
+ bindingConfig: GitHubBindingConfig;
1231
+ payload: unknown;
1232
+ deliveryId?: string;
1233
+ args: ChannelHandlerArgs;
1234
+ }): Promise<Response> {
1235
+ if (input.options.onStatus === undefined) {
1236
+ return skipped("no_handler", { event: "status" });
1237
+ }
1238
+ const parsed = parseStatusEvent({
1239
+ payload: input.payload,
1240
+ deliveryId: input.deliveryId,
1241
+ });
1242
+ if (parsed === null) {
1243
+ return Response.json(
1244
+ { ok: false, error: "invalid_status_payload" },
1245
+ { status: 400 }
1246
+ );
1247
+ }
1248
+ const installationId =
1249
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
1250
+ const binding = githubBindingStateForEvent({
1251
+ kind: parsed.ctx.conversation.kind,
1252
+ owner: parsed.ctx.repository.owner,
1253
+ repo: parsed.ctx.repository.name,
1254
+ number: parsed.ctx.conversation.number,
1255
+ repositoryId: parsed.ctx.repository.id,
1256
+ installationId,
1257
+ });
1258
+ const ctx = enrichEventContext({
1259
+ bindingConfig: input.bindingConfig,
1260
+ ctx: parsed.ctx,
1261
+ bindingState: binding,
1262
+ installationId,
1263
+ host: input.args.host,
1264
+ });
1265
+ const dispatch = await input.options.onStatus(ctx, parsed.status);
1266
+ if (dispatch === null) {
1267
+ return skipped("not_dispatched", {
1268
+ event: "status",
1269
+ state: parsed.status.state,
1270
+ });
1271
+ }
1272
+ if (isTaskDispatch(dispatch)) {
1273
+ return acknowledgeTask(input.args, dispatch, {
1274
+ event: "status",
1275
+ state: parsed.status.state,
1276
+ sha: parsed.status.sha,
1277
+ });
1278
+ }
1279
+ // A turn needs a thread to anchor to; commit statuses often have no PR.
1280
+ if (ctx.conversation.number === 0) {
1281
+ return skipped("no_pull_request", { event: "status" });
1282
+ }
1283
+ const message = [
1284
+ dispatch.message ??
1285
+ `Commit status ${parsed.status.state} for ${parsed.status.context} at ${parsed.status.sha}`,
1286
+ ...(dispatch.context ?? []),
1287
+ ].join("\n");
1288
+ return startTurn({
1289
+ args: input.args,
1290
+ ctx,
1291
+ message,
1292
+ dispatch,
1293
+ state: sessionStateFromBinding({
1294
+ binding,
1295
+ deliveryId: ctx.deliveryId,
1296
+ eventName: ctx.eventName,
1297
+ headSha: parsed.status.sha,
1298
+ }),
1299
+ });
1300
+ }
1301
+
1302
+ async function handleGenericEvent(input: {
1303
+ options: GitHubChannelOptions;
1304
+ bindingConfig: GitHubBindingConfig;
1305
+ eventName: string;
1306
+ payload: unknown;
1307
+ deliveryId?: string;
1308
+ args: ChannelHandlerArgs;
1309
+ }): Promise<Response> {
1310
+ if (input.options.onEvent === undefined) {
1311
+ return skipped("no_handler", { event: input.eventName });
1312
+ }
1313
+ const parsed = parseGenericEvent({
1314
+ eventName: input.eventName,
1315
+ payload: input.payload,
1316
+ deliveryId: input.deliveryId,
1317
+ });
1318
+ if (parsed === null) {
1319
+ return skipped("unsupported_event", { event: input.eventName });
1320
+ }
1321
+ const installationId =
1322
+ readInstallationId(input.payload) ?? parsed.ctx.installationId;
1323
+ const binding = githubBindingStateForEvent({
1324
+ kind: parsed.ctx.conversation.kind,
1325
+ owner: parsed.ctx.repository.owner,
1326
+ repo: parsed.ctx.repository.name,
1327
+ number: parsed.ctx.conversation.number,
1328
+ repositoryId: parsed.ctx.repository.id,
1329
+ installationId,
1330
+ });
1331
+ const ctx = enrichEventContext({
1332
+ bindingConfig: input.bindingConfig,
1333
+ ctx: parsed.ctx,
1334
+ bindingState: binding,
1335
+ installationId,
1336
+ host: input.args.host,
1337
+ });
1338
+ const dispatch = await input.options.onEvent(ctx, parsed.event);
1339
+ if (dispatch === null) {
1340
+ return skipped("not_dispatched", { event: input.eventName });
1341
+ }
1342
+ if (isTaskDispatch(dispatch)) {
1343
+ return acknowledgeTask(input.args, dispatch, {
1344
+ event: input.eventName,
1345
+ ...(parsed.event.action === undefined
1346
+ ? {}
1347
+ : { action: parsed.event.action }),
1348
+ });
1349
+ }
1350
+ if (ctx.conversation.number === 0) {
1351
+ return skipped("no_conversation", { event: input.eventName });
1352
+ }
1353
+ const message = [
1354
+ dispatch.message ?? `GitHub ${input.eventName} event`,
1355
+ ...(dispatch.context ?? []),
1356
+ ].join("\n");
1357
+ return startTurn({
1358
+ args: input.args,
1359
+ ctx,
1360
+ message,
1361
+ dispatch,
1362
+ state: sessionStateFromBinding({
1363
+ binding,
1364
+ deliveryId: ctx.deliveryId,
1365
+ eventName: ctx.eventName,
1366
+ }),
1367
+ });
1368
+ }
1369
+
1370
+ async function startTurn(input: {
1371
+ args: ChannelHandlerArgs;
1372
+ ctx: GitHubEventContext;
1373
+ message: string;
1374
+ dispatch: GitHubTurnDispatch;
1375
+ cloud?: AgentCloudOptions;
1376
+ state: GitHubChannelState;
1377
+ }): Promise<Response> {
1378
+ const session = await input.args.send(input.message, {
1379
+ auth: input.dispatch.auth,
1380
+ continuationToken: githubContinuationKey(input.ctx),
1381
+ title: input.dispatch.title ?? defaultGitHubTitle(input.ctx),
1382
+ ...(input.cloud === undefined ? {} : { cloud: input.cloud }),
1383
+ state: input.state as unknown as JsonValue,
1384
+ });
1385
+ return Response.json({
1386
+ ok: true,
1387
+ sessionId: session.id,
1388
+ isNew: session.isNew,
1389
+ continuationToken: session.continuationToken,
1390
+ });
1391
+ }
1392
+
1393
+ function acknowledgeTask(
1394
+ args: ChannelHandlerArgs,
1395
+ dispatch: GitHubTaskDispatch,
1396
+ extra: Record<string, unknown>
1397
+ ): Response {
1398
+ // Defer the task past the current turn so the 202 can flush before work
1399
+ // starts (and so tests can assert scheduling without racing microtasks).
1400
+ args.waitUntil(
1401
+ new Promise<void>((resolve, reject) => {
1402
+ setImmediate(() => {
1403
+ Promise.resolve()
1404
+ .then(dispatch.task)
1405
+ .then(() => resolve(), reject);
1406
+ });
1407
+ })
1408
+ );
1409
+ return Response.json(
1410
+ { ok: true, dispatched: true, mode: "task", ...extra },
1411
+ { status: 202 }
1412
+ );
1413
+ }
1414
+
1415
+ function skipped(
1416
+ reason: string,
1417
+ extra: Record<string, unknown> = {}
1418
+ ): Response {
1419
+ return Response.json({ ok: true, skipped: true, reason, ...extra });
1420
+ }
1421
+
1422
+ function formatCiMessage(
1423
+ label: string,
1424
+ id: number,
1425
+ event: { action: string; status?: string; conclusion?: string | null }
1426
+ ): string {
1427
+ const outcome = event.conclusion ?? event.status;
1428
+ return `${label} ${event.action}: ${id}${outcome ? ` (${outcome})` : ""}`;
1429
+ }
1430
+
1431
+ function isIgnoredInboundComment(
1432
+ body: string,
1433
+ author: GitHubUser | undefined,
1434
+ botName: string | undefined
1435
+ ): boolean {
1436
+ if (body.includes("<!-- agent-serve-github -->")) {
1437
+ return true;
1438
+ }
1439
+ if (isGitHubAppBotAuthor(author, botName)) {
1440
+ return true;
1441
+ }
1442
+ return false;
1443
+ }
1444
+
1445
+ /** Match GitHub App bot actors (`slug` config vs `slug[bot]` login). */
1446
+ export function isGitHubAppBotAuthor(
1447
+ author: GitHubUser | undefined,
1448
+ botName: string | undefined
1449
+ ): boolean {
1450
+ const normalizedBot = botName?.trim();
1451
+ if (normalizedBot === undefined || normalizedBot === "") {
1452
+ return false;
1453
+ }
1454
+ if (author?.type !== "Bot") {
1455
+ return false;
1456
+ }
1457
+ const login = author.login;
1458
+ const stripBotSuffix = (value: string): string =>
1459
+ value.replace(/\[bot\]$/i, "").toLowerCase();
1460
+ return (
1461
+ login.toLowerCase() === normalizedBot.toLowerCase() ||
1462
+ stripBotSuffix(login) === stripBotSuffix(normalizedBot)
1463
+ );
1464
+ }
1465
+
1466
+ function parseIssueCommentEvent(args: {
1467
+ payload: unknown;
1468
+ deliveryId?: string;
1469
+ }): {
1470
+ action: string;
1471
+ ctx: GitHubParsedContext;
1472
+ comment: GitHubComment;
1473
+ } | null {
1474
+ const schema = z.object({
1475
+ action: z.string().min(1),
1476
+ repository: repositorySchema,
1477
+ issue: z.object({
1478
+ number: z.number().int().positive(),
1479
+ html_url: z.string().url(),
1480
+ pull_request: z.unknown().optional(),
1481
+ }),
1482
+ comment: z.object({
1483
+ id: z.number(),
1484
+ body: z.string(),
1485
+ html_url: z.string().url(),
1486
+ url: z.string().url().optional(),
1487
+ user: z
1488
+ .object({
1489
+ login: z.string().optional(),
1490
+ id: z.number().optional(),
1491
+ type: z.string().optional(),
1492
+ })
1493
+ .nullable()
1494
+ .optional(),
1495
+ }),
1496
+ sender: senderSchema,
1497
+ installation: installationSchema,
1498
+ });
1499
+ const parsed = schema.safeParse(args.payload);
1500
+ if (!parsed.success) return null;
1501
+ const payload = parsed.data;
1502
+ const kind: GitHubConversationKind =
1503
+ payload.issue.pull_request === undefined ? "issue" : "pull_request";
1504
+ const repository = toRepositoryRef(payload.repository);
1505
+ const sender = toUser(payload.sender);
1506
+ const author = toUser(payload.comment.user);
1507
+ const installationId = payload.installation?.id;
1508
+ const ctx: GitHubParsedContext = {
1509
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
1510
+ eventName: "issue_comment",
1511
+ repository,
1512
+ conversation: {
1513
+ kind,
1514
+ number: payload.issue.number,
1515
+ url: payload.issue.html_url,
1516
+ },
1517
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
1518
+ ...(installationId === undefined ? {} : { installationId }),
1519
+ };
1520
+ const comment: GitHubComment = {
1521
+ id: payload.comment.id,
1522
+ body: payload.comment.body,
1523
+ htmlUrl: payload.comment.html_url,
1524
+ url: payload.comment.url ?? payload.comment.html_url,
1525
+ ...(author === undefined ? {} : { author }),
1526
+ };
1527
+ return { action: payload.action, ctx, comment };
1528
+ }
1529
+
1530
+ function parseReviewCommentEvent(args: {
1531
+ payload: unknown;
1532
+ deliveryId?: string;
1533
+ }): {
1534
+ action: string;
1535
+ ctx: GitHubParsedContext;
1536
+ comment: GitHubComment;
1537
+ } | null {
1538
+ const schema = z.object({
1539
+ action: z.string().min(1),
1540
+ repository: repositorySchema,
1541
+ pull_request: z.object({
1542
+ number: z.number().int().positive(),
1543
+ html_url: z.string().url(),
1544
+ }),
1545
+ comment: z.object({
1546
+ id: z.number(),
1547
+ body: z.string(),
1548
+ html_url: z.string().url(),
1549
+ url: z.string().url().optional(),
1550
+ path: z.string().optional(),
1551
+ line: z.number().nullable().optional(),
1552
+ pull_request_review_id: z.number().nullable().optional(),
1553
+ in_reply_to_id: z.number().optional(),
1554
+ user: z
1555
+ .object({
1556
+ login: z.string().optional(),
1557
+ id: z.number().optional(),
1558
+ type: z.string().optional(),
1559
+ })
1560
+ .nullable()
1561
+ .optional(),
1562
+ }),
1563
+ sender: senderSchema,
1564
+ installation: installationSchema,
1565
+ });
1566
+ const parsed = schema.safeParse(args.payload);
1567
+ if (!parsed.success) return null;
1568
+ const payload = parsed.data;
1569
+ const repository = toRepositoryRef(payload.repository);
1570
+ const sender = toUser(payload.sender);
1571
+ const author = toUser(payload.comment.user);
1572
+ const installationId = payload.installation?.id;
1573
+ const ctx: GitHubParsedContext = {
1574
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
1575
+ eventName: "pull_request_review_comment",
1576
+ repository,
1577
+ conversation: {
1578
+ kind: "review_thread",
1579
+ number: payload.pull_request.number,
1580
+ url: payload.pull_request.html_url,
1581
+ },
1582
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
1583
+ ...(installationId === undefined ? {} : { installationId }),
1584
+ };
1585
+ const comment: GitHubComment = {
1586
+ id: payload.comment.id,
1587
+ body: payload.comment.body,
1588
+ htmlUrl: payload.comment.html_url,
1589
+ url: payload.comment.url ?? payload.comment.html_url,
1590
+ ...(author === undefined ? {} : { author }),
1591
+ ...(payload.comment.path === undefined
1592
+ ? {}
1593
+ : { path: payload.comment.path }),
1594
+ ...(payload.comment.line === undefined
1595
+ ? {}
1596
+ : { line: payload.comment.line }),
1597
+ ...(payload.comment.pull_request_review_id === undefined
1598
+ ? {}
1599
+ : { pullRequestReviewId: payload.comment.pull_request_review_id }),
1600
+ ...(payload.comment.in_reply_to_id === undefined
1601
+ ? {}
1602
+ : { inReplyToId: payload.comment.in_reply_to_id }),
1603
+ };
1604
+ return { action: payload.action, ctx, comment };
1605
+ }
1606
+
1607
+ function parseIssueEvent(args: {
1608
+ payload: unknown;
1609
+ deliveryId?: string;
1610
+ }): { ctx: GitHubParsedContext; issue: GitHubIssueEvent } | null {
1611
+ const schema = z.object({
1612
+ action: z.string().min(1),
1613
+ repository: repositorySchema,
1614
+ issue: z.object({
1615
+ number: z.number().int().positive(),
1616
+ html_url: z.string().url(),
1617
+ title: z.string(),
1618
+ body: z.string().nullable().optional(),
1619
+ user: z
1620
+ .object({
1621
+ login: z.string().optional(),
1622
+ })
1623
+ .nullable()
1624
+ .optional(),
1625
+ }),
1626
+ sender: senderSchema,
1627
+ installation: installationSchema,
1628
+ });
1629
+ const parsed = schema.safeParse(args.payload);
1630
+ if (!parsed.success) return null;
1631
+ const payload = parsed.data;
1632
+ const repository = toRepositoryRef(payload.repository);
1633
+ const sender = toUser(payload.sender);
1634
+ const installationId = payload.installation?.id;
1635
+ const ctx: GitHubParsedContext = {
1636
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
1637
+ eventName: "issues",
1638
+ repository,
1639
+ conversation: {
1640
+ kind: "issue",
1641
+ number: payload.issue.number,
1642
+ url: payload.issue.html_url,
1643
+ },
1644
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
1645
+ ...(installationId === undefined ? {} : { installationId }),
1646
+ };
1647
+ const issue: GitHubIssueEvent = {
1648
+ action: payload.action,
1649
+ number: payload.issue.number,
1650
+ url: payload.issue.html_url,
1651
+ title: payload.issue.title,
1652
+ ...(payload.issue.body === undefined || payload.issue.body === null
1653
+ ? {}
1654
+ : { body: payload.issue.body }),
1655
+ ...(payload.issue.user?.login === undefined
1656
+ ? {}
1657
+ : { authorLogin: payload.issue.user.login }),
1658
+ };
1659
+ return { ctx, issue };
1660
+ }
1661
+
1662
+ function parseCheckSuiteEvent(args: {
1663
+ payload: unknown;
1664
+ deliveryId?: string;
1665
+ }): {
1666
+ ctx: GitHubParsedContext | null;
1667
+ unanchoredCtx: GitHubParsedContext;
1668
+ checkSuite: GitHubCheckSuiteEvent;
1669
+ } | null {
1670
+ const schema = z.object({
1671
+ action: z.string().min(1),
1672
+ repository: repositorySchema,
1673
+ check_suite: z.object({
1674
+ id: z.number(),
1675
+ head_sha: z.string().min(1),
1676
+ status: z.string().optional(),
1677
+ conclusion: z.string().nullable().optional(),
1678
+ app: z
1679
+ .object({
1680
+ slug: z.string().optional(),
1681
+ name: z.string().optional(),
1682
+ })
1683
+ .nullable()
1684
+ .optional(),
1685
+ pull_requests: z
1686
+ .array(
1687
+ z.object({
1688
+ number: z.number(),
1689
+ url: z.string().optional(),
1690
+ html_url: z.string().optional(),
1691
+ })
1692
+ )
1693
+ .optional(),
1694
+ }),
1695
+ sender: senderSchema,
1696
+ installation: installationSchema,
1697
+ });
1698
+ const parsed = schema.safeParse(args.payload);
1699
+ if (!parsed.success) return null;
1700
+ const payload = parsed.data;
1701
+ const repository = toRepositoryRef(payload.repository);
1702
+ const sender = toUser(payload.sender);
1703
+ const installationId = payload.installation?.id;
1704
+ const pullRequests = toCiPullRequests(payload.check_suite.pull_requests);
1705
+ const checkSuite: GitHubCheckSuiteEvent = {
1706
+ action: payload.action,
1707
+ checkSuiteId: payload.check_suite.id,
1708
+ headSha: payload.check_suite.head_sha,
1709
+ ...(payload.check_suite.status === undefined
1710
+ ? {}
1711
+ : { status: payload.check_suite.status }),
1712
+ conclusion: (payload.check_suite.conclusion ??
1713
+ null) as GitHubCheckSuiteEvent["conclusion"],
1714
+ app: toCiApp(payload.check_suite.app),
1715
+ pullRequests,
1716
+ };
1717
+ const unanchoredCtx = unanchoredEventContext({
1718
+ deliveryId: args.deliveryId,
1719
+ eventName: "check_suite",
1720
+ repository,
1721
+ sender,
1722
+ installationId,
1723
+ });
1724
+ const first = pullRequests[0];
1725
+ if (first === undefined) {
1726
+ return { ctx: null, unanchoredCtx, checkSuite };
1727
+ }
1728
+ return {
1729
+ ctx: {
1730
+ ...unanchoredCtx,
1731
+ conversation: {
1732
+ kind: "pull_request",
1733
+ number: first.number,
1734
+ url: first.url ?? `${repository.url}/pull/${first.number}`,
1735
+ },
1736
+ },
1737
+ unanchoredCtx,
1738
+ checkSuite,
1739
+ };
1740
+ }
1741
+
1742
+ function parseCheckRunEvent(args: { payload: unknown; deliveryId?: string }): {
1743
+ ctx: GitHubParsedContext | null;
1744
+ unanchoredCtx: GitHubParsedContext;
1745
+ checkRun: GitHubCheckRunEvent;
1746
+ } | null {
1747
+ const schema = z.object({
1748
+ action: z.string().min(1),
1749
+ repository: repositorySchema,
1750
+ check_run: z.object({
1751
+ id: z.number(),
1752
+ name: z.string().optional(),
1753
+ head_sha: z.string().min(1),
1754
+ status: z.string().optional(),
1755
+ conclusion: z.string().nullable().optional(),
1756
+ html_url: z.string().optional(),
1757
+ check_suite: z
1758
+ .object({
1759
+ id: z.number().optional(),
1760
+ })
1761
+ .optional(),
1762
+ app: z
1763
+ .object({
1764
+ slug: z.string().optional(),
1765
+ name: z.string().optional(),
1766
+ })
1767
+ .nullable()
1768
+ .optional(),
1769
+ pull_requests: z
1770
+ .array(
1771
+ z.object({
1772
+ number: z.number(),
1773
+ url: z.string().optional(),
1774
+ html_url: z.string().optional(),
1775
+ })
1776
+ )
1777
+ .optional(),
1778
+ }),
1779
+ sender: senderSchema,
1780
+ installation: installationSchema,
1781
+ });
1782
+ const parsed = schema.safeParse(args.payload);
1783
+ if (!parsed.success) return null;
1784
+ const payload = parsed.data;
1785
+ const repository = toRepositoryRef(payload.repository);
1786
+ const sender = toUser(payload.sender);
1787
+ const installationId = payload.installation?.id;
1788
+ const pullRequests = toCiPullRequests(payload.check_run.pull_requests);
1789
+ const checkRun: GitHubCheckRunEvent = {
1790
+ action: payload.action,
1791
+ checkRunId: payload.check_run.id,
1792
+ headSha: payload.check_run.head_sha,
1793
+ ...(payload.check_run.name === undefined
1794
+ ? {}
1795
+ : { name: payload.check_run.name }),
1796
+ ...(payload.check_run.check_suite?.id === undefined
1797
+ ? {}
1798
+ : { checkSuiteId: payload.check_run.check_suite.id }),
1799
+ ...(payload.check_run.status === undefined
1800
+ ? {}
1801
+ : { status: payload.check_run.status }),
1802
+ conclusion: (payload.check_run.conclusion ??
1803
+ null) as GitHubCheckRunEvent["conclusion"],
1804
+ app: toCiApp(payload.check_run.app),
1805
+ pullRequests,
1806
+ };
1807
+ const unanchoredCtx = unanchoredEventContext({
1808
+ deliveryId: args.deliveryId,
1809
+ eventName: "check_run",
1810
+ repository,
1811
+ sender,
1812
+ installationId,
1813
+ });
1814
+ const first = pullRequests[0];
1815
+ if (first === undefined) {
1816
+ return { ctx: null, unanchoredCtx, checkRun };
1817
+ }
1818
+ return {
1819
+ ctx: {
1820
+ ...unanchoredCtx,
1821
+ conversation: {
1822
+ kind: "pull_request",
1823
+ number: first.number,
1824
+ url: first.url ?? `${repository.url}/pull/${first.number}`,
1825
+ },
1826
+ },
1827
+ unanchoredCtx,
1828
+ checkRun,
1829
+ };
1830
+ }
1831
+
1832
+ function parseWorkflowRunEvent(args: {
1833
+ payload: unknown;
1834
+ deliveryId?: string;
1835
+ }): {
1836
+ ctx: GitHubParsedContext | null;
1837
+ unanchoredCtx: GitHubParsedContext;
1838
+ workflowRun: GitHubWorkflowRunEvent;
1839
+ } | null {
1840
+ const schema = z.object({
1841
+ action: z.string().min(1),
1842
+ repository: repositorySchema,
1843
+ workflow_run: z.object({
1844
+ id: z.number(),
1845
+ name: z.string().nullable().optional(),
1846
+ workflow_id: z.number().optional(),
1847
+ head_sha: z.string().min(1),
1848
+ status: z.string().optional(),
1849
+ conclusion: z.string().nullable().optional(),
1850
+ html_url: z.string().optional(),
1851
+ pull_requests: z
1852
+ .array(
1853
+ z.object({
1854
+ number: z.number(),
1855
+ url: z.string().optional(),
1856
+ html_url: z.string().optional(),
1857
+ })
1858
+ )
1859
+ .optional(),
1860
+ }),
1861
+ sender: senderSchema,
1862
+ installation: installationSchema,
1863
+ });
1864
+ const parsed = schema.safeParse(args.payload);
1865
+ if (!parsed.success) return null;
1866
+ const payload = parsed.data;
1867
+ const repository = toRepositoryRef(payload.repository);
1868
+ const sender = toUser(payload.sender);
1869
+ const installationId = payload.installation?.id;
1870
+ const pullRequests = toCiPullRequests(payload.workflow_run.pull_requests);
1871
+ const workflowRun: GitHubWorkflowRunEvent = {
1872
+ action: payload.action,
1873
+ workflowRunId: payload.workflow_run.id,
1874
+ headSha: payload.workflow_run.head_sha,
1875
+ ...(payload.workflow_run.name === undefined ||
1876
+ payload.workflow_run.name === null
1877
+ ? {}
1878
+ : { name: payload.workflow_run.name }),
1879
+ ...(payload.workflow_run.workflow_id === undefined
1880
+ ? {}
1881
+ : { workflowId: payload.workflow_run.workflow_id }),
1882
+ ...(payload.workflow_run.html_url === undefined
1883
+ ? {}
1884
+ : { htmlUrl: payload.workflow_run.html_url }),
1885
+ ...(payload.workflow_run.status === undefined
1886
+ ? {}
1887
+ : { status: payload.workflow_run.status }),
1888
+ conclusion: (payload.workflow_run.conclusion ??
1889
+ null) as GitHubWorkflowRunEvent["conclusion"],
1890
+ // workflow_run is GitHub Actions-only (Eve semantics).
1891
+ app: { slug: "github-actions", name: "GitHub Actions" },
1892
+ pullRequests,
1893
+ };
1894
+ const unanchoredCtx = unanchoredEventContext({
1895
+ deliveryId: args.deliveryId,
1896
+ eventName: "workflow_run",
1897
+ repository,
1898
+ sender,
1899
+ installationId,
1900
+ });
1901
+ const first = pullRequests[0];
1902
+ if (first === undefined) {
1903
+ return { ctx: null, unanchoredCtx, workflowRun };
1904
+ }
1905
+ return {
1906
+ ctx: {
1907
+ ...unanchoredCtx,
1908
+ conversation: {
1909
+ kind: "pull_request",
1910
+ number: first.number,
1911
+ url: first.url ?? `${repository.url}/pull/${first.number}`,
1912
+ },
1913
+ },
1914
+ unanchoredCtx,
1915
+ workflowRun,
1916
+ };
1917
+ }
1918
+
1919
+ function parseStatusEvent(args: {
1920
+ payload: unknown;
1921
+ deliveryId?: string;
1922
+ }): { ctx: GitHubParsedContext; status: GitHubStatusEvent } | null {
1923
+ const schema = z.object({
1924
+ state: z.string().min(1),
1925
+ sha: z.string().min(1),
1926
+ context: z.string().optional(),
1927
+ description: z.string().nullable().optional(),
1928
+ target_url: z.string().nullable().optional(),
1929
+ name: z.string().optional(),
1930
+ branches: z
1931
+ .array(
1932
+ z.object({
1933
+ name: z.string(),
1934
+ commit: z.object({ sha: z.string().optional() }).optional(),
1935
+ })
1936
+ )
1937
+ .optional(),
1938
+ commit: z
1939
+ .object({
1940
+ sha: z.string().optional(),
1941
+ html_url: z.string().optional(),
1942
+ })
1943
+ .optional(),
1944
+ repository: repositorySchema,
1945
+ sender: senderSchema,
1946
+ installation: installationSchema,
1947
+ pull_requests: z
1948
+ .array(
1949
+ z.object({
1950
+ number: z.number(),
1951
+ url: z.string().optional(),
1952
+ html_url: z.string().optional(),
1953
+ })
1954
+ )
1955
+ .optional(),
1956
+ });
1957
+ const parsed = schema.safeParse(args.payload);
1958
+ if (!parsed.success) return null;
1959
+ const payload = parsed.data;
1960
+ const repository = toRepositoryRef(payload.repository);
1961
+ const sender = toUser(payload.sender);
1962
+ const installationId = payload.installation?.id;
1963
+ const pullRequests = toCiPullRequests(payload.pull_requests);
1964
+ const first = pullRequests[0];
1965
+ const status: GitHubStatusEvent = {
1966
+ state: payload.state,
1967
+ context: payload.context ?? "",
1968
+ ...(payload.description === undefined
1969
+ ? {}
1970
+ : { description: payload.description }),
1971
+ ...(payload.target_url === undefined
1972
+ ? {}
1973
+ : { targetUrl: payload.target_url }),
1974
+ sha: payload.sha,
1975
+ ...(payload.name === undefined ? {} : { name: payload.name }),
1976
+ branches: (payload.branches ?? []).map((branch) => ({
1977
+ name: branch.name,
1978
+ ...(branch.commit === undefined ? {} : { commit: branch.commit }),
1979
+ })),
1980
+ ...(payload.commit === undefined
1981
+ ? {}
1982
+ : {
1983
+ commit: {
1984
+ ...(payload.commit.sha === undefined
1985
+ ? {}
1986
+ : { sha: payload.commit.sha }),
1987
+ ...(payload.commit.html_url === undefined
1988
+ ? {}
1989
+ : { htmlUrl: payload.commit.html_url }),
1990
+ },
1991
+ }),
1992
+ raw: args.payload,
1993
+ };
1994
+ const ctx: GitHubParsedContext = {
1995
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
1996
+ eventName: "status",
1997
+ repository,
1998
+ conversation: {
1999
+ kind: "pull_request",
2000
+ number: first?.number ?? 0,
2001
+ url: first?.url ?? repository.url,
2002
+ },
2003
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
2004
+ ...(installationId === undefined ? {} : { installationId }),
2005
+ };
2006
+ return { ctx, status };
2007
+ }
2008
+
2009
+ function parseGenericEvent(args: {
2010
+ eventName: string;
2011
+ payload: unknown;
2012
+ deliveryId?: string;
2013
+ }): { ctx: GitHubParsedContext; event: GitHubRawEvent } | null {
2014
+ const schema = z.object({
2015
+ action: z.string().optional(),
2016
+ repository: repositorySchema.optional(),
2017
+ sender: senderSchema,
2018
+ installation: installationSchema,
2019
+ issue: z
2020
+ .object({
2021
+ number: z.number().int().positive(),
2022
+ html_url: z.string().url().optional(),
2023
+ pull_request: z.unknown().optional(),
2024
+ })
2025
+ .optional(),
2026
+ pull_request: z
2027
+ .object({
2028
+ number: z.number().int().positive(),
2029
+ html_url: z.string().url().optional(),
2030
+ })
2031
+ .optional(),
2032
+ });
2033
+ const parsed = schema.safeParse(args.payload);
2034
+ if (!parsed.success) {
2035
+ return null;
2036
+ }
2037
+ const payload = parsed.data;
2038
+ if (payload.repository === undefined) {
2039
+ return null;
2040
+ }
2041
+ const repository = toRepositoryRef(payload.repository);
2042
+ const sender = toUser(payload.sender);
2043
+ const installationId = payload.installation?.id;
2044
+ const kind: GitHubConversationKind =
2045
+ payload.issue?.pull_request !== undefined
2046
+ ? "pull_request"
2047
+ : payload.issue !== undefined
2048
+ ? "issue"
2049
+ : "pull_request";
2050
+ const number = payload.issue?.number ?? payload.pull_request?.number ?? 0;
2051
+ const url =
2052
+ payload.issue?.html_url ?? payload.pull_request?.html_url ?? repository.url;
2053
+ const event: GitHubRawEvent = {
2054
+ eventName: args.eventName,
2055
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
2056
+ ...(payload.action === undefined ? {} : { action: payload.action }),
2057
+ payload: args.payload,
2058
+ };
2059
+ const ctx: GitHubParsedContext = {
2060
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
2061
+ eventName: args.eventName,
2062
+ repository,
2063
+ conversation: { kind, number, url },
2064
+ ...(sender === undefined ? {} : { sender, senderLogin: sender.login }),
2065
+ ...(installationId === undefined ? {} : { installationId }),
2066
+ };
2067
+ return { ctx, event };
2068
+ }
2069
+
2070
+ function unanchoredEventContext(args: {
2071
+ deliveryId?: string;
2072
+ eventName: string;
2073
+ repository: GitHubRepositoryRef;
2074
+ sender?: GitHubUser;
2075
+ installationId?: number;
2076
+ }): GitHubParsedContext {
2077
+ return {
2078
+ ...(args.deliveryId === undefined ? {} : { deliveryId: args.deliveryId }),
2079
+ eventName: args.eventName,
2080
+ repository: args.repository,
2081
+ conversation: {
2082
+ kind: "pull_request",
2083
+ number: 0,
2084
+ url: args.repository.url,
2085
+ },
2086
+ ...(args.sender === undefined
2087
+ ? {}
2088
+ : { sender: args.sender, senderLogin: args.sender.login }),
2089
+ ...(args.installationId === undefined
2090
+ ? {}
2091
+ : { installationId: args.installationId }),
2092
+ };
2093
+ }
2094
+
2095
+ function toRepositoryRef(repo: {
2096
+ id?: number;
2097
+ full_name: string;
2098
+ html_url: string;
2099
+ name?: string;
2100
+ owner?: { login?: string };
2101
+ }): GitHubRepositoryRef {
2102
+ const [ownerFromName, nameFromFull] = repo.full_name.split("/");
2103
+ return {
2104
+ fullName: repo.full_name,
2105
+ url: repo.html_url,
2106
+ owner: repo.owner?.login ?? ownerFromName ?? "",
2107
+ name: repo.name ?? nameFromFull ?? repo.full_name,
2108
+ ...(repo.id === undefined ? {} : { id: repo.id }),
2109
+ };
2110
+ }
2111
+
2112
+ function toUser(
2113
+ raw:
2114
+ | {
2115
+ login?: string;
2116
+ id?: number;
2117
+ type?: string;
2118
+ }
2119
+ | null
2120
+ | undefined
2121
+ ): GitHubUser | undefined {
2122
+ if (raw?.login === undefined || raw.login === "") return undefined;
2123
+ return {
2124
+ login: raw.login,
2125
+ ...(raw.id === undefined ? {} : { id: raw.id }),
2126
+ ...(raw.type === undefined ? {} : { type: raw.type }),
2127
+ };
2128
+ }
2129
+
2130
+ function toCiPullRequests(
2131
+ raw: Array<{ number: number; url?: string; html_url?: string }> | undefined
2132
+ ): GitHubCiPullRequestRef[] {
2133
+ return (raw ?? []).map((pr) => ({
2134
+ number: pr.number,
2135
+ ...(pr.html_url !== undefined
2136
+ ? { url: pr.html_url }
2137
+ : pr.url !== undefined
2138
+ ? { url: pr.url }
2139
+ : {}),
2140
+ }));
2141
+ }
2142
+
2143
+ function toCiApp(
2144
+ raw: { slug?: string; name?: string } | null | undefined
2145
+ ): GitHubCiAppRef {
2146
+ return {
2147
+ ...(raw?.slug === undefined ? {} : { slug: raw.slug }),
2148
+ ...(raw?.name === undefined ? {} : { name: raw.name }),
2149
+ };
2150
+ }
2151
+
2152
+ function escapeRegExp(value: string): string {
2153
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2154
+ }
2155
+
2156
+ /**
2157
+ * Attach the GitHub API + thread handles to a parsed context, producing the
2158
+ * full {@link GitHubEventContext} handed to inbound hooks.
2159
+ */
2160
+ function enrichEventContext(input: {
2161
+ bindingConfig: GitHubBindingConfig;
2162
+ ctx: Omit<GitHubEventContext, "github" | "thread" | "host"> &
2163
+ Partial<Pick<GitHubEventContext, "github" | "thread">>;
2164
+ bindingState: GitHubBindingState;
2165
+ installationId?: number;
2166
+ host: HostContext;
2167
+ }): GitHubEventContext {
2168
+ const { github, thread } = buildGitHubBinding({
2169
+ config: input.bindingConfig,
2170
+ state: input.bindingState,
2171
+ });
2172
+ return {
2173
+ ...input.ctx,
2174
+ ...(input.installationId === undefined
2175
+ ? {}
2176
+ : { installationId: input.installationId }),
2177
+ github,
2178
+ thread,
2179
+ host: input.host,
2180
+ };
2181
+ }
2182
+
2183
+ /**
2184
+ * Enrich an unanchored CI context (`conversation.number === 0`). The binding
2185
+ * still resolves `github`, but `thread.post` throws until a PR is attached.
2186
+ */
2187
+ function enrichCiContext(input: {
2188
+ bindingConfig: GitHubBindingConfig;
2189
+ ctx: GitHubParsedContext;
2190
+ installationId?: number;
2191
+ host: HostContext;
2192
+ }): GitHubEventContext {
2193
+ const binding = githubBindingStateForEvent({
2194
+ kind: input.ctx.conversation.kind,
2195
+ owner: input.ctx.repository.owner,
2196
+ repo: input.ctx.repository.name,
2197
+ number: input.ctx.conversation.number,
2198
+ repositoryId: input.ctx.repository.id,
2199
+ installationId: input.installationId,
2200
+ });
2201
+ return enrichEventContext({
2202
+ bindingConfig: input.bindingConfig,
2203
+ ctx: input.ctx,
2204
+ bindingState: binding,
2205
+ installationId: input.installationId,
2206
+ host: input.host,
2207
+ });
2208
+ }
2209
+
2210
+ /** Build the durable per-session channel state stored with the turn. */
2211
+ function sessionStateFromBinding(input: {
2212
+ binding: GitHubBindingState;
2213
+ deliveryId?: string;
2214
+ eventName: string;
2215
+ headSha?: string;
2216
+ baseRef?: string;
2217
+ headRef?: string;
2218
+ }): GitHubChannelState {
2219
+ return emptyGitHubChannelState({
2220
+ ...input.binding,
2221
+ deliveryId: input.deliveryId ?? null,
2222
+ eventName: input.eventName,
2223
+ headSha: input.headSha ?? null,
2224
+ baseRef: input.baseRef ?? null,
2225
+ headRef: input.headRef ?? null,
2226
+ });
2227
+ }
2228
+
2229
+ /** Read `installation.id` from a raw webhook payload when present. */
2230
+ function readInstallationId(payload: unknown): number | undefined {
2231
+ if (typeof payload !== "object" || payload === null) return undefined;
2232
+ const installation = (payload as { installation?: { id?: number } })
2233
+ .installation;
2234
+ return typeof installation?.id === "number" ? installation.id : undefined;
2235
+ }
2236
+
2237
+ function envWebhookSecret(): GitHubCredentialValue | undefined {
2238
+ if (
2239
+ process.env.GITHUB_WEBHOOK_SECRET === undefined ||
2240
+ process.env.GITHUB_WEBHOOK_SECRET === ""
2241
+ ) {
2242
+ return undefined;
2243
+ }
2244
+ return () => process.env.GITHUB_WEBHOOK_SECRET;
2245
+ }
2246
+
2247
+ async function resolveCloudOptions(
2248
+ cloud: GitHubChannelOptions["cloud"],
2249
+ ctx: GitHubEventContext,
2250
+ pullRequest: GitHubPullRequestEvent
2251
+ ): Promise<AgentCloudOptions | undefined> {
2252
+ if (cloud === undefined || cloud === false) {
2253
+ return undefined;
2254
+ }
2255
+ if (cloud === true) {
2256
+ return {
2257
+ repos: [
2258
+ {
2259
+ url: ctx.repository.url,
2260
+ prUrl: pullRequest.url,
2261
+ startingRef: pullRequest.headSha,
2262
+ },
2263
+ ],
2264
+ };
2265
+ }
2266
+ if (typeof cloud === "function") {
2267
+ return await cloud(ctx, pullRequest);
2268
+ }
2269
+ return cloud;
2270
+ }