@cursor/july 0.1.1 → 0.1.2

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 (199) hide show
  1. package/AGENTS.md +24 -2
  2. package/README.md +25 -15
  3. package/dist/bin/agent-serve.js +101 -12
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +31 -14
  6. package/dist/channels/github/cursor-account.d.ts +43 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -0
  8. package/dist/channels/github/cursor-account.js +95 -0
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +46 -9
  11. package/dist/channels/github/index.d.ts +2 -2
  12. package/dist/channels/github/index.js +2 -2
  13. package/dist/channels/github/types.d.ts +17 -0
  14. package/dist/channels/github/types.d.ts.map +1 -1
  15. package/dist/channels/slack/slack-channel.d.ts +8 -2
  16. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  17. package/dist/channels/slack/slack-channel.js +8 -0
  18. package/dist/channels/slack/types.d.ts +24 -3
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/channels/slack/types.js +15 -1
  21. package/dist/docs/404.html +2 -2
  22. package/dist/docs/ab.html +8 -8
  23. package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
  24. package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
  25. package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
  26. package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
  27. package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
  28. package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
  29. package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
  30. package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
  31. package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
  32. package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
  33. package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
  34. package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
  35. package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
  36. package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
  37. package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
  38. package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
  39. package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
  40. package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
  41. package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
  42. package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
  43. package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
  44. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
  45. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
  46. package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
  47. package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
  48. package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
  49. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
  50. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
  51. package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
  52. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
  53. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
  54. package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
  55. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
  56. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
  58. package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
  59. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
  60. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
  61. package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
  62. package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
  63. package/dist/docs/building-with-agents.html +4 -4
  64. package/dist/docs/concepts.html +4 -4
  65. package/dist/docs/deployment.html +7 -7
  66. package/dist/docs/evals.html +7 -7
  67. package/dist/docs/guides/agent-to-agent.html +6 -6
  68. package/dist/docs/guides/cloud-runtime.html +5 -5
  69. package/dist/docs/guides/github.html +14 -7
  70. package/dist/docs/guides/human-in-the-loop.html +6 -6
  71. package/dist/docs/guides/slack.html +8 -8
  72. package/dist/docs/guides/webhooks.html +6 -6
  73. package/dist/docs/hashmap.json +1 -1
  74. package/dist/docs/hillclimbing.html +5 -5
  75. package/dist/docs/index.html +7 -7
  76. package/dist/docs/quickstart.html +180 -23
  77. package/dist/docs/reference/agent-config.html +7 -7
  78. package/dist/docs/reference/channels.html +8 -8
  79. package/dist/docs/reference/cli.html +4 -4
  80. package/dist/docs/reference/connections.html +5 -5
  81. package/dist/docs/reference/hooks.html +5 -5
  82. package/dist/docs/reference/http-api.html +6 -6
  83. package/dist/docs/reference/instructions.html +13 -11
  84. package/dist/docs/reference/playground.html +4 -4
  85. package/dist/docs/reference/project-layout.html +4 -4
  86. package/dist/docs/reference/schedules.html +7 -7
  87. package/dist/docs/reference/sessions.html +4 -4
  88. package/dist/docs/reference/skills.html +6 -6
  89. package/dist/docs/reference/subagents.html +6 -6
  90. package/dist/docs/reference/tools.html +7 -7
  91. package/dist/docs/scaffolding-agents.html +5 -5
  92. package/dist/docs/storage.html +41 -0
  93. package/dist/docs/troubleshooting.html +4 -4
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1 -0
  97. package/dist/internal/cli-deploy.d.ts +52 -0
  98. package/dist/internal/cli-deploy.d.ts.map +1 -0
  99. package/dist/internal/cli-deploy.js +731 -0
  100. package/dist/internal/cursor/backend-client.d.ts +10 -1
  101. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  102. package/dist/internal/cursor/backend-client.js +79 -1
  103. package/dist/internal/cursor/github-credentials.d.ts +44 -0
  104. package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
  105. package/dist/internal/cursor/github-credentials.js +195 -0
  106. package/dist/internal/deploy-client.d.ts +176 -0
  107. package/dist/internal/deploy-client.d.ts.map +1 -0
  108. package/dist/internal/deploy-client.js +375 -0
  109. package/dist/internal/discovery.d.ts.map +1 -1
  110. package/dist/internal/discovery.js +73 -5
  111. package/dist/internal/distribution.d.ts.map +1 -1
  112. package/dist/internal/distribution.js +1 -0
  113. package/dist/internal/eval-run-store.d.ts +22 -3
  114. package/dist/internal/eval-run-store.d.ts.map +1 -1
  115. package/dist/internal/eval-run-store.js +37 -19
  116. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
  117. package/dist/internal/handleAgentServeTrigger.js +10 -12
  118. package/dist/internal/host-platforms.d.ts +7 -2
  119. package/dist/internal/host-platforms.d.ts.map +1 -1
  120. package/dist/internal/host-platforms.js +15 -10
  121. package/dist/internal/hosting.d.ts +37 -0
  122. package/dist/internal/hosting.d.ts.map +1 -0
  123. package/dist/internal/hosting.js +67 -0
  124. package/dist/internal/reminder-runner.d.ts +7 -0
  125. package/dist/internal/reminder-runner.d.ts.map +1 -1
  126. package/dist/internal/reminder-runner.js +50 -6
  127. package/dist/internal/reminder-store.d.ts +2 -0
  128. package/dist/internal/reminder-store.d.ts.map +1 -1
  129. package/dist/internal/reminder-store.js +18 -0
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +173 -42
  132. package/dist/internal/session-engine.d.ts +49 -0
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +246 -17
  135. package/dist/internal/storage-coordinator.d.ts +139 -0
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -0
  137. package/dist/internal/storage-coordinator.js +499 -0
  138. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  139. package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
  140. package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
  141. package/dist/playground/index.html +2 -2
  142. package/dist/storage.d.ts +204 -0
  143. package/dist/storage.d.ts.map +1 -0
  144. package/dist/storage.js +153 -0
  145. package/dist/types.d.ts +44 -3
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/README.md +3 -2
  148. package/docs/guides/github.md +43 -8
  149. package/docs/guides/slack.md +1 -1
  150. package/docs/quickstart.md +329 -51
  151. package/docs/reference/instructions.md +8 -6
  152. package/docs/scaffolding-agents.md +1 -1
  153. package/docs/storage.md +98 -0
  154. package/package.json +8 -1
  155. package/skills/github/SKILL.md +9 -1
  156. package/src/bin/agent-serve.ts +139 -0
  157. package/src/channels/github/api.ts +42 -23
  158. package/src/channels/github/cursor-account.ts +165 -0
  159. package/src/channels/github/github-channel.ts +66 -6
  160. package/src/channels/github/index.ts +2 -2
  161. package/src/channels/github/types.ts +19 -0
  162. package/src/channels/slack/slack-channel.ts +17 -3
  163. package/src/channels/slack/types.ts +44 -3
  164. package/src/index.ts +13 -0
  165. package/src/internal/cli-deploy.ts +940 -0
  166. package/src/internal/cursor/backend-client.ts +103 -1
  167. package/src/internal/cursor/github-credentials.ts +248 -0
  168. package/src/internal/deploy-client.ts +591 -0
  169. package/src/internal/discovery.ts +88 -1
  170. package/src/internal/distribution.ts +1 -0
  171. package/src/internal/eval-run-store.ts +48 -19
  172. package/src/internal/handleAgentServeTrigger.ts +10 -12
  173. package/src/internal/host-platforms.ts +28 -11
  174. package/src/internal/hosting.ts +77 -0
  175. package/src/internal/reminder-runner.ts +50 -6
  176. package/src/internal/reminder-store.ts +21 -0
  177. package/src/internal/server.ts +213 -27
  178. package/src/internal/session-engine.ts +285 -7
  179. package/src/internal/storage-coordinator.ts +615 -0
  180. package/src/storage.ts +325 -0
  181. package/src/types.ts +40 -2
  182. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
  183. package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
  184. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
  185. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
  186. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
  187. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
  188. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  189. package/dist/playground/assets/index-1K-hG-7p.css +0 -1
  190. /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
  191. /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
  192. /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
  193. /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
  194. /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
  198. /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
  199. /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
