@cursor/july 0.1.105 → 0.1.107

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 (217) hide show
  1. package/dist/bin/agent-serve.js +1 -8
  2. package/dist/channels/bitbucket/types.d.ts +2 -4
  3. package/dist/channels/bitbucket/types.d.ts.map +1 -1
  4. package/dist/channels/deployments/types.d.ts +5 -8
  5. package/dist/channels/deployments/types.d.ts.map +1 -1
  6. package/dist/channels/github/types.d.ts +2 -4
  7. package/dist/channels/github/types.d.ts.map +1 -1
  8. package/dist/channels/gitlab/types.d.ts +2 -5
  9. package/dist/channels/gitlab/types.d.ts.map +1 -1
  10. package/dist/channels/origin/types.d.ts +2 -3
  11. package/dist/channels/origin/types.d.ts.map +1 -1
  12. package/dist/channels/slack/defaults.d.ts.map +1 -1
  13. package/dist/channels/slack/defaults.js +3 -0
  14. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  15. package/dist/channels/slack/dispatch.js +1 -1
  16. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  17. package/dist/channels/slack/live-delivery.js +48 -11
  18. package/dist/channels/slack/types.d.ts +4 -3
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/docs/404.html +3 -3
  21. package/dist/docs/ab.html +6 -6
  22. package/dist/docs/assets/{app.BVwaffdK.js → app.CtuckIiL.js} +1 -1
  23. package/dist/docs/assets/chunks/@localSearchIndexroot.DXXZxiMv.js +1 -0
  24. package/dist/docs/assets/chunks/{VPLocalSearchBox.WuWTZ1x8.js → VPLocalSearchBox.Bkkhnu8K.js} +2 -2
  25. package/dist/docs/assets/chunks/{theme.BYdI_6sF.js → theme.DhpIe0Pa.js} +2 -2
  26. package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.js → guides_opentelemetry.md.SCRMskno.js} +3 -3
  27. package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.lean.js → guides_opentelemetry.md.SCRMskno.lean.js} +1 -1
  28. package/dist/docs/assets/{reference_channels.md.DBGcaB7m.js → reference_channels.md.D-qTqwcq.js} +2 -2
  29. package/dist/docs/assets/{reference_channels.md.DBGcaB7m.lean.js → reference_channels.md.D-qTqwcq.lean.js} +1 -1
  30. package/dist/docs/assets/{reference_cli.md.C6Lo8SEd.js → reference_cli.md.Dm67hd2D.js} +1 -1
  31. package/dist/docs/assets/{skills_otel.md.CqhobH3B.js → skills_otel.md.B5Ddv1xH.js} +1 -1
  32. package/dist/docs/assets/{style.BRuM8477.css → style.DCD_zyg3.css} +1 -1
  33. package/dist/docs/building-with-agents.html +6 -6
  34. package/dist/docs/concepts.html +5 -5
  35. package/dist/docs/deployment.html +6 -6
  36. package/dist/docs/evals.html +6 -6
  37. package/dist/docs/guides/agent-to-agent.html +6 -6
  38. package/dist/docs/guides/cloud-agents.html +6 -6
  39. package/dist/docs/guides/convert-automation.html +6 -6
  40. package/dist/docs/guides/github.html +6 -6
  41. package/dist/docs/guides/grokbot-agents.html +6 -6
  42. package/dist/docs/guides/human-in-the-loop.html +6 -6
  43. package/dist/docs/guides/mcp-oauth.html +6 -6
  44. package/dist/docs/guides/opentelemetry.html +8 -8
  45. package/dist/docs/guides/opentelemetry.md +3 -4
  46. package/dist/docs/guides/slack.html +6 -6
  47. package/dist/docs/guides/webhooks.html +6 -6
  48. package/dist/docs/hashmap.json +1 -1
  49. package/dist/docs/hillclimbing.html +6 -6
  50. package/dist/docs/index.html +6 -6
  51. package/dist/docs/llms-full.txt +22 -28
  52. package/dist/docs/quickstart.html +6 -6
  53. package/dist/docs/reference/agent-config.html +6 -6
  54. package/dist/docs/reference/artifacts.html +6 -6
  55. package/dist/docs/reference/channels.html +7 -7
  56. package/dist/docs/reference/channels.md +11 -19
  57. package/dist/docs/reference/cli.html +8 -8
  58. package/dist/docs/reference/cli.md +5 -2
  59. package/dist/docs/reference/connections.html +6 -6
  60. package/dist/docs/reference/extensions.html +6 -6
  61. package/dist/docs/reference/hooks.html +6 -6
  62. package/dist/docs/reference/http-api.html +6 -6
  63. package/dist/docs/reference/instructions.html +6 -6
  64. package/dist/docs/reference/playground.html +5 -5
  65. package/dist/docs/reference/project-layout.html +6 -6
  66. package/dist/docs/reference/prompt.html +6 -6
  67. package/dist/docs/reference/result.html +6 -6
  68. package/dist/docs/reference/schedules.html +6 -6
  69. package/dist/docs/reference/sessions.html +5 -5
  70. package/dist/docs/reference/skills.html +6 -6
  71. package/dist/docs/reference/subagents.html +6 -6
  72. package/dist/docs/reference/tools.html +6 -6
  73. package/dist/docs/scaffolding-agents.html +5 -5
  74. package/dist/docs/skills/ab.html +6 -6
  75. package/dist/docs/skills/create-agent.html +6 -6
  76. package/dist/docs/skills/debug.html +5 -5
  77. package/dist/docs/skills/evals.html +6 -6
  78. package/dist/docs/skills/framework-map.html +5 -5
  79. package/dist/docs/skills/github.html +6 -6
  80. package/dist/docs/skills/hillclimb.html +6 -6
  81. package/dist/docs/skills/index.html +5 -5
  82. package/dist/docs/skills/mcp-auth.html +6 -6
  83. package/dist/docs/skills/otel.html +8 -8
  84. package/dist/docs/skills/otel.md +3 -3
  85. package/dist/docs/skills/setup-slack.html +6 -6
  86. package/dist/docs/storage.html +6 -6
  87. package/dist/docs/templates/agentic-owners.html +6 -6
  88. package/dist/docs/templates/agents-md.html +6 -6
  89. package/dist/docs/templates/code-wiki.html +6 -6
  90. package/dist/docs/templates/demo.html +6 -6
  91. package/dist/docs/templates/grokbot-agents.html +6 -6
  92. package/dist/docs/templates/pr-autofixer.html +6 -6
  93. package/dist/docs/templates/security-help.html +6 -6
  94. package/dist/docs/templates/security-reviewer.html +6 -6
  95. package/dist/docs/templates/triage.html +6 -6
  96. package/dist/docs/troubleshooting.html +5 -5
  97. package/dist/index.d.ts +4 -5
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +3 -4
  100. package/dist/internal/cli-deploy.d.ts.map +1 -1
  101. package/dist/internal/cli-deploy.js +23 -11
  102. package/dist/internal/cursor/backend-client.d.ts +25 -0
  103. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  104. package/dist/internal/cursor/backend-client.js +57 -2
  105. package/dist/internal/deploy-client.d.ts +8 -0
  106. package/dist/internal/deploy-client.d.ts.map +1 -1
  107. package/dist/internal/deploy-client.js +14 -0
  108. package/dist/internal/deploy-manifest.d.ts +3 -0
  109. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  110. package/dist/internal/deploy-manifest.js +15 -12
  111. package/dist/internal/discovery/agent.d.ts.map +1 -1
  112. package/dist/internal/discovery/agent.js +1 -0
  113. package/dist/internal/discovery/info.d.ts.map +1 -1
  114. package/dist/internal/discovery/info.js +7 -3
  115. package/dist/internal/discovery/modules.d.ts +1 -1
  116. package/dist/internal/discovery/modules.d.ts.map +1 -1
  117. package/dist/internal/discovery/modules.js +1 -0
  118. package/dist/internal/discovery/project.d.ts.map +1 -1
  119. package/dist/internal/discovery/project.js +3 -0
  120. package/dist/internal/discovery/reminders.d.ts +7 -0
  121. package/dist/internal/discovery/reminders.d.ts.map +1 -0
  122. package/dist/internal/discovery/reminders.js +65 -0
  123. package/dist/internal/discovery/subagent.js +1 -0
  124. package/dist/internal/distribution.d.ts +5 -0
  125. package/dist/internal/distribution.d.ts.map +1 -1
  126. package/dist/internal/distribution.js +17 -0
  127. package/dist/internal/framework-storage-selection.d.ts +14 -3
  128. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  129. package/dist/internal/framework-storage-selection.js +36 -6
  130. package/dist/internal/guest-network.d.ts.map +1 -1
  131. package/dist/internal/guest-network.js +2 -0
  132. package/dist/internal/hosted-delivery-protocol.d.ts +48 -0
  133. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  134. package/dist/internal/hosted-delivery-protocol.js +60 -0
  135. package/dist/internal/hosted-delivery.d.ts +10 -0
  136. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  137. package/dist/internal/hosted-delivery.js +83 -18
  138. package/dist/internal/local-control-plane.d.ts +18 -2
  139. package/dist/internal/local-control-plane.d.ts.map +1 -1
  140. package/dist/internal/local-control-plane.js +199 -17
  141. package/dist/internal/reminder-control-plane-protocol.d.ts +187 -0
  142. package/dist/internal/reminder-control-plane-protocol.d.ts.map +1 -0
  143. package/dist/internal/reminder-control-plane-protocol.js +244 -0
  144. package/dist/internal/reminder-runner.d.ts +48 -6
  145. package/dist/internal/reminder-runner.d.ts.map +1 -1
  146. package/dist/internal/reminder-runner.js +336 -48
  147. package/dist/internal/reminder-schedule.d.ts +5 -2
  148. package/dist/internal/reminder-schedule.d.ts.map +1 -1
  149. package/dist/internal/reminder-schedule.js +8 -6
  150. package/dist/internal/reminder-store.d.ts +24 -7
  151. package/dist/internal/reminder-store.d.ts.map +1 -1
  152. package/dist/internal/reminder-store.js +6 -5
  153. package/dist/internal/request-error-status.d.ts +2 -0
  154. package/dist/internal/request-error-status.d.ts.map +1 -1
  155. package/dist/internal/request-error-status.js +22 -3
  156. package/dist/internal/scm/pr-url.d.ts +40 -0
  157. package/dist/internal/scm/pr-url.d.ts.map +1 -1
  158. package/dist/internal/scm/pr-url.js +93 -14
  159. package/dist/internal/server.d.ts.map +1 -1
  160. package/dist/internal/server.js +67 -27
  161. package/dist/internal/session-engine.d.ts +4 -0
  162. package/dist/internal/session-engine.d.ts.map +1 -1
  163. package/dist/internal/session-engine.js +5 -0
  164. package/dist/playground/assets/{index-BaQzjaNK.js → index-Bq2HpEQB.js} +23 -23
  165. package/dist/playground/assets/index-CZKKNlmb.css +1 -0
  166. package/dist/playground/index.html +2 -2
  167. package/dist/reminders.d.ts +24 -5
  168. package/dist/reminders.d.ts.map +1 -1
  169. package/dist/reminders.js +28 -5
  170. package/dist/types.d.ts +89 -20
  171. package/dist/types.d.ts.map +1 -1
  172. package/docs/guides/opentelemetry.md +3 -4
  173. package/docs/reference/channels.md +11 -19
  174. package/docs/reference/cli.md +5 -2
  175. package/package.json +3 -3
  176. package/skills/otel/SKILL.md +3 -3
  177. package/src/bin/agent-serve.ts +1 -9
  178. package/src/channels/bitbucket/types.ts +2 -4
  179. package/src/channels/deployments/types.ts +5 -8
  180. package/src/channels/github/types.ts +2 -4
  181. package/src/channels/gitlab/types.ts +2 -5
  182. package/src/channels/origin/types.ts +2 -3
  183. package/src/channels/slack/defaults.ts +3 -0
  184. package/src/channels/slack/dispatch.ts +1 -0
  185. package/src/channels/slack/live-delivery.ts +53 -12
  186. package/src/channels/slack/types.ts +4 -3
  187. package/src/index.ts +6 -3
  188. package/src/internal/cli-deploy.ts +30 -12
  189. package/src/internal/cursor/backend-client.ts +90 -2
  190. package/src/internal/deploy-client.ts +16 -0
  191. package/src/internal/deploy-manifest.ts +16 -11
  192. package/src/internal/discovery/agent.ts +1 -0
  193. package/src/internal/discovery/info.ts +6 -0
  194. package/src/internal/discovery/modules.ts +2 -4
  195. package/src/internal/discovery/project.ts +3 -0
  196. package/src/internal/discovery/reminders.ts +65 -0
  197. package/src/internal/discovery/subagent.ts +1 -0
  198. package/src/internal/distribution.ts +20 -0
  199. package/src/internal/framework-storage-selection.ts +43 -6
  200. package/src/internal/guest-network.ts +3 -0
  201. package/src/internal/hosted-delivery-protocol.ts +96 -0
  202. package/src/internal/hosted-delivery.ts +154 -27
  203. package/src/internal/local-control-plane.ts +281 -22
  204. package/src/internal/reminder-control-plane-protocol.ts +499 -0
  205. package/src/internal/reminder-runner.ts +421 -48
  206. package/src/internal/reminder-schedule.ts +11 -6
  207. package/src/internal/reminder-store.ts +27 -7
  208. package/src/internal/request-error-status.ts +27 -3
  209. package/src/internal/scm/pr-url.ts +167 -20
  210. package/src/internal/server.ts +69 -16
  211. package/src/internal/session-engine.ts +5 -0
  212. package/src/reminders.ts +35 -4
  213. package/src/types.ts +95 -20
  214. package/dist/docs/assets/chunks/@localSearchIndexroot.CJxMklAy.js +0 -1
  215. package/dist/playground/assets/index-CceqnLTX.css +0 -1
  216. /package/dist/docs/assets/{reference_cli.md.C6Lo8SEd.lean.js → reference_cli.md.Dm67hd2D.lean.js} +0 -0
  217. /package/dist/docs/assets/{skills_otel.md.CqhobH3B.lean.js → skills_otel.md.B5Ddv1xH.lean.js} +0 -0
