@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,122 @@
1
+ import type { HostContext } from "@cursor/july";
2
+ import type { SendMessageFn, SendMessageOptions } from "@cursor/july/channels";
3
+ import {
4
+ cloudOptionsFor,
5
+ continuationTokenFor,
6
+ driveMessage,
7
+ parsePrRef,
8
+ } from "./drive.js";
9
+ import { armMergeConflictWatch } from "./merge-watch.js";
10
+ import { lookupPrAgent } from "./affinity.js";
11
+ import { fetchPrState, isTerminalPrState } from "./pr-state.js";
12
+
13
+ export type StartDriveErrorCode = "missing_pr" | "unrecognized_pr";
14
+
15
+ export interface StartDriveError {
16
+ ok: false;
17
+ code: StartDriveErrorCode;
18
+ error: string;
19
+ }
20
+
21
+ export interface StartDriveSuccess {
22
+ ok: true;
23
+ pr: string;
24
+ sessionId: string | null;
25
+ continuationToken: string | null;
26
+ bcId: string | null;
27
+ agentUrl: string | null;
28
+ status: string;
29
+ result: string | null;
30
+ mergeConflictReminderId: string | null;
31
+ }
32
+
33
+ export type StartDriveResult = StartDriveError | StartDriveSuccess;
34
+
35
+ export interface StartDriveArgs {
36
+ pr: string;
37
+ hint?: string;
38
+ send: SendMessageFn;
39
+ host: HostContext;
40
+ }
41
+
42
+ function agentUrlFor(bcId: string | null): string | null {
43
+ return bcId === null ? null : `https://cursor.com/agents/${bcId}`;
44
+ }
45
+
46
+ export async function startDrive(
47
+ args: StartDriveArgs
48
+ ): Promise<StartDriveResult> {
49
+ const prRaw = args.pr.trim();
50
+ if (prRaw === "") {
51
+ return {
52
+ ok: false,
53
+ code: "missing_pr",
54
+ error: 'Body must include "pr": a GitHub PR URL or owner/repo#N.',
55
+ };
56
+ }
57
+ const pr = parsePrRef(prRaw);
58
+ if (pr === undefined) {
59
+ return {
60
+ ok: false,
61
+ code: "unrecognized_pr",
62
+ error: `Unrecognized PR reference: ${args.pr}`,
63
+ };
64
+ }
65
+
66
+ const hint = args.hint?.trim() || undefined;
67
+ const prState = await fetchPrState({ github: args.host.github, pr });
68
+
69
+ if (isTerminalPrState(prState)) {
70
+ return {
71
+ ok: true,
72
+ pr: pr.label,
73
+ sessionId: null,
74
+ continuationToken: null,
75
+ bcId: null,
76
+ agentUrl: null,
77
+ status: "finished",
78
+ result: `PR ${pr.label} is already ${prState.toLowerCase()}. Nothing to drive.`,
79
+ mergeConflictReminderId: null,
80
+ };
81
+ }
82
+
83
+ const continuationToken = continuationTokenFor(pr);
84
+ const boundAgent = await lookupPrAgent(pr, args.host.kv);
85
+ const sendOptions: SendMessageOptions = {
86
+ continuationToken,
87
+ title: `PR ${pr.label}`,
88
+ cloud: cloudOptionsFor(pr),
89
+ sdkAgentId: boundAgent,
90
+ };
91
+
92
+ const session = await args.send(driveMessage(pr, { hint }), sendOptions);
93
+
94
+ let reminderId: string | null = null;
95
+ if (args.host.reminders !== undefined) {
96
+ const reminder = await armMergeConflictWatch({
97
+ reminders: args.host.reminders,
98
+ pr,
99
+ channelId: "drive",
100
+ continuationToken,
101
+ }).catch(() => undefined);
102
+ if (reminder !== undefined) {
103
+ reminderId = reminder.id;
104
+ }
105
+ }
106
+
107
+ const bcId = session.info.sdkAgentId?.startsWith("bc-")
108
+ ? session.info.sdkAgentId
109
+ : null;
110
+
111
+ return {
112
+ ok: true,
113
+ pr: pr.label,
114
+ sessionId: session.id,
115
+ continuationToken,
116
+ bcId,
117
+ agentUrl: agentUrlFor(bcId),
118
+ status: "started",
119
+ result: null,
120
+ mergeConflictReminderId: reminderId,
121
+ };
122
+ }
@@ -0,0 +1,225 @@
1
+ import type { HostKvApi } from "@cursor/july/kv";
2
+
3
+ export const WEBHOOK_BUFFER_KV_KEY: string = "webhook-buffer";
4
+
5
+ export interface BufferedWebhookEvent {
6
+ eventName: string;
7
+ action?: string;
8
+ prUrl: string;
9
+ owner: string;
10
+ repo: string;
11
+ number: number;
12
+ headSha?: string;
13
+ }
14
+
15
+ export interface WebhookBufferOptions {
16
+ debounceMs?: number;
17
+ onFlush: (event: BufferedWebhookEvent) => void | Promise<void>;
18
+ kv: HostKvApi;
19
+ }
20
+
21
+ interface BufferEntry {
22
+ latest: BufferedWebhookEvent;
23
+ count: number;
24
+ timer: ReturnType<typeof setTimeout> | undefined;
25
+ deadlineMs: number;
26
+ }
27
+
28
+ interface SnapshotEntry {
29
+ latest: BufferedWebhookEvent;
30
+ count: number;
31
+ deadlineMs: number;
32
+ }
33
+
34
+ export class WebhookBuffer {
35
+ private readonly entries = new Map<string, BufferEntry>();
36
+ private readonly inFlight = new Set<string>();
37
+ private readonly debounceMs: number;
38
+ private readonly onFlush: WebhookBufferOptions["onFlush"];
39
+ private readonly kv: HostKvApi;
40
+ private persistChain: Promise<void> = Promise.resolve();
41
+ private generation = 0;
42
+
43
+ constructor(options: WebhookBufferOptions) {
44
+ this.debounceMs = options.debounceMs ?? 3000;
45
+ this.onFlush = options.onFlush;
46
+ this.kv = options.kv;
47
+ }
48
+
49
+ static keyFor(
50
+ event: Pick<BufferedWebhookEvent, "owner" | "repo" | "number">
51
+ ): string {
52
+ return `${event.owner}/${event.repo}#${event.number}`;
53
+ }
54
+
55
+ async enqueue(event: BufferedWebhookEvent): Promise<void> {
56
+ const key = WebhookBuffer.keyFor(event);
57
+ const existing = this.entries.get(key);
58
+ if (existing === undefined) {
59
+ this.entries.set(key, {
60
+ latest: event,
61
+ count: 1,
62
+ timer: undefined,
63
+ deadlineMs: Date.now() + this.debounceMs,
64
+ });
65
+ } else {
66
+ existing.latest = event;
67
+ existing.count += 1;
68
+ existing.deadlineMs = Date.now() + this.debounceMs;
69
+ }
70
+ await this.persist();
71
+ this.schedule(key);
72
+ }
73
+
74
+ async restore(): Promise<number> {
75
+ const snapshot = await this.kv.get(WEBHOOK_BUFFER_KV_KEY);
76
+ if (
77
+ !isJsonObject(snapshot) ||
78
+ snapshot.version !== 1 ||
79
+ !isJsonObject(snapshot.entries)
80
+ ) {
81
+ return 0;
82
+ }
83
+ let restored = 0;
84
+ for (const [key, entry] of Object.entries(snapshot.entries)) {
85
+ if (!isValidSnapshotEntry(entry)) {
86
+ continue;
87
+ }
88
+ this.entries.set(key, {
89
+ latest: entry.latest,
90
+ count: Math.max(1, entry.count),
91
+ timer: undefined,
92
+ deadlineMs: entry.deadlineMs,
93
+ });
94
+ this.schedule(key);
95
+ restored += 1;
96
+ }
97
+ return restored;
98
+ }
99
+
100
+ async discard(key: string): Promise<void> {
101
+ const entry = this.entries.get(key);
102
+ if (entry === undefined) {
103
+ return;
104
+ }
105
+ clearTimeout(entry.timer);
106
+ this.entries.delete(key);
107
+ await this.persist();
108
+ }
109
+
110
+ async clear(): Promise<void> {
111
+ this.generation += 1;
112
+ for (const entry of this.entries.values()) {
113
+ clearTimeout(entry.timer);
114
+ }
115
+ this.entries.clear();
116
+ this.inFlight.clear();
117
+ }
118
+
119
+ private schedule(key: string): void {
120
+ const entry = this.entries.get(key);
121
+ if (entry === undefined) {
122
+ return;
123
+ }
124
+ clearTimeout(entry.timer);
125
+ const delay = Math.max(0, entry.deadlineMs - Date.now());
126
+ entry.timer = setTimeout(() => {
127
+ void this.flush(key);
128
+ }, delay);
129
+ }
130
+
131
+ private async flush(key: string): Promise<void> {
132
+ const entry = this.entries.get(key);
133
+ if (entry === undefined) {
134
+ return;
135
+ }
136
+
137
+ if (this.inFlight.has(key)) {
138
+ entry.deadlineMs = Date.now() + this.debounceMs;
139
+ await this.persist();
140
+ this.schedule(key);
141
+ return;
142
+ }
143
+
144
+ entry.timer = undefined;
145
+ this.inFlight.add(key);
146
+ const generation = this.generation;
147
+ try {
148
+ const event = entry.latest;
149
+ const count = entry.count;
150
+ await this.onFlush(event);
151
+ if (generation !== this.generation) {
152
+ return;
153
+ }
154
+ const current = this.entries.get(key);
155
+ if (
156
+ current !== undefined &&
157
+ current.latest === event &&
158
+ current.count === count
159
+ ) {
160
+ this.entries.delete(key);
161
+ }
162
+ await this.persist();
163
+ } catch (error) {
164
+ if (generation !== this.generation) {
165
+ throw error;
166
+ }
167
+ entry.deadlineMs = Date.now() + this.debounceMs;
168
+ await this.persist();
169
+ this.schedule(key);
170
+ throw error;
171
+ } finally {
172
+ if (generation === this.generation) {
173
+ this.inFlight.delete(key);
174
+ }
175
+ }
176
+ }
177
+
178
+ private persist(): Promise<void> {
179
+ const entries: Record<string, SnapshotEntry> = {};
180
+ for (const [key, entry] of this.entries) {
181
+ entries[key] = {
182
+ latest: entry.latest,
183
+ count: entry.count,
184
+ deadlineMs: entry.deadlineMs,
185
+ };
186
+ }
187
+ const write = (): Promise<void> =>
188
+ this.kv.put(
189
+ WEBHOOK_BUFFER_KV_KEY,
190
+ JSON.parse(
191
+ JSON.stringify({
192
+ version: 1,
193
+ updatedAt: new Date().toISOString(),
194
+ entries,
195
+ })
196
+ )
197
+ );
198
+ const next = this.persistChain.then(write, write);
199
+ this.persistChain = next.then(
200
+ () => undefined,
201
+ () => undefined
202
+ );
203
+ return next;
204
+ }
205
+ }
206
+
207
+ function isJsonObject(value: unknown): value is Record<string, unknown> {
208
+ return typeof value === "object" && value !== null && !Array.isArray(value);
209
+ }
210
+
211
+ function isValidSnapshotEntry(entry: unknown): entry is SnapshotEntry {
212
+ if (!isJsonObject(entry) || !isJsonObject(entry.latest)) {
213
+ return false;
214
+ }
215
+ const latest = entry.latest;
216
+ return (
217
+ typeof latest.eventName === "string" &&
218
+ typeof latest.prUrl === "string" &&
219
+ typeof latest.owner === "string" &&
220
+ typeof latest.repo === "string" &&
221
+ typeof latest.number === "number" &&
222
+ typeof entry.count === "number" &&
223
+ typeof entry.deadlineMs === "number"
224
+ );
225
+ }
@@ -0,0 +1,231 @@
1
+ import type {
2
+ GitHubCheckRunEvent,
3
+ GitHubCheckSuiteEvent,
4
+ GitHubEventContext,
5
+ GitHubPullRequestEvent,
6
+ GitHubRawEvent,
7
+ GitHubStatusEvent,
8
+ } from "@cursor/july/channels/github";
9
+ import type { BufferedWebhookEvent } from "./webhook-buffer.js";
10
+
11
+ const PULL_REQUEST_ACTIONS = new Set([
12
+ "reopened",
13
+ "synchronize",
14
+ "edited",
15
+ "ready_for_review",
16
+ "review_requested",
17
+ "converted_to_draft",
18
+ ]);
19
+
20
+ const CHECK_SUITE_CONCLUSIONS = new Set([
21
+ "success",
22
+ "failure",
23
+ "timed_out",
24
+ "action_required",
25
+ "startup_failure",
26
+ ]);
27
+
28
+ const CHECK_RUN_FAILURE_CONCLUSIONS = new Set([
29
+ "failure",
30
+ "timed_out",
31
+ "action_required",
32
+ "startup_failure",
33
+ ]);
34
+
35
+ export function shouldBufferPullRequest(pr: GitHubPullRequestEvent): boolean {
36
+ return PULL_REQUEST_ACTIONS.has(pr.action);
37
+ }
38
+
39
+ export function shouldBufferStatus(status: GitHubStatusEvent): boolean {
40
+ return status.state === "failure" || status.state === "error";
41
+ }
42
+
43
+ function shouldBufferCheckSuite(suite: GitHubCheckSuiteEvent): boolean {
44
+ return (
45
+ suite.action === "completed" &&
46
+ suite.conclusion != null &&
47
+ CHECK_SUITE_CONCLUSIONS.has(suite.conclusion)
48
+ );
49
+ }
50
+
51
+ function shouldBufferCheckRun(run: GitHubCheckRunEvent): boolean {
52
+ return (
53
+ run.action === "completed" &&
54
+ run.conclusion != null &&
55
+ CHECK_RUN_FAILURE_CONCLUSIONS.has(run.conclusion)
56
+ );
57
+ }
58
+
59
+ function shouldBufferRawEvent(event: GitHubRawEvent): boolean {
60
+ if (event.eventName === "pull_request_review") {
61
+ return event.action === "submitted" || event.action === "dismissed";
62
+ }
63
+ if (event.eventName === "pull_request_review_thread") {
64
+ return event.action === "resolved" || event.action === "unresolved";
65
+ }
66
+ return false;
67
+ }
68
+
69
+ function fromConversation(
70
+ ctx: GitHubEventContext,
71
+ fields: {
72
+ eventName: string;
73
+ action?: string;
74
+ headSha?: string;
75
+ }
76
+ ): BufferedWebhookEvent | null {
77
+ if (ctx.conversation.kind !== "pull_request") {
78
+ return null;
79
+ }
80
+ if (ctx.conversation.number <= 0) {
81
+ return null;
82
+ }
83
+ return {
84
+ eventName: fields.eventName,
85
+ action: fields.action,
86
+ prUrl: ctx.conversation.url,
87
+ owner: ctx.repository.owner,
88
+ repo: ctx.repository.name,
89
+ number: ctx.conversation.number,
90
+ headSha: fields.headSha,
91
+ };
92
+ }
93
+
94
+ async function resolvePrFromCommitSha(input: {
95
+ ctx: GitHubEventContext;
96
+ sha: string;
97
+ }): Promise<{ number: number; url: string } | undefined> {
98
+ try {
99
+ const octokit = await input.ctx.github.getOctokit();
100
+ const { data: pulls } = await octokit.request(
101
+ "GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls",
102
+ {
103
+ owner: input.ctx.repository.owner,
104
+ repo: input.ctx.repository.name,
105
+ commit_sha: input.sha,
106
+ }
107
+ );
108
+ const open = pulls.find((pull) => pull.state === "open");
109
+ if (open === undefined) {
110
+ return undefined;
111
+ }
112
+ return {
113
+ number: open.number,
114
+ url: open.html_url,
115
+ };
116
+ } catch {
117
+ return undefined;
118
+ }
119
+ }
120
+
121
+ async function fromSha(input: {
122
+ ctx: GitHubEventContext;
123
+ sha: string;
124
+ eligible: boolean;
125
+ eventName: string;
126
+ action?: string;
127
+ }): Promise<BufferedWebhookEvent | null> {
128
+ if (!input.eligible) {
129
+ return null;
130
+ }
131
+ const direct = fromConversation(input.ctx, {
132
+ eventName: input.eventName,
133
+ action: input.action,
134
+ headSha: input.sha,
135
+ });
136
+ if (direct !== null) {
137
+ return direct;
138
+ }
139
+ const resolved = await resolvePrFromCommitSha({
140
+ ctx: input.ctx,
141
+ sha: input.sha,
142
+ });
143
+ if (resolved === undefined) {
144
+ return null;
145
+ }
146
+ return {
147
+ eventName: input.eventName,
148
+ action: input.action,
149
+ prUrl: resolved.url,
150
+ owner: input.ctx.repository.owner,
151
+ repo: input.ctx.repository.name,
152
+ number: resolved.number,
153
+ headSha: input.sha,
154
+ };
155
+ }
156
+
157
+ export function bufferedEventFromPr(input: {
158
+ ctx: GitHubEventContext;
159
+ pr: GitHubPullRequestEvent;
160
+ }): BufferedWebhookEvent | null {
161
+ if (!shouldBufferPullRequest(input.pr)) {
162
+ return null;
163
+ }
164
+ return {
165
+ eventName: "pull_request",
166
+ action: input.pr.action,
167
+ prUrl: input.pr.url,
168
+ owner: input.ctx.repository.owner,
169
+ repo: input.ctx.repository.name,
170
+ number: input.pr.number,
171
+ headSha: input.pr.headSha,
172
+ };
173
+ }
174
+
175
+ export function bufferedEventFromComment(input: {
176
+ ctx: GitHubEventContext;
177
+ eventName: string;
178
+ }): BufferedWebhookEvent | null {
179
+ return fromConversation(input.ctx, { eventName: input.eventName });
180
+ }
181
+
182
+ export function bufferedEventFromCheckSuite(input: {
183
+ ctx: GitHubEventContext;
184
+ suite: GitHubCheckSuiteEvent;
185
+ }): Promise<BufferedWebhookEvent | null> {
186
+ return fromSha({
187
+ ctx: input.ctx,
188
+ sha: input.suite.headSha,
189
+ eligible: shouldBufferCheckSuite(input.suite),
190
+ eventName: "check_suite",
191
+ action: input.suite.action,
192
+ });
193
+ }
194
+
195
+ export function bufferedEventFromCheckRun(input: {
196
+ ctx: GitHubEventContext;
197
+ run: GitHubCheckRunEvent;
198
+ }): Promise<BufferedWebhookEvent | null> {
199
+ return fromSha({
200
+ ctx: input.ctx,
201
+ sha: input.run.headSha,
202
+ eligible: shouldBufferCheckRun(input.run),
203
+ eventName: "check_run",
204
+ action: input.run.action,
205
+ });
206
+ }
207
+
208
+ export function bufferedEventFromStatus(input: {
209
+ ctx: GitHubEventContext;
210
+ status: GitHubStatusEvent;
211
+ }): Promise<BufferedWebhookEvent | null> {
212
+ return fromSha({
213
+ ctx: input.ctx,
214
+ sha: input.status.sha,
215
+ eligible: shouldBufferStatus(input.status),
216
+ eventName: "status",
217
+ });
218
+ }
219
+
220
+ export function bufferedEventFromRaw(input: {
221
+ ctx: GitHubEventContext;
222
+ event: GitHubRawEvent;
223
+ }): BufferedWebhookEvent | null {
224
+ if (!shouldBufferRawEvent(input.event)) {
225
+ return null;
226
+ }
227
+ return fromConversation(input.ctx, {
228
+ eventName: input.event.eventName,
229
+ action: input.event.action,
230
+ });
231
+ }
@@ -0,0 +1,7 @@
1
+ import { defineStorage } from "@cursor/july/storage";
2
+ import { cursorHostedStorage } from "@cursor/july/storage/cursor-hosted";
3
+
4
+ export default defineStorage({
5
+ name: "cursor-hosted",
6
+ ...cursorHostedStorage(),
7
+ });
@@ -0,0 +1,22 @@
1
+ import { defineTool } from "@cursor/july/tools";
2
+ import type { ToolExecuteResult } from "@cursor/july/tools";
3
+ import { driveStartBodySchema } from "../lib/drive-schemas.js";
4
+ import { startDrive } from "../lib/start-drive.js";
5
+
6
+ export default defineTool({
7
+ description:
8
+ "Start PR autofixer on a GitHub pull request. Attaches the PR to a Cursor cloud agent and returns as soon as the session exists. bcId / agentUrl are included when already bound; otherwise they fill in from agent.bound. Triage continues asynchronously. Call whenever the user wants to drive, triage, or unblock a PR. Do not review the PR yourself from chat; there is no checkout here.",
9
+ inputSchema: driveStartBodySchema,
10
+ async execute({ pr, hint }, ctx): Promise<ToolExecuteResult> {
11
+ const result = await startDrive({
12
+ pr,
13
+ hint,
14
+ send: (message, options) => ctx.send("drive", message, options),
15
+ host: ctx.host,
16
+ });
17
+ if (!result.ok) {
18
+ return { ok: false, error: result.error, code: result.code };
19
+ }
20
+ return result;
21
+ },
22
+ });
@@ -0,0 +1,16 @@
1
+ import { defineEval } from "@cursor/july/evals";
2
+
3
+ export default defineEval({
4
+ tags: ["smoke"],
5
+ cases: [
6
+ {
7
+ id: "no-pr",
8
+ description: "Chat without a PR does not start a cloud drive.",
9
+ async test(t) {
10
+ await t.send("What can you help with?");
11
+ t.succeeded();
12
+ t.calledTool("drive_pr", { count: 0 });
13
+ },
14
+ },
15
+ ],
16
+ });
@@ -0,0 +1,5 @@
1
+ import { defineEvalConfig } from "@cursor/july/evals";
2
+
3
+ export default defineEvalConfig({
4
+ maxConcurrency: 10,
5
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "questions": [
3
+ {
4
+ "id": "repos",
5
+ "prompt": "GitHub repositories Cursor should relay SCM events for",
6
+ "hint": "Comma-separated owner/repo. One owner. Up to 20.",
7
+ "placeholder": "acme/widgets, acme/api",
8
+ "parse": "owner-repo-list"
9
+ }
10
+ ],
11
+ "replace": [
12
+ {
13
+ "file": "agent/lib/repos.ts",
14
+ "find": "[\"owner/repo\"]",
15
+ "answer": "repos",
16
+ "as": "json-array"
17
+ },
18
+ {
19
+ "file": "agent/channels/slack.ts",
20
+ "find": "acme/widgets",
21
+ "answer": "repos",
22
+ "as": "first"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "pr-autofixer",
3
+ "private": true,
4
+ "type": "module",
5
+ "description": "PR autofixer: attach a pull request to a Cursor cloud agent and keep it moving toward merge.",
6
+ "scripts": {
7
+ "check": "tsc --noEmit",
8
+ "dev": "agent-sdk dev"
9
+ },
10
+ "dependencies": {
11
+ "@cursor/july": "latest",
12
+ "zod": "^3.25.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^22.14.0",
16
+ "typescript": "^5.8.0"
17
+ }
18
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "skipLibCheck": true,
9
+ "types": ["node"]
10
+ },
11
+ "include": ["agent/**/*", "evals/**/*"]
12
+ }