@@ -14,12 +14,21 @@ import {
14
14
  } from "node:http";
15
15
  import type { AddressInfo } from "node:net";
16
16
  import { join } from "node:path";
17
+ import {
18
+ type CursorGithubRepoScope,
19
+ createCursorAccountGithubTokenResolver,
20
+ type GithubCursorAccountRuntime,
21
+ getGithubCursorAccountRuntime,
22
+ parseCursorGithubRepoScope,
23
+ resolveCursorGithubPermissions,
24
+ } from "../channels/github/cursor-account.js";
17
25
  import { findGitHubChannelTargets } from "../channels/github/forward.js";
18
26
  import {
19
27
  dispatchCursorSlackEnvelope,
20
28
  getSlackCursorAccountRuntime,
21
29
  type SlackCursorAccountRuntime,
22
30
  } from "../channels/slack/cursor-account.js";
31
+ import { assertSlackCursorAgentName } from "../channels/slack/types.js";
23
32
  import { allowAll, bearerAuth, localDevStrict } from "../channels.js";
24
33
  import type {
25
34
  AgentProject,
@@ -45,6 +54,7 @@ import {
45
54
  resolveApiKey,
46
55
  SIGN_IN_HINT,
47
56
  } from "./cursor/credentials.js";
57
+ import { CursorGithubCredentialProvider } from "./cursor/github-credentials.js";
48
58
  import {
49
59
  CursorEventRelay,
50
60
  formatCursorScmEventLog,
@@ -76,7 +86,7 @@ import {
76
86
  collectGitHubTriggerTargets,
77
87
  handleAgentServeTrigger,
78
88
  } from "./handleAgentServeTrigger.js";
79
- import { warmupHostGitHubAuth } from "./host-platforms.js";
89
+ import { createHostPlatforms, warmupHostGitHubAuth } from "./host-platforms.js";
80
90
  import {
81
91
  buildHttpChannelRoutes,
82
92
  errorResponse,
@@ -187,9 +197,16 @@ export async function startServer(
187
197
  // agentkit_enabled gate applies to every turn.
188
198
  process.env.CURSOR_AGENTKIT_VERSION ??= packageVersion();
189
199
 
200
+ const cursorGithubTargets = collectCursorGithubTargets(mounts);
201
+ const cursorGithubConfig = resolveCursorGithubEventConfig({
202
+ configured: options.cursorEvents,
203
+ targets: cursorGithubTargets,
204
+ });
205
+ const effectiveCursorEvents = cursorGithubConfig.cursorEvents;
206
+
190
207
  // One resolution for every Cursor credential consumer (SDK runner, account
191
- // MCP bridge, SCM event relay): explicit option → CURSOR_API_KEY → stored
192
- // `agent-serve login`.
208
+ // MCP bridge, SCM event relay, GitHub credentials): explicit option →
209
+ // CURSOR_API_KEY → stored `agent-serve login`.
193
210
  const accountKey = await resolveApiKey(
194
211
  options.apiKey === undefined ? {} : { apiKey: options.apiKey }
195
212
  );
@@ -198,9 +215,53 @@ export async function startServer(
198
215
  // `cursorEvents` cannot work signed out. Require the credential before any
199
216
  // slow or stateful startup work (playground build, channel onStart, cron).
200
217
  const cursorEventsAuth =
201
- options.cursorEvents === undefined
218
+ effectiveCursorEvents === undefined
202
219
  ? undefined
203
- : requireCursorEventsAuth(options.cursorEvents, accountKey);
220
+ : requireCursorEventsAuth(effectiveCursorEvents, accountKey);
221
+
222
+ let cursorGithubCredentials: CursorGithubCredentialProvider | undefined;
223
+ if (cursorGithubConfig.scope !== undefined) {
224
+ if (cursorEventsAuth === undefined) {
225
+ throw new Error(
226
+ "githubChannel({ cursorAccount: true }) could not resolve Cursor event authentication."
227
+ );
228
+ }
229
+ if (options.allowAnonymous === true) {
230
+ throw new Error(
231
+ "githubChannel({ cursorAccount: true }) cannot be served with --allow-anonymous because admitted callers could use the signed-in account's GitHub access."
232
+ );
233
+ }
234
+ if (cursorGithubConfig.scope.permissions === "contents-write") {
235
+ logger(
236
+ '[agent-serve] warning: Cursor GitHub credentials include contents:write — the host holds a push-capable token shared with model-driven tools and untrusted webhook content; prefer permissions: "pr-write" unless this agent must push code'
237
+ );
238
+ }
239
+ const provider = new CursorGithubCredentialProvider({
240
+ client: new CursorBackendClient({
241
+ backendUrl: cursorEventsAuth.baseUrl,
242
+ apiKey: cursorEventsAuth.apiKey,
243
+ logger,
244
+ }),
245
+ scope: cursorGithubConfig.scope,
246
+ logger,
247
+ });
248
+ cursorGithubCredentials = provider;
249
+ for (const target of cursorGithubTargets) {
250
+ target.accountRuntime.credentials.bind(() => provider.getToken());
251
+ }
252
+ }
253
+ const resolvedCursorGithubCredentials = cursorGithubCredentials;
254
+ const hostPlatforms = createHostPlatforms(
255
+ resolvedCursorGithubCredentials === undefined
256
+ ? {}
257
+ : {
258
+ githubCredentials: {
259
+ token: createCursorAccountGithubTokenResolver(() =>
260
+ resolvedCursorGithubCredentials.getToken()
261
+ ),
262
+ },
263
+ }
264
+ );
204
265
 
205
266
  if (playground) {
206
267
  // Auto-build dist/playground when missing (monorepo / source checkout).
@@ -267,16 +328,42 @@ export async function startServer(
267
328
  const baseStateRoot =
268
329
  options.stateRoot ?? join(project.rootDir, ".agent-serve");
269
330
  const stateRoot = slug === "" ? baseStateRoot : join(baseStateRoot, slug);
270
- const engine = new SessionEngine({ project, stateRoot, runner, logger });
331
+ const engine = new SessionEngine({
332
+ project,
333
+ stateRoot,
334
+ runner,
335
+ logger,
336
+ platforms: hostPlatforms,
337
+ });
338
+ // Rehydrate sessions from the authored storage sink before any
339
+ // channel or route can address them (continuation tokens must resolve).
340
+ await engine.restoreStoredSessions();
341
+
271
342
  if (!githubAuthWarmed) {
272
343
  // Soft ambient GitHub auth check before channel onStart (which may call
273
344
  // host.github). Missing auth is a warning only — serve still starts.
274
- await warmupHostGitHubAuth({ logger, github: engine.host.github });
345
+ await warmupHostGitHubAuth({
346
+ logger,
347
+ github: engine.host.github,
348
+ ...(cursorGithubCredentials === undefined
349
+ ? {}
350
+ : { authSource: "cursor-account" }),
351
+ });
275
352
  githubAuthWarmed = true;
276
353
  }
277
354
  const scheduleRunner = new ScheduleRunner(engine, project, logger);
278
355
  const reminderRunner = new ReminderRunner(engine, logger);
279
- const evalRuns = new EvalRunStore(project.rootDir, logger);
356
+ const evalRuns = new EvalRunStore(
357
+ project.rootDir,
358
+ logger,
359
+ // defineStorage fallback for playground eval history; explicit
360
+ // `evals.config.ts` persistRuns still wins inside the store.
361
+ engine.storage?.asEvalRunPersistence()
362
+ );
363
+ // Eval history restores with the same startup step as sessions (reads
364
+ // also hydrate lazily, but doing it here surfaces store problems at
365
+ // boot and makes /v1/dev/evals complete from the first request).
366
+ await evalRuns.hydrate();
280
367
  engine.setReminders({
281
368
  create: (input) => reminderRunner.create(input),
282
369
  list: (filter) => reminderRunner.list(filter),
@@ -486,15 +573,6 @@ export async function startServer(
486
573
  });
487
574
  }
488
575
 
489
- // Cursor SCM events relay (opt-in: serve({ cursorEvents }) / --cursor-events).
490
- const cursorEventRelay = startCursorEventRelay({
491
- options,
492
- mounts,
493
- auth: cursorEventsAuth,
494
- logger,
495
- collectTriggerTargets,
496
- });
497
-
498
576
  // Cursor Slack relay: automatic when a mounted agent authored
499
577
  // `slackChannel({ cursorAccount: true })`. Inbound rides the account's
500
578
  // Slack connection (`/v0/slack-events`); the classic Socket-Mode path is
@@ -505,6 +583,24 @@ export async function startServer(
505
583
  accountKey,
506
584
  logger,
507
585
  });
586
+ try {
587
+ await cursorGithubCredentials?.start();
588
+ } catch (error) {
589
+ await cursorSlackRelay?.stop();
590
+ throw error;
591
+ }
592
+
593
+ // Cursor SCM events relay (opt-in: serve({ cursorEvents }) / --cursor-events).
594
+ const cursorEventRelay = startCursorEventRelay({
595
+ config: effectiveCursorEvents,
596
+ stateRoot: options.stateRoot,
597
+ mounts,
598
+ auth: cursorEventsAuth,
599
+ logger,
600
+ collectTriggerTargets,
601
+ cursorAccountOnly:
602
+ options.cursorEvents === undefined && cursorGithubTargets.length > 0,
603
+ });
508
604
 
509
605
  const server = createServer((req, res) => {
510
606
  void handleRequest(router, req, res, logger);
@@ -512,8 +608,15 @@ export async function startServer(
512
608
 
513
609
  const host = options.host ?? "127.0.0.1";
514
610
  const port = options.port ?? 3000;
515
- server.listen(port, host);
516
- await once(server, "listening");
611
+ try {
612
+ server.listen(port, host);
613
+ await once(server, "listening");
614
+ } catch (error) {
615
+ await cursorEventRelay?.stop();
616
+ await cursorSlackRelay?.stop();
617
+ cursorGithubCredentials?.stop();
618
+ throw error;
619
+ }
517
620
  const address = server.address() as AddressInfo;
518
621
  const displayHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
519
622
  const url = `http://${displayHost}:${address.port}`;
@@ -645,7 +748,11 @@ export async function startServer(
645
748
  }
646
749
  server.close();
647
750
  server.closeAllConnections();
648
- await Promise.all(runtimes.map((runtime) => runtime.engine.close()));
751
+ try {
752
+ await Promise.all(runtimes.map((runtime) => runtime.engine.close()));
753
+ } finally {
754
+ cursorGithubCredentials?.stop();
755
+ }
649
756
  },
650
757
  };
651
758
  if (playgroundDev?.url !== undefined) {
@@ -1233,14 +1340,16 @@ function requireCursorEventsAuth(
1233
1340
  * no github channel) warn and leave the relay off — serve still starts.
1234
1341
  */
1235
1342
  function startCursorEventRelay(input: {
1236
- options: ServeOptions;
1343
+ config: ServeOptions["cursorEvents"];
1344
+ stateRoot: string | undefined;
1237
1345
  mounts: AgentMount[];
1238
1346
  auth: CursorEventsAuth | undefined;
1239
1347
  logger: (line: string) => void;
1240
1348
  collectTriggerTargets: () => ReturnType<typeof collectGitHubTriggerTargets>;
1349
+ cursorAccountOnly: boolean;
1241
1350
  }): CursorEventRelay | undefined {
1242
- const { options, mounts, auth, logger, collectTriggerTargets } = input;
1243
- const config = options.cursorEvents;
1351
+ const { config, stateRoot, mounts, auth, logger, collectTriggerTargets } =
1352
+ input;
1244
1353
  if (config === undefined || auth === undefined) {
1245
1354
  return undefined;
1246
1355
  }
@@ -1262,7 +1371,7 @@ function startCursorEventRelay(input: {
1262
1371
  return undefined;
1263
1372
  }
1264
1373
  const baseStateRoot =
1265
- options.stateRoot ??
1374
+ stateRoot ??
1266
1375
  join(mounts[0]?.project.rootDir ?? process.cwd(), ".agent-serve");
1267
1376
  const relay = new CursorEventRelay({
1268
1377
  baseUrl: auth.baseUrl,
@@ -1275,12 +1384,27 @@ function startCursorEventRelay(input: {
1275
1384
  // envelope — rich pullRequest meta when present for onPullRequest Zod.
1276
1385
  dispatch: async (event) => {
1277
1386
  if (event.provider !== "github") return;
1387
+ const targets = collectTriggerTargets().filter((target) => {
1388
+ const cursorAccount = target.options.cursorAccount;
1389
+ if (
1390
+ input.cursorAccountOnly &&
1391
+ cursorAccount !== true &&
1392
+ typeof cursorAccount !== "object"
1393
+ ) {
1394
+ return false;
1395
+ }
1396
+ return typeof cursorAccount !== "object"
1397
+ ? true
1398
+ : cursorAccount.repos.some(
1399
+ (repo) => repo.toLowerCase() === event.repo.toLowerCase()
1400
+ );
1401
+ });
1278
1402
  const result = await handleAgentServeTrigger({
1279
1403
  type: "github",
1280
1404
  bodyText: JSON.stringify(scmMetadataToGitHubPayload(event)),
1281
1405
  eventName: event.eventName,
1282
1406
  deliveryId: event.deliveryId,
1283
- targets: collectTriggerTargets(),
1407
+ targets,
1284
1408
  });
1285
1409
  const outcomes = result.targets
1286
1410
  .map((target) => `${target.slug || "default"}:${target.status}`)
@@ -1297,6 +1421,64 @@ function startCursorEventRelay(input: {
1297
1421
  return relay;
1298
1422
  }
1299
1423
 
1424
+ interface CursorGithubTarget {
1425
+ slug: string;
1426
+ channelId: string;
1427
+ accountRuntime: GithubCursorAccountRuntime;
1428
+ }
1429
+
1430
+ function collectCursorGithubTargets(
1431
+ mounts: AgentMount[]
1432
+ ): CursorGithubTarget[] {
1433
+ const targets: CursorGithubTarget[] = [];
1434
+ for (const mount of mounts) {
1435
+ for (const channel of mount.project.channels) {
1436
+ const accountRuntime = getGithubCursorAccountRuntime(channel.definition);
1437
+ if (accountRuntime !== undefined) {
1438
+ targets.push({
1439
+ slug: mount.slug,
1440
+ channelId: channel.id,
1441
+ accountRuntime,
1442
+ });
1443
+ }
1444
+ }
1445
+ }
1446
+ return targets;
1447
+ }
1448
+
1449
+ interface CursorGithubConfigResolution {
1450
+ cursorEvents: ServeOptions["cursorEvents"];
1451
+ scope: CursorGithubRepoScope | undefined;
1452
+ }
1453
+
1454
+ function resolveCursorGithubEventConfig(input: {
1455
+ configured: ServeOptions["cursorEvents"];
1456
+ targets: CursorGithubTarget[];
1457
+ }): CursorGithubConfigResolution {
1458
+ if (input.targets.length === 0) {
1459
+ return { cursorEvents: input.configured, scope: undefined };
1460
+ }
1461
+ const repos = [
1462
+ ...(input.configured?.repos ?? []),
1463
+ ...input.targets.flatMap((target) => [
1464
+ ...(target.accountRuntime.scope?.repos ?? []),
1465
+ ]),
1466
+ ];
1467
+ const permissions = resolveCursorGithubPermissions(
1468
+ input.targets.map(
1469
+ (target) => target.accountRuntime.scope?.permissions ?? "pr-write"
1470
+ )
1471
+ );
1472
+ const scope = parseCursorGithubRepoScope(repos, permissions);
1473
+ return {
1474
+ cursorEvents: {
1475
+ ...input.configured,
1476
+ repos: [...scope.repos],
1477
+ },
1478
+ scope,
1479
+ };
1480
+ }
1481
+
1300
1482
  interface CursorSlackTarget {
1301
1483
  runtime: MountedRuntime;
1302
1484
  channelId: string;
@@ -1304,13 +1486,16 @@ interface CursorSlackTarget {
1304
1486
  accountRuntime: SlackCursorAccountRuntime;
1305
1487
  }
1306
1488
 
1307
- /** `weather-agent` → `Weather Agent` (Slack display name default). */
1489
+ /**
1490
+ * `weather-agent` → `WeatherAgent` (single-token Cursor Slack handle).
1491
+ * Must not contain whitespace — see {@link assertSlackCursorAgentName}.
1492
+ */
1308
1493
  function slackAgentNameFromSlug(slug: string): string {
1309
1494
  return slug
1310
1495
  .split(/[-_\s]+/)
1311
1496
  .filter((word) => word.length > 0)
1312
1497
  .map((word) => word[0].toUpperCase() + word.slice(1))
1313
- .join(" ");
1498
+ .join("");
1314
1499
  }
1315
1500
 
1316
1501
  /**
@@ -1341,6 +1526,7 @@ function collectCursorSlackTargets(
1341
1526
  `slackChannel({ cursorAccount: true }) on agent "${runtime.slug || runtime.project.name}" resolved an empty agent name; set agentName.`
1342
1527
  );
1343
1528
  }
1529
+ assertSlackCursorAgentName(agentName);
1344
1530
  const nameKey = agentName.toLowerCase();
1345
1531
  const existing = byName.get(nameKey);
1346
1532
  if (existing !== undefined) {