@@ -23,8 +23,14 @@ export interface ResolvedSchedule {
23
23
  nextFireAtMs: number;
24
24
  }
25
25
 
26
- /** Resolve create-input schedule fields into a concrete next-fire plan. */
27
- export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
26
+ /**
27
+ * Resolve create-input schedule fields into a concrete next-fire plan.
28
+ * Pass `nowMs` for deterministic CP / test resolution; CP owns this call.
29
+ */
30
+ export function resolveSchedule(
31
+ input: Pick<ReminderCreateInput, "every" | "cron" | "delay" | "at">,
32
+ nowMs: number = Date.now()
33
+ ): ResolvedSchedule {
28
34
  const shapes = [
29
35
  input.every !== undefined,
30
36
  input.cron !== undefined,
@@ -37,7 +43,6 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
37
43
  );
38
44
  }
39
45
 
40
- const now = Date.now();
41
46
  if (input.every !== undefined) {
42
47
  const intervalMs = parseDuration(input.every);
43
48
  if (intervalMs <= 0) {
@@ -49,7 +54,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
49
54
  kind: "every",
50
55
  value: String(input.every),
51
56
  intervalMs,
52
- nextFireAtMs: now + intervalMs,
57
+ nextFireAtMs: nowMs + intervalMs,
53
58
  };
54
59
  }
