@cursor/july 0.1.55 → 0.1.61

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 (261) hide show
  1. package/dist/bin/agent-serve.js +30 -23
  2. package/dist/channels/github/github-channel.d.ts +12 -0
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +67 -15
  5. package/dist/channels/github/types.d.ts +27 -1
  6. package/dist/channels/github/types.d.ts.map +1 -1
  7. package/dist/channels/origin/cursor-account.d.ts +22 -0
  8. package/dist/channels/origin/cursor-account.d.ts.map +1 -0
  9. package/dist/channels/origin/cursor-account.js +35 -0
  10. package/dist/channels/origin/events.d.ts +10 -0
  11. package/dist/channels/origin/events.d.ts.map +1 -0
  12. package/dist/channels/origin/events.js +27 -0
  13. package/dist/channels/origin/index.d.ts +29 -0
  14. package/dist/channels/origin/index.d.ts.map +1 -0
  15. package/dist/channels/origin/index.js +28 -0
  16. package/dist/channels/origin/origin-channel.d.ts +68 -0
  17. package/dist/channels/origin/origin-channel.d.ts.map +1 -0
  18. package/dist/channels/origin/origin-channel.js +567 -0
  19. package/dist/channels/origin/origin-webhook.d.ts +38 -0
  20. package/dist/channels/origin/origin-webhook.d.ts.map +1 -0
  21. package/dist/channels/origin/origin-webhook.js +151 -0
  22. package/dist/channels/origin/types.d.ts +152 -0
  23. package/dist/channels/origin/types.d.ts.map +1 -0
  24. package/dist/channels/origin/types.js +1 -0
  25. package/dist/channels/slack/doctor.d.ts +17 -1
  26. package/dist/channels/slack/doctor.d.ts.map +1 -1
  27. package/dist/channels/slack/doctor.js +186 -10
  28. package/dist/docs/404.html +2 -2
  29. package/dist/docs/ab.html +4 -4
  30. package/dist/docs/assets/{app.CVQxhV85.js → app.CW3KJh72.js} +1 -1
  31. package/dist/docs/assets/chunks/@localSearchIndexroot.BuymfBAo.js +1 -0
  32. package/dist/docs/assets/chunks/{VPLocalSearchBox.QAPzJjql.js → VPLocalSearchBox.Bk6EWpPj.js} +1 -1
  33. package/dist/docs/assets/chunks/{theme.CSxTCOFu.js → theme.Bz9AodBA.js} +2 -2
  34. package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.js → example-agents_fsd.md.ZeGEpAw_.js} +2 -2
  35. package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.lean.js → example-agents_fsd.md.ZeGEpAw_.lean.js} +1 -1
  36. package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.js → example-agents_security-reviewer.md.DBL1TwtP.js} +2 -2
  37. package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.lean.js → example-agents_security-reviewer.md.DBL1TwtP.lean.js} +1 -1
  38. package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.js +12 -0
  39. package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.lean.js +1 -0
  40. package/dist/docs/assets/{guides_github.md.B8QKtIpw.js → guides_github.md.CLLQJKRB.js} +4 -4
  41. package/dist/docs/assets/{guides_github.md.B8QKtIpw.lean.js → guides_github.md.CLLQJKRB.lean.js} +1 -1
  42. package/dist/docs/assets/index.md.DhRHS_-L.js +5 -0
  43. package/dist/docs/assets/{index.md.COeoSkcF.lean.js → index.md.DhRHS_-L.lean.js} +1 -1
  44. package/dist/docs/assets/{reference_cli.md.D_CCfG8R.js → reference_cli.md.Cl14Jfo3.js} +8 -2
  45. package/dist/docs/assets/{reference_cli.md.D_CCfG8R.lean.js → reference_cli.md.Cl14Jfo3.lean.js} +1 -1
  46. package/dist/docs/assets/scaffolding-agents.md.CrWSA0Ko.js +1 -0
  47. package/dist/docs/assets/{scaffolding-agents.md.mkc3B_ZW.lean.js → scaffolding-agents.md.CrWSA0Ko.lean.js} +1 -1
  48. package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.js +14 -0
  49. package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.lean.js +1 -0
  50. package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.js +4 -0
  51. package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.lean.js +1 -0
  52. package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.js +8 -0
  53. package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.lean.js +1 -0
  54. package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.js +2 -0
  55. package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.lean.js +1 -0
  56. package/dist/docs/building-with-agents.html +4 -4
  57. package/dist/docs/concepts.html +5 -5
  58. package/dist/docs/deployment.html +4 -4
  59. package/dist/docs/evals.html +4 -4
  60. package/dist/docs/example-agents/approval-buddy.html +4 -4
  61. package/dist/docs/example-agents/benny.html +4 -4
  62. package/dist/docs/example-agents/bugbot.html +4 -4
  63. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  64. package/dist/docs/example-agents/codeowners-review.html +4 -4
  65. package/dist/docs/example-agents/concierge.html +4 -4
  66. package/dist/docs/example-agents/fsd.html +5 -5
  67. package/dist/docs/example-agents/index.html +4 -4
  68. package/dist/docs/example-agents/knowledge-base.html +4 -4
  69. package/dist/docs/example-agents/oncall.html +4 -4
  70. package/dist/docs/example-agents/security-reviewer.html +5 -5
  71. package/dist/docs/example-agents/slack-agent.html +4 -4
  72. package/dist/docs/example-agents/weather-agent.html +4 -4
  73. package/dist/docs/guides/agent-to-agent.html +4 -4
  74. package/dist/docs/guides/cloud-runtime.html +4 -4
  75. package/dist/docs/guides/convert-automation.html +36 -0
  76. package/dist/docs/guides/github.html +7 -7
  77. package/dist/docs/guides/human-in-the-loop.html +4 -4
  78. package/dist/docs/guides/mcp-oauth.html +4 -4
  79. package/dist/docs/guides/opentelemetry.html +4 -4
  80. package/dist/docs/guides/slack.html +4 -4
  81. package/dist/docs/guides/webhooks.html +4 -4
  82. package/dist/docs/hashmap.json +1 -1
  83. package/dist/docs/hillclimbing.html +4 -4
  84. package/dist/docs/index.html +6 -6
  85. package/dist/docs/quickstart.html +4 -4
  86. package/dist/docs/reference/agent-config.html +4 -4
  87. package/dist/docs/reference/artifacts.html +4 -4
  88. package/dist/docs/reference/channels.html +4 -4
  89. package/dist/docs/reference/cli.html +12 -6
  90. package/dist/docs/reference/connections.html +4 -4
  91. package/dist/docs/reference/hooks.html +4 -4
  92. package/dist/docs/reference/http-api.html +4 -4
  93. package/dist/docs/reference/instructions.html +4 -4
  94. package/dist/docs/reference/playground.html +4 -4
  95. package/dist/docs/reference/project-layout.html +4 -4
  96. package/dist/docs/reference/prompt.html +4 -4
  97. package/dist/docs/reference/schedules.html +4 -4
  98. package/dist/docs/reference/sessions.html +4 -4
  99. package/dist/docs/reference/skills.html +4 -4
  100. package/dist/docs/reference/subagents.html +4 -4
  101. package/dist/docs/reference/tools.html +4 -4
  102. package/dist/docs/scaffolding-agents.html +5 -5
  103. package/dist/docs/storage.html +4 -4
  104. package/dist/docs/templates/agentic-owners.html +38 -0
  105. package/dist/docs/templates/demo.html +28 -0
  106. package/dist/docs/templates/pr-autofixer.html +32 -0
  107. package/dist/docs/templates/security-reviewer.html +26 -0
  108. package/dist/docs/templates/triage.html +5 -5
  109. package/dist/docs/troubleshooting.html +4 -4
  110. package/dist/index.d.ts +3 -3
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -2
  113. package/dist/internal/cli-ax.d.ts +2 -0
  114. package/dist/internal/cli-ax.d.ts.map +1 -1
  115. package/dist/internal/cli-ax.js +20 -0
  116. package/dist/internal/cli-slack.d.ts +2 -0
  117. package/dist/internal/cli-slack.d.ts.map +1 -1
  118. package/dist/internal/cli-slack.js +11 -2
  119. package/dist/internal/cursor-event-relay.d.ts +59 -11
  120. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  121. package/dist/internal/cursor-event-relay.js +133 -26
  122. package/dist/internal/discovery.d.ts.map +1 -1
  123. package/dist/internal/discovery.js +11 -2
  124. package/dist/internal/distribution.d.ts.map +1 -1
  125. package/dist/internal/distribution.js +1 -0
  126. package/dist/internal/handleAgentServeTrigger.d.ts +33 -6
  127. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
  128. package/dist/internal/handleAgentServeTrigger.js +97 -19
  129. package/dist/internal/init-interview.d.ts +67 -0
  130. package/dist/internal/init-interview.d.ts.map +1 -0
  131. package/dist/internal/init-interview.js +300 -0
  132. package/dist/internal/init-project.d.ts +4 -0
  133. package/dist/internal/init-project.d.ts.map +1 -1
  134. package/dist/internal/init-project.js +40 -16
  135. package/dist/internal/mcp-endpoint.d.ts +7 -0
  136. package/dist/internal/mcp-endpoint.d.ts.map +1 -1
  137. package/dist/internal/mcp-endpoint.js +7 -0
  138. package/dist/internal/server.d.ts.map +1 -1
  139. package/dist/internal/server.js +159 -57
  140. package/dist/internal/session-engine.d.ts.map +1 -1
  141. package/dist/internal/session-engine.js +20 -21
  142. package/dist/internal/session-links.d.ts +14 -0
  143. package/dist/internal/session-links.d.ts.map +1 -0
  144. package/dist/internal/session-links.js +69 -0
  145. package/dist/internal/sessions-client.d.ts.map +1 -1
  146. package/dist/internal/sessions-client.js +1 -0
  147. package/dist/playground/assets/index-ClbuaLpm.css +1 -0
  148. package/dist/playground/assets/{index-0oqXUkrR.js → index-SqkR13k_.js} +39 -39
  149. package/dist/playground/index.html +2 -2
  150. package/dist/types.d.ts +19 -12
  151. package/dist/types.d.ts.map +1 -1
  152. package/docs/README.md +15 -0
  153. package/docs/example-agents/fsd.md +3 -0
  154. package/docs/example-agents/security-reviewer.md +3 -0
  155. package/docs/guides/convert-automation.md +176 -0
  156. package/docs/guides/github.md +6 -2
  157. package/docs/reference/cli.md +34 -10
  158. package/docs/scaffolding-agents.md +3 -0
  159. package/docs/templates/agentic-owners.md +97 -0
  160. package/docs/templates/demo.md +84 -0
  161. package/docs/templates/pr-autofixer.md +136 -0
  162. package/docs/templates/security-reviewer.md +89 -0
  163. package/package.json +9 -1
  164. package/skills/github/SKILL.md +4 -5
  165. package/src/bin/agent-serve.ts +13 -6
  166. package/src/channels/github/github-channel.ts +95 -18
  167. package/src/channels/github/types.ts +31 -0
  168. package/src/channels/origin/cursor-account.ts +64 -0
  169. package/src/channels/origin/events.ts +41 -0
  170. package/src/channels/origin/index.ts +29 -0
  171. package/src/channels/origin/origin-channel.ts +784 -0
  172. package/src/channels/origin/origin-webhook.ts +202 -0
  173. package/src/channels/origin/types.ts +186 -0
  174. package/src/channels/slack/doctor.ts +218 -5
  175. package/src/index.ts +3 -1
  176. package/src/internal/cli-ax.ts +23 -0
  177. package/src/internal/cli-slack.ts +11 -1
  178. package/src/internal/cursor-event-relay.ts +193 -31
  179. package/src/internal/discovery.ts +15 -5
  180. package/src/internal/distribution.ts +1 -0
  181. package/src/internal/handleAgentServeTrigger.ts +129 -19
  182. package/src/internal/init-interview.ts +384 -0
  183. package/src/internal/init-project.ts +70 -15
  184. package/src/internal/mcp-endpoint.ts +7 -0
  185. package/src/internal/server.ts +188 -58
  186. package/src/internal/session-engine.ts +5 -21
  187. package/src/internal/session-links.ts +88 -0
  188. package/src/internal/sessions-client.ts +1 -0
  189. package/src/types.ts +19 -12
  190. package/templates/agentic-owners/README.md +73 -0
  191. package/templates/agentic-owners/agent/agent.ts +15 -0
  192. package/templates/agentic-owners/agent/channels/github.ts +85 -0
  193. package/templates/agentic-owners/agent/instructions.md +34 -0
  194. package/templates/agentic-owners/agent/lib/config.ts +13 -0
  195. package/templates/agentic-owners/agent/lib/github.ts +241 -0
  196. package/templates/agentic-owners/agent/lib/owners/README.md +50 -0
  197. package/templates/agentic-owners/agent/lib/owners/constants.ts +22 -0
  198. package/templates/agentic-owners/agent/lib/owners/decision.ts +485 -0
  199. package/templates/agentic-owners/agent/lib/owners/handles.ts +54 -0
  200. package/templates/agentic-owners/agent/lib/owners/match.ts +177 -0
  201. package/templates/agentic-owners/agent/lib/owners/matching.ts +95 -0
  202. package/templates/agentic-owners/agent/lib/owners/parse.ts +589 -0
  203. package/templates/agentic-owners/agent/lib/owners/policyMode.ts +65 -0
  204. package/templates/agentic-owners/agent/lib/owners/prompt.ts +66 -0
  205. package/templates/agentic-owners/agent/lib/owners/prompts/base-review.md +105 -0
  206. package/templates/agentic-owners/agent/lib/owners/types.ts +45 -0
  207. package/templates/agentic-owners/agent/lib/review.ts +777 -0
  208. package/templates/agentic-owners/agent/tools/record_review.ts +69 -0
  209. package/templates/agentic-owners/evals/evals.config.ts +5 -0
  210. package/templates/agentic-owners/evals/owners.eval.ts +116 -0
  211. package/templates/agentic-owners/package.json +20 -0
  212. package/templates/agentic-owners/tsconfig.json +12 -0
  213. package/templates/demo/README.md +33 -0
  214. package/templates/demo/agent/agent.ts +15 -0
  215. package/templates/demo/agent/channels/github.ts +50 -0
  216. package/templates/demo/agent/channels/queue.ts +30 -0
  217. package/templates/demo/agent/instructions.md +8 -0
  218. package/templates/demo/agent/lib/collect.ts +78 -0
  219. package/templates/demo/agent/tools/record_demo.ts +40 -0
  220. package/templates/demo/evals/demo.eval.ts +33 -0
  221. package/templates/demo/evals/evals.config.ts +5 -0
  222. package/templates/demo/package.json +18 -0
  223. package/templates/demo/tsconfig.json +12 -0
  224. package/templates/pr-autofixer/README.md +44 -0
  225. package/templates/pr-autofixer/agent/agent.ts +22 -0
  226. package/templates/pr-autofixer/agent/channels/drive.ts +40 -0
  227. package/templates/pr-autofixer/agent/channels/github.ts +167 -0
  228. package/templates/pr-autofixer/agent/channels/slack.ts +17 -0
  229. package/templates/pr-autofixer/agent/hooks/bind-agent.ts +19 -0
  230. package/templates/pr-autofixer/agent/instructions.md +83 -0
  231. package/templates/pr-autofixer/agent/lib/affinity.ts +33 -0
  232. package/templates/pr-autofixer/agent/lib/drive-schemas.ts +15 -0
  233. package/templates/pr-autofixer/agent/lib/drive.ts +79 -0
  234. package/templates/pr-autofixer/agent/lib/merge-watch.ts +123 -0
  235. package/templates/pr-autofixer/agent/lib/pr-state.ts +54 -0
  236. package/templates/pr-autofixer/agent/lib/repos.ts +19 -0
  237. package/templates/pr-autofixer/agent/lib/start-drive.ts +122 -0
  238. package/templates/pr-autofixer/agent/lib/webhook-buffer.ts +225 -0
  239. package/templates/pr-autofixer/agent/lib/webhook-routing.ts +231 -0
  240. package/templates/pr-autofixer/agent/storage.ts +7 -0
  241. package/templates/pr-autofixer/agent/tools/drive_pr.ts +22 -0
  242. package/templates/pr-autofixer/evals/coordinator.eval.ts +16 -0
  243. package/templates/pr-autofixer/evals/evals.config.ts +5 -0
  244. package/templates/pr-autofixer/init.json +25 -0
  245. package/templates/pr-autofixer/package.json +18 -0
  246. package/templates/pr-autofixer/tsconfig.json +12 -0
  247. package/templates/security-reviewer/README.md +31 -0
  248. package/templates/security-reviewer/agent/agent.ts +15 -0
  249. package/templates/security-reviewer/agent/channels/github.ts +46 -0
  250. package/templates/security-reviewer/agent/instructions.md +22 -0
  251. package/templates/security-reviewer/agent/lib/repos.ts +5 -0
  252. package/templates/security-reviewer/agent/tools/post_findings.ts +72 -0
  253. package/templates/security-reviewer/evals/evals.config.ts +5 -0
  254. package/templates/security-reviewer/evals/review.eval.ts +53 -0
  255. package/templates/security-reviewer/init.json +25 -0
  256. package/templates/security-reviewer/package.json +18 -0
  257. package/templates/security-reviewer/tsconfig.json +12 -0
  258. package/dist/docs/assets/chunks/@localSearchIndexroot.BFpWSo6D.js +0 -1
  259. package/dist/docs/assets/index.md.COeoSkcF.js +0 -5
  260. package/dist/docs/assets/scaffolding-agents.md.mkc3B_ZW.js +0 -1
  261. package/dist/playground/assets/index-tHCjJxv4.css +0 -1