55
60
  if (input.cron !== undefined) {
@@ -57,7 +62,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
57
62
  return {
58
63
  kind: "cron",
59
64
  value: input.cron,
60
- nextFireAtMs: nextCronFireMs(input.cron, now),
65
+ nextFireAtMs: nextCronFireMs(input.cron, nowMs),
61
66
  };
62
67
  }
63
68
  if (input.delay !== undefined) {
@@ -65,7 +70,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
65
70
  return {
66
71
  kind: "delay",
67
72
  value: String(input.delay),
68
- nextFireAtMs: now + delayMs,
73
+ nextFireAtMs: nowMs + delayMs,
69
74
  };
70
75
  }
71
76
  const atMs =
@@ -2,10 +2,10 @@
2
2
  * Reminder document store used by the local control plane
3
3
  * (`<stateRoot>/control-plane/reminders/`).
4
4
  *
5
- * Handlers (`run` / function `until`) are not serializable — they live only
6
- * in the in-memory {@link ReminderRunner} registry. The control plane holds
7
- * schedule, prompt, target, and status so prompt-form reminders survive
8
- * restart.
5
+ * Anonymous `run` / function `until` handlers are not serializable — they live
6
+ * only in the in-memory {@link ReminderRunner} registry. Named `handler` +
7
+ * `args` and prompt-form reminders survive restart (handlers resolve from
8
+ * `agent/reminders/` on every fire).
9
9
  */