@@ -0,0 +1,784 @@
1
+ import { z } from "zod";
2
+ import {
3
+ allowAll,
4
+ defineChannel,
5
+ localDevStrict,
6
+ loopbackOnly,
7
+ POST,
8
+ } from "../../channels.js";
9
+ import { isDevMode } from "../../internal/dev-mode.js";
10
+ import type {
11
+ AgentCloudOptions,
12
+ AuthContext,
13
+ ChannelDefinition,
14
+ ChannelHandlerArgs,
15
+ } from "../../types.js";
16
+ import {
17
+ parseCursorOriginRepoScope,
18
+ registerOriginCursorAccountRuntime,
19
+ } from "./cursor-account.js";
20
+ import { deriveOriginWebhookEvents, ORIGIN_CHANNEL_KIND } from "./events.js";
21
+ import {
22
+ DEFAULT_ORIGIN_API_BASE_URL,
23
+ type OriginWebhookHeaders,
24
+ verifyOriginWebhook,
25
+ } from "./origin-webhook.js";
26
+ import type {
27
+ OriginActor,
28
+ OriginChannelOptions,
29
+ OriginComment,
30
+ OriginDispatch,
31
+ OriginEventContext,
32
+ OriginPullRequestEvent,
33
+ OriginRepositoryRef,
34
+ OriginReview,
35
+ OriginTaskDispatch,
36
+ OriginTurnDispatch,
37
+ OriginWebhookEnvelope,
38
+ } from "./types.js";
39
+
40
+ /**
41
+ * Default dispatch gate when no `onPullRequest` hook is declared: the common
42
+ * review triggers start a turn as the delivery's actor.
43
+ */
44
+ const DEFAULT_PULL_REQUEST_TYPES: ReadonlySet<string> = new Set([
45
+ "pull_request.created",
46
+ "pull_request.published",
47
+ "pull_request.head_ref.pushed",
48
+ "pull_request.base_ref.updated",
49
+ ]);
50
+
51
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
52
+ return typeof value === "object" && value !== null && !Array.isArray(value)
53
+ ? (value as Record<string, unknown>)
54
+ : undefined;
55
+ }
56
+
57
+ function asNonEmptyString(value: unknown): string | undefined {
58
+ return typeof value === "string" && value.trim() !== ""
59
+ ? value.trim()
60
+ : undefined;
61
+ }
62
+
63
+ function asPositiveInt(value: unknown): number | undefined {
64
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
65
+ return value;
66
+ }
67
+ if (typeof value === "string" && /^[1-9]\d*$/.test(value)) {
68
+ return Number(value);
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ function asBoolean(value: unknown): boolean | undefined {
74
+ return typeof value === "boolean" ? value : undefined;
75
+ }
76
+
77
+ function originActor(value: unknown): OriginActor | undefined {
78
+ const record = asRecord(value);
79
+ if (record === undefined) return undefined;
80
+ const nestedUser = asRecord(record.user);
81
+ const nestedApp = asRecord(record.app);
82
+ const id =
83
+ asNonEmptyString(nestedUser?.id) ??
84
+ asNonEmptyString(nestedApp?.id) ??
85
+ asNonEmptyString(asRecord(record.serviceAccount)?.id) ??
86
+ asNonEmptyString(record.id);
87
+ if (id === undefined) return undefined;
88
+ const kind = asNonEmptyString(record.kind);
89
+ return kind === undefined ? { id } : { id, kind };
90
+ }
91
+
92
+ function originHeaders(request: Request): OriginWebhookHeaders {
93
+ return {
94
+ "webhook-id": request.headers.get("webhook-id") ?? undefined,
95
+ "webhook-timestamp": request.headers.get("webhook-timestamp") ?? undefined,
96
+ "webhook-signature": request.headers.get("webhook-signature") ?? undefined,
97
+ };
98
+ }
99
+
100
+ function skipOriginVerify(): boolean {
101
+ return process.env.AGENT_SERVE_ORIGIN_SKIP_VERIFY?.trim() === "1";
102
+ }
103
+
104
+ /**
105
+ * Direct Origin HTTP deliveries verify v1ed. `--dev` and
106
+ * `AGENT_SERVE_ORIGIN_SKIP_VERIFY=1` skip that so local fixtures work, matching
107
+ * GitHub's unsigned loopback posture when no webhook secret is set.
108
+ */
109
+ function shouldSkipOriginVerify(): boolean {
110
+ return skipOriginVerify() || isDevMode();
111
+ }
112
+
113
+ function asIdList(value: string | readonly string[] | undefined): string[] {
114
+ if (value === undefined) return [];
115
+ const items = typeof value === "string" ? [value] : [...value];
116
+ return [...new Set(items.map((id) => id.trim()).filter(Boolean))];
117
+ }
118
+
119
+ /** `AGENT_SERVE_ORIGIN_APP_ID` fallback for {@link OriginChannelOptions.appId}. */
120
+ export function envOriginAppId(): string | undefined {
121
+ const value = process.env.AGENT_SERVE_ORIGIN_APP_ID?.trim();
122
+ return value === undefined || value === "" ? undefined : value;
123
+ }
124
+
125
+ function configuredOriginAppIds(
126
+ options: OriginChannelOptions
127
+ ): readonly string[] {
128
+ const fromOptions = asIdList(options.appId);
129
+ if (fromOptions.length > 0) return fromOptions;
130
+ const fromEnv = envOriginAppId();
131
+ return fromEnv === undefined ? [] : [fromEnv];
132
+ }
133
+
134
+ function configuredOriginInstallationIds(
135
+ options: OriginChannelOptions
136
+ ): readonly string[] {
137
+ return asIdList(options.installationId);
138
+ }
139
+
140
+ /**
141
+ * Repo allowlist for HTTP and trigger fan-out. `undefined` means no extra
142
+ * repo gate (the bound Origin app's installations still bound what Origin
143
+ * will send).
144
+ */
145
+ function configuredOriginRepoAllowlist(
146
+ options: OriginChannelOptions
147
+ ): ReadonlySet<string> | undefined {
148
+ const repos: string[] = [];
149
+ if (options.repos !== undefined) {
150
+ repos.push(...parseCursorOriginRepoScope(options.repos).repos);
151
+ }
152
+ if (typeof options.cursorAccount === "object") {
153
+ repos.push(
154
+ ...parseCursorOriginRepoScope(options.cursorAccount.repos).repos
155
+ );
156
+ }
157
+ return repos.length === 0 ? undefined : new Set(repos);
158
+ }
159
+
160
+ /**
161
+ * v1ed proves Origin signed the body. Channel auth is the signed
162
+ * `envelope.appId` matching this channel's configured Origin app (the GitHub
163
+ * webhook-secret analogue). Cursor-internal documents the same gap.
164
+ */
165
+ function originHttpBindingError(
166
+ options: OriginChannelOptions,
167
+ envelope: OriginWebhookEnvelope
168
+ ): string | undefined {
169
+ const allowedApps = configuredOriginAppIds(options);
170
+ if (allowedApps.length === 0) {
171
+ return "unbound_origin_app";
172
+ }
173
+ const deliveredAppId = envelope.appId?.trim();
174
+ if (deliveredAppId === undefined || deliveredAppId === "") {
175
+ return "invalid_app";
176
+ }
177
+ if (!allowedApps.includes(deliveredAppId)) {
178
+ return "invalid_app";
179
+ }
180
+ const allowedInstalls = configuredOriginInstallationIds(options);
181
+ if (allowedInstalls.length === 0) {
182
+ return undefined;
183
+ }
184
+ const deliveredInstallationId = envelope.installationId?.trim();
185
+ if (
186
+ deliveredInstallationId === undefined ||
187
+ deliveredInstallationId === "" ||
188
+ !allowedInstalls.includes(deliveredInstallationId)
189
+ ) {
190
+ return "invalid_installation";
191
+ }
192
+ return undefined;
193
+ }
194
+
195
+ function resolveApiBaseUrl(options: OriginChannelOptions): string {
196
+ const authored =
197
+ typeof options.apiBaseUrl === "function"
198
+ ? options.apiBaseUrl()
199
+ : options.apiBaseUrl;
200
+ const trimmed = authored?.trim();
201
+ return trimmed === undefined || trimmed === ""
202
+ ? DEFAULT_ORIGIN_API_BASE_URL
203
+ : trimmed;
204
+ }
205
+
206
+ const originEnvelopeSchema: z.ZodType<OriginWebhookEnvelope> = z.object({
207
+ deliveryId: z.string().optional(),
208
+ appId: z.string().optional(),
209
+ installationId: z.string().optional(),
210
+ sender: z
211
+ .object({
212
+ id: z.string().min(1),
213
+ kind: z.string().optional(),
214
+ })
215
+ .optional(),
216
+ event: z.object({
217
+ id: z.string().optional(),
218
+ type: z.string().min(1),
219
+ eventTime: z.string().optional(),
220
+ payload: z.unknown().optional(),
221
+ }),
222
+ });
223
+
224
+ export function parseOriginWebhookEnvelope(
225
+ payload: unknown
226
+ ): OriginWebhookEnvelope | null {
227
+ const parsed = originEnvelopeSchema.safeParse(payload);
228
+ return parsed.success ? parsed.data : null;
229
+ }
230
+
231
+ function originRepoFromPayload(
232
+ payload: Record<string, unknown>
233
+ ): OriginRepositoryRef | undefined {
234
+ const repository =
235
+ asRecord(payload.repository) ??
236
+ asRecord(asRecord(payload.pullRequest)?.repository);
237
+ const owner = asNonEmptyString(asRecord(repository?.owner)?.slug);
238
+ const name = asNonEmptyString(repository?.name);
239
+ if (owner === undefined || name === undefined) return undefined;
240
+ return { owner, name, fullName: `${owner}/${name}` };
241
+ }
242
+
243
+ function originPrUrl(
244
+ repository: OriginRepositoryRef,
245
+ number: number,
246
+ explicit?: string
247
+ ): string {
248
+ return (
249
+ explicit ??
250
+ `https://cursor.com/codebase/${repository.owner}/${repository.name}/pull/${number}`
251
+ );
252
+ }
253
+
254
+ function originSender(
255
+ envelope: OriginWebhookEnvelope,
256
+ payload: Record<string, unknown>
257
+ ): OriginActor | undefined {
258
+ return (
259
+ envelope.sender ??
260
+ originActor(payload.actor) ??
261
+ originActor(payload.pusher) ??
262
+ originActor(asRecord(payload.review)?.author) ??
263
+ originActor(asRecord(payload.comment)?.author) ??
264
+ originActor(asRecord(payload.pullRequest)?.author)
265
+ );
266
+ }
267
+
268
+ export function parseOriginPullRequestEvent(input: {
269
+ envelope: OriginWebhookEnvelope;
270
+ }): {
271
+ ctx: Omit<OriginEventContext, "host" | "artifacts">;
272
+ pullRequest: OriginPullRequestEvent;
273
+ payload: Record<string, unknown>;
274
+ } | null {
275
+ const payload = asRecord(input.envelope.event.payload) ?? {};
276
+ const repository = originRepoFromPayload(payload);
277
+ const pullRequest = asRecord(payload.pullRequest);
278
+ const number = asPositiveInt(pullRequest?.number);
279
+ if (repository === undefined || number === undefined) {
280
+ return null;
281
+ }
282
+ const eventType = input.envelope.event.type;
283
+ const url = originPrUrl(
284
+ repository,
285
+ number,
286
+ asNonEmptyString(pullRequest?.url)
287
+ );
288
+ const head = asRecord(pullRequest?.head);
289
+ const base = asRecord(pullRequest?.base);
290
+ const version = asRecord(pullRequest?.version);
291
+ const author = originActor(pullRequest?.author);
292
+ return {
293
+ payload,
294
+ ctx: {
295
+ deliveryId: input.envelope.deliveryId,
296
+ eventType,
297
+ repository,
298
+ pullRequestNumber: number,
299
+ pullRequestUrl: url,
300
+ sender: originSender(input.envelope, payload),
301
+ },
302
+ pullRequest: {
303
+ type: eventType,
304
+ number,
305
+ url,
306
+ title: asNonEmptyString(pullRequest?.title),
307
+ body:
308
+ pullRequest?.body === null
309
+ ? null
310
+ : (asNonEmptyString(pullRequest?.body) ??
311
+ (typeof pullRequest?.body === "string"
312
+ ? pullRequest.body
313
+ : undefined)),
314
+ draft: asBoolean(pullRequest?.draft),
315
+ merged:
316
+ eventType === "pull_request.merged" || asBoolean(pullRequest?.merged),
317
+ authorId: author?.id,
318
+ headRef: asNonEmptyString(head?.ref),
319
+ headSha:
320
+ asNonEmptyString(head?.sha) ?? asNonEmptyString(version?.headSha),
321
+ baseRef: asNonEmptyString(base?.ref),
322
+ baseSha:
323
+ asNonEmptyString(base?.sha) ?? asNonEmptyString(version?.baseSha),
324
+ },
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Derive the turn's auth from the actor that triggered the delivery — the
330
+ * standard `{ auth: defaultOriginAuth(ctx) }` dispatch from an inbound hook.
331
+ * The subscriber's Cursor identity is not the turn principal (same pattern
332
+ * as {@link defaultGitHubAuth}).
333
+ */
334
+ export function defaultOriginAuth(
335
+ ctx: Omit<OriginEventContext, "host" | "artifacts">
336
+ ): AuthContext {
337
+ const senderId = ctx.sender?.id;
338
+ return {
339
+ authenticator: "origin-webhook",
340
+ principalId: senderId ?? "origin",
341
+ principalType: ctx.sender?.kind === "app" ? "service" : "user",
342
+ attributes: {
343
+ conversation_kind: "pull_request",
344
+ repository: ctx.repository.fullName,
345
+ ...(ctx.deliveryId === undefined ? {} : { delivery_id: ctx.deliveryId }),
346
+ ...(senderId === undefined ? {} : { subject: senderId }),
347
+ ...(ctx.sender?.kind === undefined ? {} : { user_type: ctx.sender.kind }),
348
+ issuer: `origin:${ctx.repository.owner}`,
349
+ },
350
+ };
351
+ }
352
+
353
+ export function originContinuationKey(
354
+ ctx: Pick<OriginEventContext, "repository" | "pullRequestNumber">
355
+ ): string {
356
+ return `origin-pr:${ctx.repository.fullName}#${ctx.pullRequestNumber}`;
357
+ }
358
+
359
+ export function defaultOriginTitle(
360
+ ctx: Pick<OriginEventContext, "repository" | "pullRequestNumber">
361
+ ): string {
362
+ return `Origin ${ctx.repository.fullName}#${ctx.pullRequestNumber}`;
363
+ }
364
+
365
+ export function defaultOriginPullRequestMessage(
366
+ ctx: Omit<OriginEventContext, "host" | "artifacts">,
367
+ pullRequest: OriginPullRequestEvent
368
+ ): string {
369
+ const head =
370
+ pullRequest.headRef !== undefined && pullRequest.headSha !== undefined
371
+ ? `Head: ${pullRequest.headRef} (${pullRequest.headSha})`
372
+ : undefined;
373
+ return [
374
+ `Origin pull request trigger: ${ctx.repository.fullName}#${pullRequest.number}`,
375
+ `Event: ${pullRequest.type}`,
376
+ `URL: ${pullRequest.url}`,
377
+ ...(pullRequest.title === undefined ? [] : [`Title: ${pullRequest.title}`]),
378
+ ...(head === undefined ? [] : [head]),
379
+ "Handle this pull request event.",
380
+ ].join("\n");
381
+ }
382
+
383
+ export function defaultOriginPullRequestDispatch(
384
+ ctx: Omit<OriginEventContext, "host" | "artifacts">,
385
+ pullRequest: OriginPullRequestEvent
386
+ ): OriginTurnDispatch | null {
387
+ if (!DEFAULT_PULL_REQUEST_TYPES.has(pullRequest.type)) {
388
+ return null;
389
+ }
390
+ return { auth: defaultOriginAuth(ctx) };
391
+ }
392
+
393
+ function isTaskDispatch(
394
+ dispatch: OriginDispatch
395
+ ): dispatch is OriginTaskDispatch {
396
+ return "task" in dispatch;
397
+ }
398
+
399
+ function enrichEventContext(input: {
400
+ ctx: Omit<OriginEventContext, "host" | "artifacts">;
401
+ args: ChannelHandlerArgs;
402
+ }): OriginEventContext {
403
+ return {
404
+ ...input.ctx,
405
+ host: input.args.host,
406
+ artifacts: input.args.artifacts,
407
+ };
408
+ }
409
+
410
+ function skipped(
411
+ reason: string,
412
+ extra: Record<string, unknown> = {}
413
+ ): Response {
414
+ return Response.json({ ok: true, skipped: true, reason, ...extra });
415
+ }
416
+
417
+ function acknowledgeTask(
418
+ args: ChannelHandlerArgs,
419
+ dispatch: OriginTaskDispatch,
420
+ extra: Record<string, unknown>
421
+ ): Response {
422
+ args.waitUntil(
423
+ new Promise<void>((resolve, reject) => {
424
+ setImmediate(() => {
425
+ Promise.resolve()
426
+ .then(dispatch.task)
427
+ .then(() => resolve(), reject);
428
+ });
429
+ })
430
+ );
431
+ return Response.json(
432
+ { ok: true, dispatched: true, mode: "task", ...extra },
433
+ { status: 202 }
434
+ );
435
+ }
436
+
437
+ async function startTurn(input: {
438
+ args: ChannelHandlerArgs;
439
+ ctx: OriginEventContext;
440
+ message: string;
441
+ dispatch: OriginTurnDispatch;
442
+ cloud?: AgentCloudOptions;
443
+ }): Promise<Response> {
444
+ const session = await input.args.send(input.message, {
445
+ auth: input.dispatch.auth,
446
+ continuationToken: originContinuationKey(input.ctx),
447
+ title: input.dispatch.title ?? defaultOriginTitle(input.ctx),
448
+ ...(input.cloud === undefined ? {} : { cloud: input.cloud }),
449
+ });
450
+ return Response.json({
451
+ ok: true,
452
+ sessionId: session.id,
453
+ isNew: session.isNew,
454
+ continuationToken: session.continuationToken,
455
+ });
456
+ }
457
+
458
+ async function resolveCloudOptions(
459
+ cloud: OriginChannelOptions["cloud"],
460
+ ctx: OriginEventContext,
461
+ pullRequest: OriginPullRequestEvent
462
+ ): Promise<AgentCloudOptions | undefined> {
463
+ if (cloud === undefined || cloud === false) {
464
+ return undefined;
465
+ }
466
+ if (cloud === true) {
467
+ return {
468
+ repos: [
469
+ {
470
+ url: `https://cursor.com/codebase/${ctx.repository.fullName}`,
471
+ prUrl: pullRequest.url,
472
+ startingRef: pullRequest.headSha,
473
+ },
474
+ ],
475
+ };
476
+ }
477
+ if (typeof cloud === "function") {
478
+ return await cloud(ctx, pullRequest);
479
+ }
480
+ return cloud;
481
+ }
482
+
483
+ async function applyDispatch(input: {
484
+ args: ChannelHandlerArgs;
485
+ ctx: OriginEventContext;
486
+ dispatch: OriginDispatch | null;
487
+ extra: Record<string, unknown>;
488
+ message: string;
489
+ cloud?: AgentCloudOptions;
490
+ }): Promise<Response> {
491
+ if (input.dispatch === null) {
492
+ return skipped("not_dispatched", input.extra);
493
+ }
494
+ if (isTaskDispatch(input.dispatch)) {
495
+ return acknowledgeTask(input.args, input.dispatch, input.extra);
496
+ }
497
+ const message = [
498
+ input.dispatch.message ?? input.message,
499
+ ...(input.dispatch.context ?? []),
500
+ ].join("\n");
501
+ return startTurn({
502
+ args: input.args,
503
+ ctx: input.ctx,
504
+ message,
505
+ dispatch: input.dispatch,
506
+ cloud: input.cloud,
507
+ });
508
+ }
509
+
510
+ /**
511
+ * Core Origin webhook dispatch used by {@link originChannel} routes and by
512
+ * {@link handleAgentServeTrigger} fan-out. Signature verification is the
513
+ * caller's responsibility (HTTP route verifies; internal fan-out skips).
514
+ */
515
+ export async function dispatchOriginWebhook(input: {
516
+ options: OriginChannelOptions;
517
+ envelope: OriginWebhookEnvelope;
518
+ args: ChannelHandlerArgs;
519
+ }): Promise<Response> {
520
+ const eventType = input.envelope.event.type;
521
+ const parsed = parseOriginPullRequestEvent({ envelope: input.envelope });
522
+ if (parsed === null) {
523
+ return Response.json(
524
+ { ok: false, error: "invalid_origin_payload" },
525
+ { status: 400 }
526
+ );
527
+ }
528
+ const ctx = enrichEventContext({ ctx: parsed.ctx, args: input.args });
529
+ const { pullRequest, payload } = parsed;
530
+ const repoAllowlist = configuredOriginRepoAllowlist(input.options);
531
+ if (
532
+ repoAllowlist !== undefined &&
533
+ !repoAllowlist.has(ctx.repository.fullName.toLowerCase())
534
+ ) {
535
+ return skipped("repo_not_allowed", { repo: ctx.repository.fullName });
536
+ }
537
+
538
+ if (eventType.startsWith("pull_request.comment.")) {
539
+ if (input.options.onComment === undefined) {
540
+ return skipped("no_handler", { event: eventType });
541
+ }
542
+ const commentRecord = asRecord(payload.comment);
543
+ const commentId = asNonEmptyString(commentRecord?.id);
544
+ const commentBody =
545
+ typeof commentRecord?.body === "string" ? commentRecord.body : undefined;
546
+ if (commentId === undefined || commentBody === undefined) {
547
+ return Response.json(
548
+ { ok: false, error: "invalid_origin_comment_payload" },
549
+ { status: 400 }
550
+ );
551
+ }
552
+ const comment: OriginComment = {
553
+ id: commentId,
554
+ body: commentBody,
555
+ author: originActor(commentRecord?.author),
556
+ };
557
+ const dispatch = await input.options.onComment(ctx, comment);
558
+ return applyDispatch({
559
+ args: input.args,
560
+ ctx,
561
+ dispatch,
562
+ extra: { event: eventType },
563
+ message: `Origin comment on ${ctx.repository.fullName}#${ctx.pullRequestNumber}`,
564
+ });
565
+ }
566
+
567
+ if (eventType.startsWith("pull_request.review.")) {
568
+ if (input.options.onReview === undefined) {
569
+ return skipped("no_handler", { event: eventType });
570
+ }
571
+ const reviewRecord = asRecord(payload.review) ?? {};
572
+ const review: OriginReview = {
573
+ id: asNonEmptyString(reviewRecord.id),
574
+ verdict: asNonEmptyString(reviewRecord.verdict),
575
+ author: originActor(reviewRecord.author),
576
+ };
577
+ const dispatch = await input.options.onReview(ctx, review);
578
+ return applyDispatch({
579
+ args: input.args,
580
+ ctx,
581
+ dispatch,
582
+ extra: { event: eventType },
583
+ message: `Origin review on ${ctx.repository.fullName}#${ctx.pullRequestNumber}`,
584
+ });
585
+ }
586
+
587
+ if (eventType.startsWith("pull_request.")) {
588
+ const dispatch =
589
+ input.options.onPullRequest === undefined
590
+ ? defaultOriginPullRequestDispatch(ctx, pullRequest)
591
+ : await input.options.onPullRequest(ctx, pullRequest);
592
+ const cloud = await resolveCloudOptions(
593
+ input.options.cloud,
594
+ ctx,
595
+ pullRequest
596
+ );
597
+ return applyDispatch({
598
+ args: input.args,
599
+ ctx,
600
+ dispatch,
601
+ extra: { event: eventType },
602
+ message: defaultOriginPullRequestMessage(ctx, pullRequest),
603
+ cloud,
604
+ });
605
+ }
606
+
607
+ if (input.options.onEvent !== undefined) {
608
+ const dispatch = await input.options.onEvent(ctx, {
609
+ eventType,
610
+ payload,
611
+ });
612
+ return applyDispatch({
613
+ args: input.args,
614
+ ctx,
615
+ dispatch,
616
+ extra: { event: eventType },
617
+ message: `Origin ${eventType} event`,
618
+ });
619
+ }
620
+
621
+ return skipped("unsupported_event", { event: eventType });
622
+ }
623
+
624
+ /**
625
+ * HTTP entry for a single agent's Origin channel: optional v1ed verify, then
626
+ * app-id bind, then {@link dispatchOriginWebhook}.
627
+ */
628
+ export async function handleOriginWebhookRequest(input: {
629
+ options: OriginChannelOptions;
630
+ request: Request;
631
+ args: ChannelHandlerArgs;
632
+ /** When true, skip v1ed even though Origin HTTP deliveries are signed. */
633
+ skipSignatureVerification?: boolean;
634
+ }): Promise<Response> {
635
+ const bodyText = await input.request.text();
636
+ const requireHttpBinding =
637
+ input.skipSignatureVerification !== true &&
638
+ shouldSkipOriginVerify() !== true;
639
+ if (requireHttpBinding) {
640
+ try {
641
+ await verifyOriginWebhook(bodyText, originHeaders(input.request), {
642
+ apiBaseUrl: resolveApiBaseUrl(input.options),
643
+ });
644
+ } catch {
645
+ return Response.json(
646
+ { ok: false, error: "invalid_signature" },
647
+ { status: 401 }
648
+ );
649
+ }
650
+ }
651
+
652
+ let payload: unknown;
653
+ try {
654
+ payload = JSON.parse(bodyText) as unknown;
655
+ } catch {
656
+ return Response.json({ ok: false, error: "invalid_json" }, { status: 400 });
657
+ }
658
+ const envelope = parseOriginWebhookEnvelope(payload);
659
+ if (envelope === null) {
660
+ return Response.json(
661
+ { ok: false, error: "invalid_origin_payload" },
662
+ { status: 400 }
663
+ );
664
+ }
665
+ if (requireHttpBinding) {
666
+ const bindingError = originHttpBindingError(input.options, envelope);
667
+ if (bindingError !== undefined) {
668
+ return Response.json({ ok: false, error: bindingError }, { status: 401 });
669
+ }
670
+ }
671
+ return dispatchOriginWebhook({
672
+ options: input.options,
673
+ envelope,
674
+ args: input.args,
675
+ });
676
+ }
677
+
678
+ const originChannelOptionsByDefinition = new WeakMap<
679
+ object,
680
+ OriginChannelOptions
681
+ >();
682
+
683
+ export function getOriginChannelOptions(
684
+ definition: object
685
+ ): OriginChannelOptions | undefined {
686
+ return originChannelOptionsByDefinition.get(definition);
687
+ }
688
+
689
+ export function originChannel(
690
+ options: OriginChannelOptions = {}
691
+ ): ChannelDefinition {
692
+ const usesCursorAccount =
693
+ options.cursorAccount === true || typeof options.cursorAccount === "object";
694
+ const cursorScope =
695
+ typeof options.cursorAccount === "object"
696
+ ? parseCursorOriginRepoScope(options.cursorAccount.repos)
697
+ : undefined;
698
+ if (options.repos !== undefined) {
699
+ parseCursorOriginRepoScope(options.repos);
700
+ }
701
+
702
+ const routePath = options.path ?? "/";
703
+ // v1ed + configured app id is the request auth (allowAll), matching GitHub
704
+ // HMAC + webhookSecret. Without an app id, prod stays loopback-strict so a
705
+ // reachable URL cannot be driven by any Origin-signed delivery. `--dev`
706
+ // / skip-verify admits loopback only.
707
+ const auth = options.auth ?? [
708
+ (request, info) =>
709
+ (shouldSkipOriginVerify()
710
+ ? loopbackOnly()
711
+ : configuredOriginAppIds(options).length > 0
712
+ ? allowAll()
713
+ : localDevStrict())(request, info),
714
+ ];
715
+
716
+ const definition = defineChannel({
717
+ auth,
718
+ onStart: options.onStart,
719
+ onStop: options.onStop,
720
+ meta: {
721
+ kind: ORIGIN_CHANNEL_KIND,
722
+ originWebhookEvents: deriveOriginWebhookEvents(options),
723
+ },
724
+ routes: [
725
+ POST(routePath, {
726
+ bodySchema: z.unknown(),
727
+ description: "Origin webhook ingress",
728
+ handler: async (request, args) =>
729
+ handleOriginWebhookRequest({
730
+ options,
731
+ request,
732
+ args,
733
+ }),
734
+ }),
735
+ ],
736
+ });
737
+ originChannelOptionsByDefinition.set(definition, options);
738
+ if (usesCursorAccount) {
739
+ registerOriginCursorAccountRuntime(definition, {
740
+ options,
741
+ scope: cursorScope,
742
+ });
743
+ }
744
+ return definition;
745
+ }
746
+
747
+ /** Minimal mount shape shared by `serve` / trigger fan-out. */
748
+ export interface OriginAgentMountLike {
749
+ slug: string;
750
+ project: {
751
+ channels: Array<{
752
+ id: string;
753
+ definition: {
754
+ meta?: { kind?: string; originWebhookEvents?: readonly string[] };
755
+ };
756
+ }>;
757
+ };
758
+ }
759
+
760
+ export interface OriginChannelTarget {
761
+ slug: string;
762
+ channelId: string;
763
+ events: string[];
764
+ }
765
+
766
+ export function findOriginChannelTargets(
767
+ mounts: ReadonlyArray<OriginAgentMountLike>
768
+ ): OriginChannelTarget[] {
769
+ const targets: OriginChannelTarget[] = [];
770
+ for (const { slug, project } of mounts) {
771
+ for (const channel of project.channels) {
772
+ const meta = channel.definition.meta;
773
+ if (meta?.kind !== ORIGIN_CHANNEL_KIND) {
774
+ continue;
775
+ }
776
+ targets.push({
777
+ slug,
778
+ channelId: channel.id,
779
+ events: [...(meta.originWebhookEvents ?? [])],
780
+ });
781
+ }
782
+ }
783
+ return targets;
784
+ }