10
10
 
11
11
  import {
@@ -18,6 +18,7 @@ import {
18
18
  } from "node:fs/promises";
19
19
  import { join } from "node:path";
20
20
  import type {
21
+ JsonObject,
21
22
  ReminderBusyPolicy,
22
23
  ReminderScheduleKind,
23
24
  ReminderStatus,
@@ -31,6 +32,12 @@ export type {
31
32
  ReminderStatus,
32
33
  } from "../types.js";
33
34
 
35
+ /**
36
+ * Internal record status. `firing` is CP claim state and must not appear on
37
+ * public {@link ReminderStatus} / {@link ReminderInfo}.
38
+ */
39
+ export type ReminderRecordStatus = ReminderStatus | "firing";
40
+
34
41
  /** Serializable reminder record (no function bodies). */
35
42
  export interface ReminderRecord {
36
43
  version: typeof REMINDER_SNAPSHOT_VERSION;
@@ -46,8 +53,15 @@ export interface ReminderRecord {
46
53
  /** Raw schedule value: every/delay string, cron expression, or ISO `at`. */
47
54
  scheduleValue: string;
48
55
  intervalMs?: number;
49
- /** Prompt form — mutually exclusive with in-memory `run`. */
56
+ /** Prompt form — mutually exclusive with `handler` and in-memory `run`. */
50
57
  prompt?: string;
58
+ /**
59
+ * Named handler stem (`agent/reminders/<name>.ts`). Mutually exclusive with
60
+ * `prompt` and in-memory `run`.
61
+ */
62
+ handler?: string;
63
+ /** Serializable args for {@link handler}. */
64
+ args?: JsonObject;
51
65
  /**
52
66
  * True when create() registered a `run` handler. Handlers are not
53
67
  * serializable; after restart, armed records with this flag and no live
@@ -59,9 +73,14 @@ export interface ReminderRecord {
59
73
  /** Natural-language until (agent-facing); function until is in-memory only. */
60
74
  untilPrompt?: string;
61
75
  busyPolicy: ReminderBusyPolicy;
62
- status: ReminderStatus;
76
+ status: ReminderRecordStatus;
63
77
  disarmReason?: string;
64
78
  fireCount: number;
79
+ /**
80
+ * CAS generation. Cancel/rekey and successful outcome apply bump this so
81
+ * in-flight outcomes cannot resurrect a superseded reminder.
82
+ */
83
+ generation?: number;
65
84
  nextFireAtMs: number;
66
85
  createdAt: string;
67
86
  updatedAt: string;
@@ -120,7 +139,8 @@ export class ReminderStore {
120
139
 
121
140
  async listArmed(): Promise<ReminderRecord[]> {
122
141
  const all = await this.list();
123
- return all.filter((r) => r.status === "armed");
142
+ // Treat in-flight claims as still armed for restart / listing purposes.
143
+ return all.filter((r) => r.status === "armed" || r.status === "firing");
124
144
  }
125
145
 
126
146
  async get(id: string): Promise<ReminderRecord | undefined> {
@@ -7,12 +7,18 @@ import {
7
7
  hasGuestNetworkErrno,
8
8
  isQuietGuestConnectStall,
9
9
  } from "./guest-network.js";
10
+ import {
11
+ HOSTED_GUEST_RETRY_TERMINAL,
12
+ HOSTED_GUEST_RETRY_TRANSIENT,
13
+ type HostedGuestRetryDisposition,
14
+ } from "./hosted-delivery-protocol.js";
10
15
  import { SessionAuthError, SessionBusyError } from "./session-engine.js";
11
16
 
12
17
  export interface MappedRequestError {
13
18
  status: 400 | 403 | 409 | 503;
14
19
  code: string;
15
20
  message: string;
21
+ retry: HostedGuestRetryDisposition;
16
22
  }
17
23
 
18
24
  /**
@@ -23,23 +29,39 @@ export function mapThrownRequestError(
23
29
  error: unknown
24
30
  ): MappedRequestError | undefined {
25
31
  if (error instanceof SessionBusyError) {
26
- return { status: 409, code: "session_busy", message: error.message };
32
+ return {
33
+ status: 409,
34
+ code: "session_busy",
35
+ message: error.message,
36
+ retry: HOSTED_GUEST_RETRY_TRANSIENT,
37
+ };
27
38
  }
28
39
  if (error instanceof V2EngineError) {
29
40
  return {
30
41
  status: 409,
31
42
  code: "v2_identity_conflict",
32
43
  message: "Control-plane session identity conflicts with existing state.",
44
+ retry: HOSTED_GUEST_RETRY_TERMINAL,
33
45
  };
34
46
  }
35
47
  if (error instanceof SessionAuthError) {
36
- return { status: 403, code: "forbidden", message: error.message };
48
+ return {
49
+ status: 403,
50
+ code: "forbidden",
51
+ message: error.message,
52
+ retry: HOSTED_GUEST_RETRY_TERMINAL,
53
+ };
37
54
  }
38
55
  if (
39
56
  error instanceof InvalidAsOfError ||
40
57
  error instanceof VacuousTimeArgsError
41
58
  ) {
42
- return { status: 400, code: "invalid_request", message: error.message };
59
+ return {
60
+ status: 400,
61
+ code: "invalid_request",
62
+ message: error.message,
63
+ retry: HOSTED_GUEST_RETRY_TERMINAL,
64
+ };
43
65
  }
44
66
  if (
45
67
  error instanceof GuestNetworkUnavailableError ||
@@ -52,6 +74,7 @@ export function mapThrownRequestError(
52
74
  error instanceof Error
53
75
  ? error.message
54
76
  : "Guest network is not ready for outbound requests.",
77
+ retry: HOSTED_GUEST_RETRY_TRANSIENT,
55
78
  };
56
79
  }
57
80
  if (isQuietGuestConnectStall(error)) {
@@ -62,6 +85,7 @@ export function mapThrownRequestError(
62
85
  error instanceof Error
63
86
  ? error.message
64
87
  : "Guest Connect stalled without a network errno.",
88
+ retry: HOSTED_GUEST_RETRY_TRANSIENT,
65
89
  };
66
90
  }
67
91
  return undefined;
@@ -1,10 +1,21 @@
1
1
  /**
2
2
  * Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
3
3
  * humans paste: cursor.com/codebase, review.cursor.com/pr, origin.cursor.com.
4
+ *
5
+ * {@link parseScmPrUrl} is the wider parse the review seams use: it also
6
+ * recognizes GitLab merge requests (`/-/merge_requests/<iid>` on any host),
7
+ * Bitbucket Cloud / Data Center pull requests, and GitHub Enterprise Server
8
+ * (`/<owner>/<repo>/pull/<n>` on a host that is none of the above). The
9
+ * narrow {@link parsePrUrl} keeps its github.com + Origin contract for the
10
+ * free-text callers (Slack `pr-ref`) where "any host with /pull/N" would be
11
+ * too eager.
4
12
  */
5
13
 
6
14
  export type PrProvider = "github" | "origin";
7
15
 
16
+ /** Every SCM the review seams can address. */
17
+ export type ScmPrProvider = PrProvider | "gitlab" | "bitbucket";
18
+
8
19
  export type ParsedPrUrl = {
9
20
  provider: PrProvider;
10
21
  owner: string;
@@ -13,6 +24,27 @@ export type ParsedPrUrl = {
13
24
  url: string;
14
25
  };
15
26
 
27
+ export type ParsedScmPrUrl = {
28
+ provider: ScmPrProvider;
29
+ /**
30
+ * Namespace half of the repo: GitHub owner, GitLab group path (nested
31
+ * groups joined with `/`), Bitbucket workspace / project key (`~user`
32
+ * for Data Center personal repos).
33
+ */
34
+ owner: string;
35
+ repo: string;
36
+ number: number;
37
+ /** Canonical PR page URL. */
38
+ url: string;
39
+ /**
40
+ * Hostname the PR lives on (lowercase). `github.com` for dotcom, the
41
+ * GHES hostname for GitHub Enterprise, `gitlab.com` or the self-managed
42
+ * host for GitLab, `bitbucket.org` or the Data Center host for Bitbucket.
43
+ * Origin PRs report the Origin page host.
44
+ */
45
+ host: string;
46
+ };
47
+
16
48
  const ORIGIN_PAGE = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
17
49
 
18
50
  function positiveInt(raw: string | undefined): number | undefined {
@@ -27,19 +59,28 @@ function positiveInt(raw: string | undefined): number | undefined {
27
59
  }
28
60
 
29
61
  function parsed(
30
- provider: PrProvider,
62
+ provider: ScmPrProvider,
63
+ host: string,
31
64
  owner: string | undefined,
32
65
  repo: string | undefined,
33
- number: number | undefined
34
- ): ParsedPrUrl | undefined {
35
- if (owner === undefined || repo === undefined || number === undefined) {
66
+ number: number | undefined,
67
+ url?: string
68
+ ): ParsedScmPrUrl | undefined {
69
+ if (
70
+ owner === undefined ||
71
+ owner === "" ||
72
+ repo === undefined ||
73
+ repo === "" ||
74
+ number === undefined
75
+ ) {
36
76
  return undefined;
37
77
  }
38
- const url =
39
- provider === "origin"
78
+ const canonical =
79
+ url ??
80
+ (provider === "origin"
40
81
  ? `https://cursor.com/codebase/${owner}/${repo}/pull/${number}`
41
- : `https://github.com/${owner}/${repo}/pull/${number}`;
42
- return { provider, owner, repo, number, url };
82
+ : `https://${host}/${owner}/${repo}/pull/${number}`);
83
+ return { provider, owner, repo, number, url: canonical, host };
43
84
  }
44
85
 
45
86
  /** Strip Slack mrkdwn wrappers and trailing punctuation before `URL`. */
@@ -50,27 +91,49 @@ function normalizeRaw(raw: string): string {
50
91
  .replace(/[>\].,;]+$/, "");
51
92
  }
52
93
 
94
+ function isGithubDotCom(host: string): boolean {
95
+ return host === "github.com" || host === "www.github.com";
96
+ }
97
+
98
+ /** github.com and Origin only — the contract free-text PR references use. */
53
99
  export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
100
+ const wide = parseScmPrUrl(raw);
101
+ if (wide === undefined) {
102
+ return undefined;
103
+ }
104
+ if (wide.provider === "origin") {
105
+ return { ...wide, provider: "origin" };
106
+ }
107
+ if (wide.provider === "github" && isGithubDotCom(wide.host)) {
108
+ return { ...wide, provider: "github" };
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ /**
114
+ * Every provider shape, including GitHub Enterprise Server. The GitLab and
115
+ * Bitbucket path shapes are unambiguous on any host; a plain
116
+ * `/<owner>/<repo>/pull/<n>` on a host that is not github.com, Origin,
117
+ * GitLab, or Bitbucket is taken as GHES (the only remaining forge with that
118
+ * route). Callers that must not accept arbitrary hosts use
119
+ * {@link parsePrUrl}.
120
+ */
121
+ export function parseScmPrUrl(raw: string): ParsedScmPrUrl | undefined {
54
122
  let url: URL;
55
123
  try {
56
124
  url = new URL(normalizeRaw(raw));
57
125
  } catch {
58
126
  return undefined;
59
127
  }
128
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
129
+ return undefined;
130
+ }
60
131
 
61
132
  const parts = url.pathname.split("/").filter(Boolean);
62
133
  const host = url.hostname.toLowerCase();
63
134
 
64
- if (
65
- (host === "github.com" || host === "www.github.com") &&
66
- parts.length >= 4 &&
67
- parts[2] === "pull"
68
- ) {
69
- return parsed("github", parts[0], parts[1], positiveInt(parts[3]));
70
- }
71
-
72
135
  if (ORIGIN_PAGE.test(host) && parts.length >= 4 && parts[2] === "github") {
73
- return parsed("origin", parts[0], parts[1], positiveInt(parts[3]));
136
+ return parsed("origin", host, parts[0], parts[1], positiveInt(parts[3]));
74
137
  }
75
138
 
76
139
  if (
@@ -79,17 +142,101 @@ export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
79
142
  parts.length >= 5 &&
80
143
  parts[3] === "pull"
81
144
  ) {
82
- return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
145
+ return parsed("origin", host, parts[1], parts[2], positiveInt(parts[4]));
83
146
  }
84
147
 
85
148
  if (host === "review.cursor.com" && parts[0] === "pr") {
86
149
  if (parts.length >= 5 && parts[3] === "github") {
87
- return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
150
+ return parsed("origin", host, parts[1], parts[2], positiveInt(parts[4]));
88
151
  }
89
152
  if (parts.length >= 4) {
90
- return parsed("origin", parts[1], parts[2], positiveInt(parts[3]));
153
+ return parsed("origin", host, parts[1], parts[2], positiveInt(parts[3]));
91
154
  }
92
155
  }
93
156
 
157
+ // GitLab MR page: /<group>/[<subgroup>/…]<project>/-/merge_requests/<iid>
158
+ const separator = parts.indexOf("-");
159
+ if (
160
+ separator >= 2 &&
161
+ parts[separator + 1] === "merge_requests" &&
162
+ parts.length >= separator + 3
163
+ ) {
164
+ const owner = parts.slice(0, separator - 1).join("/");
165
+ const repo = parts[separator - 1];
166
+ const number = positiveInt(parts[separator + 2]);
167
+ return parsed(
168
+ "gitlab",
169
+ host,
170
+ owner,
171
+ repo,
172
+ number,
173
+ number === undefined
174
+ ? undefined
175
+ : `https://${host}/${owner}/${repo}/-/merge_requests/${number}`
176
+ );
177
+ }
178
+
179
+ // Bitbucket Cloud: bitbucket.org/<workspace>/<slug>/pull-requests/<id>
180
+ if (
181
+ (host === "bitbucket.org" || host === "www.bitbucket.org") &&
182
+ parts.length >= 4 &&
183
+ parts[2] === "pull-requests"
184
+ ) {
185
+ const number = positiveInt(parts[3]);
186
+ return parsed(
187
+ "bitbucket",
188
+ "bitbucket.org",
189
+ parts[0],
190
+ parts[1],
191
+ number,
192
+ number === undefined
193
+ ? undefined
194
+ : `https://bitbucket.org/${parts[0]}/${parts[1]}/pull-requests/${number}`
195
+ );
196
+ }
197
+
198
+ // Bitbucket Data Center: [/<context>]/projects/<KEY>/repos/<slug>/pull-requests/<id>
199
+ // (personal repos: /users/<name>/repos/<slug>/…, full name `~name/slug`).
200
+ const dc = parts.findIndex(
201
+ (part, index) =>
202
+ (part === "projects" || part === "users") &&
203
+ parts[index + 2] === "repos" &&
204
+ parts[index + 4] === "pull-requests"
205
+ );
206
+ if (dc !== -1 && parts.length >= dc + 6) {
207
+ const number = positiveInt(parts[dc + 5]);
208
+ const owner = parts[dc] === "users" ? `~${parts[dc + 1]}` : parts[dc + 1];
209
+ const slug = parts[dc + 3];
210
+ const context = parts.slice(0, dc).join("/");
211
+ return parsed(
212
+ "bitbucket",
213
+ host,
214
+ owner,
215
+ slug,
216
+ number,
217
+ number === undefined
218
+ ? undefined
219
+ : `${url.origin}/${context === "" ? "" : `${context}/`}${parts.slice(dc, dc + 4).join("/")}/pull-requests/${number}`
220
+ );
221
+ }
222
+
223
+ // GitHub (dotcom or Enterprise Server): /<owner>/<repo>/pull/<n>. Checked
224
+ // last so the forge-specific shapes above win on shared hosts.
225
+ if (
226
+ parts.length >= 4 &&
227
+ parts[2] === "pull" &&
228
+ !ORIGIN_PAGE.test(host) &&
229
+ !/(^|\.)cursor\.com$/.test(host)
230
+ ) {
231
+ const githubHost = isGithubDotCom(host) ? "github.com" : host;
232
+ return parsed(
233
+ "github",
234
+ githubHost,
235
+ parts[0],
236
+ parts[1],
237
+ positiveInt(parts[3])
238
+ );
239
+ }
240
+
94
241
  return undefined;
95
242
  }
@@ -129,6 +129,11 @@ import {
129
129
  } from "./handleAgentServeTrigger.js";
130
130
  import { createHostPlatforms, warmupHostGitHubAuth } from "./host-platforms.js";
131
131
  import { registerHostedDeliveryRoute } from "./hosted-delivery.js";
132
+ import {
133
+ HOSTED_DELIVERY_HEADER,
134
+ HOSTED_GUEST_RETRY_TERMINAL,
135
+ hostedGuestErrorResponse,
136
+ } from "./hosted-delivery-protocol.js";
132
137
  import { hostedErrorStackSnippet } from "./hosted-execution-diag.js";
133
138
  import { flushHostedExecutionProcessDeath } from "./hosted-execution-flush.js";
134
139
  import {
@@ -191,6 +196,7 @@ import {
191
196
  import {
192
197
  ReminderRunner,
193
198
  ReminderValidationError,
199
+ rejectAnonymousReminderHandlers,
194
200
  UnknownReminderError,
195
201
  } from "./reminder-runner.js";
196
202
  import { mapThrownRequestError } from "./request-error-status.js";
@@ -319,12 +325,9 @@ export async function startServer(
319
325
  noControlPlane?: boolean;
320
326
  /** `--cursor-scm-proxy`: every cursorAccount SCM pack routes via the backend forwarder. */
321
327
  cursorScmProxy?: boolean;
322
- /** Deprecated alias (`--cursor-github-proxy`); same effect as cursorScmProxy. */
323
- cursorGithubProxy?: boolean;
324
328
  };
325
329
  const noControlPlane = hosted.noControlPlane === true;
326
- const scmProxyRequested =
327
- hosted.cursorScmProxy === true || hosted.cursorGithubProxy === true;
330
+ const scmProxyRequested = hosted.cursorScmProxy === true;
328
331
  const runSchedules = options.schedules ?? !dev;
329
332
  const runReminders = options.reminders ?? !dev;
330
333
 
@@ -846,12 +849,23 @@ export async function startServer(
846
849
  }
847
850
  const scheduleRunner = new ScheduleRunner(engine, project, logger);
848
851
  let reminderRunner!: ReminderRunner;
852
+ const architecture = project.agent.architecture;
853
+ const useReminderCpProtocol = !noControlPlane && architecture === "v2";
849
854
  const localControlPlane = noControlPlane
850
855
  ? undefined
851
856
  : await createLocalControlPlane({
852
857
  fireSchedule: (name) => scheduleRunner.dispatch(name),
853
- fireReminder: (id, fireCount) =>
854
- reminderRunner.dispatch(id, { expectedFireCount: fireCount }),
858
+ ...(useReminderCpProtocol
859
+ ? {
860
+ executeReminder: (descriptor) =>
861
+ reminderRunner.executeFire(descriptor),
862
+ }
863
+ : {
864
+ fireReminder: (id, fireCount) =>
865
+ reminderRunner.dispatch(id, {
866
+ expectedFireCount: fireCount,
867
+ }),
868
+ }),
855
869
  stateDir: join(stateRoot, "control-plane"),
856
870
  armReminderClocks: runReminders,
857
871
  logger,
@@ -859,7 +873,16 @@ export async function startServer(
859
873
  const timerClient = noControlPlane
860
874
  ? createCursorHostedPlatformTimers()
861
875
  : localControlPlane;
862
- reminderRunner = new ReminderRunner(engine, logger, timerClient);
876
+ reminderRunner = new ReminderRunner(engine, logger, timerClient, {
877
+ namedHandlers: project.reminders ?? [],
878
+ // Architecture v2 rejects anonymous run/until everywhere (local + hosted).
879
+ rejectAnonymousHandlers: rejectAnonymousReminderHandlers({
880
+ noControlPlane,
881
+ architecture,
882
+ }),
883
+ architecture,
884
+ controlPlane: useReminderCpProtocol ? localControlPlane : undefined,
885
+ });
863
886
  const evalRuns = new EvalRunStore(
864
887
  project.rootDir,
865
888
  logger,
@@ -935,6 +958,7 @@ export async function startServer(
935
958
  const startArgs: ChannelStartArgs = {
936
959
  send: (message, sendOptions) =>
937
960
  engine.send(channel.id, message, sendOptions),
961
+ architecture: project.agent.architecture,
938
962
  getSession: (sessionId) =>
939
963
  engine.getChannelSession(channel.id, sessionId),
940
964
  receive: (target, input) => engine.receive(target, input),
@@ -1521,6 +1545,8 @@ function mountAgentRoutes(
1521
1545
  channelId,
1522
1546
  hostedSessionId,
1523
1547
  }),
1548
+ fireReminder: (reminderId, fireCount) =>
1549
+ reminderRunner.dispatch(reminderId, { expectedFireCount: fireCount }),
1524
1550
  drainSessionDurability: (sessionId) =>
1525
1551
  engine.drainSessionDurability(sessionId),
1526
1552
  flushOtel: args.flushOtel,
@@ -2985,19 +3011,34 @@ async function handleRequest(
2985
3011
  logger: (line: string) => void
2986
3012
  ): Promise<void> {
2987
3013
  let response: Response;
3014
+ let hostedDeliveryRequest = false;
2988
3015
  const method = req.method ?? "GET";
2989
3016
  try {
2990
3017
  const request = await toFetchRequest(req);
3018
+ hostedDeliveryRequest = request.headers.get(HOSTED_DELIVERY_HEADER) === "1";
3019
+ const requestErrorResponse = (
3020
+ status: number,
3021
+ error: string,
3022
+ message: string
3023
+ ) =>
3024
+ hostedDeliveryRequest
3025
+ ? hostedGuestErrorResponse({
3026
+ status,
3027
+ error,
3028
+ retry: HOSTED_GUEST_RETRY_TERMINAL,
3029
+ message,
3030
+ })
3031
+ : errorResponse(status, error, message);
2991
3032
  const url = new URL(request.url);
2992
3033
  const match = router.match(request.method, url.pathname);
2993
3034
  if (match === undefined) {
2994
3035
  response = router.hasPath(url.pathname)
2995
- ? errorResponse(
3036
+ ? requestErrorResponse(
2996
3037
  405,
2997
3038
  "method_not_allowed",
2998
3039
  `${request.method} is not supported on ${url.pathname}.`
2999
3040
  )
3000
- : errorResponse(
3041
+ : requestErrorResponse(
3001
3042
  404,
3002
3043
  "not_found",
3003
3044
  `No route for ${request.method} ${url.pathname}.`
@@ -3011,7 +3052,7 @@ async function handleRequest(
3011
3052
  unauthorized = auth === null;
3012
3053
  }
3013
3054
  response = unauthorized
3014
- ? errorResponse(
3055
+ ? requestErrorResponse(
3015
3056
  401,
3016
3057
  "unauthorized",
3017
3058
  "No auth policy admitted this request. In production, configure channel auth (e.g. bearerAuth) — the default localDevStrict() policy only admits loopback callers with a loopback Host."
@@ -3025,7 +3066,14 @@ async function handleRequest(
3025
3066
  } catch (error) {
3026
3067
  const mapped = mapThrownRequestError(error);
3027
3068
  if (mapped !== undefined) {
3028
- response = errorResponse(mapped.status, mapped.code, mapped.message);
3069
+ response = hostedDeliveryRequest
3070
+ ? hostedGuestErrorResponse({
3071
+ status: mapped.status,
3072
+ error: mapped.code,
3073
+ retry: mapped.retry,
3074
+ message: mapped.message,
3075
+ })
3076
+ : errorResponse(mapped.status, mapped.code, mapped.message);
3029
3077
  } else {
3030
3078
  logger(
3031
3079
  `[agent-sdk] request failed: ${
@@ -3035,13 +3083,18 @@ async function handleRequest(
3035
3083
  }`
3036
3084
  );
3037
3085
  const snippet = hostedErrorStackSnippet(error);
3038
- response = errorResponse(
3039
- 500,
3040
- "internal_error",
3086
+ const message =
3041
3087
  snippet.length > 0
3042
3088
  ? `Internal server error: ${snippet}`
3043
- : "Internal server error."
3044
- );
3089
+ : "Internal server error.";
3090
+ response = hostedDeliveryRequest
3091
+ ? hostedGuestErrorResponse({
3092
+ status: 500,
3093
+ error: "internal_error",
3094
+ retry: HOSTED_GUEST_RETRY_TERMINAL,
3095
+ message,
3096
+ })
3097
+ : errorResponse(500, "internal_error", message);
3045
3098
  }
3046
3099
  }
3047
3100
 
@@ -4744,6 +4744,10 @@ export class SessionEngine {
4744
4744
  * conversation whose blobs persisted but whose record (which selects the
4745
4745
  * conversation to hydrate) never reached the sink. No-op without a
4746
4746
  * storage mirror.
4747
+ *
4748
+ * Under a debounced event policy the turn-end flush is a timer, not the
4749
+ * chain's tail, so flush explicitly before waiting on the queue; a no-op
4750
+ * when the chain already flushed.
4747
4751
  */
4748
4752
  async drainSessionDurability(sessionId: string): Promise<void> {
4749
4753
  if (this.storage === undefined) {
@@ -4753,6 +4757,7 @@ export class SessionEngine {
4753
4757
  if (chain !== undefined) {
4754
4758
  await chain.catch(() => {});
4755
4759
  }
4760
+ this.storage.flushSession(sessionId);
4756
4761
  await this.storage.whenIdle();
4757
4762
  }
4758
4763