@cursor/july 0.1.94 → 0.1.95

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 (209) hide show
  1. package/dist/channels/github/github-channel.d.ts.map +1 -1
  2. package/dist/channels/github/github-channel.js +14 -2
  3. package/dist/channels/github/types.d.ts +18 -3
  4. package/dist/channels/github/types.d.ts.map +1 -1
  5. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  6. package/dist/channels/origin/origin-channel.js +11 -3
  7. package/dist/channels/origin/origin-webhook.d.ts +11 -1
  8. package/dist/channels/origin/origin-webhook.d.ts.map +1 -1
  9. package/dist/channels/origin/origin-webhook.js +23 -3
  10. package/dist/channels/origin/types.d.ts +8 -0
  11. package/dist/channels/origin/types.d.ts.map +1 -1
  12. package/dist/channels/slack/pr-ref.d.ts +7 -1
  13. package/dist/channels/slack/pr-ref.d.ts.map +1 -1
  14. package/dist/channels/slack/pr-ref.js +42 -23
  15. package/dist/docs/404.html +2 -2
  16. package/dist/docs/ab.html +4 -4
  17. package/dist/docs/assets/{app.CFDEas4I.js → app.BBj0klBO.js} +1 -1
  18. package/dist/docs/assets/chunks/@localSearchIndexroot.oqPawjiD.js +1 -0
  19. package/dist/docs/assets/chunks/{VPLocalSearchBox.B1IIYpYS.js → VPLocalSearchBox.CUEXpL78.js} +1 -1
  20. package/dist/docs/assets/chunks/{theme.Ct4NSiLm.js → theme.DabmQnia.js} +2 -2
  21. package/dist/docs/assets/index.md.BoHaMdeZ.js +5 -0
  22. package/dist/docs/assets/{index.md.DRakGHFe.lean.js → index.md.BoHaMdeZ.lean.js} +1 -1
  23. package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.js → reference_cli.md.BsMOxDvh.js} +3 -2
  24. package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.lean.js → reference_cli.md.BsMOxDvh.lean.js} +1 -1
  25. package/dist/docs/assets/{reference_skills.md.8son6Hjm.js → reference_skills.md.BFW9retM.js} +3 -3
  26. package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.js → templates_agentic-owners.md.BZSH4N9z.js} +1 -1
  27. package/dist/docs/assets/{templates_demo.md.DhFcWN6j.js → templates_demo.md.BeQX9V3H.js} +1 -1
  28. package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.js → templates_pr-autofixer.md.x5zl6-GT.js} +2 -2
  29. package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.lean.js → templates_pr-autofixer.md.x5zl6-GT.lean.js} +1 -1
  30. package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.js +4 -0
  31. package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.lean.js +1 -0
  32. package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.js → templates_security-reviewer.md.lshxbCLK.js} +2 -2
  33. package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.lean.js → templates_security-reviewer.md.lshxbCLK.lean.js} +1 -1
  34. package/dist/docs/assets/{templates_triage.md.CVlpctKS.js → templates_triage.md.Co4UNzkZ.js} +3 -3
  35. package/dist/docs/assets/{templates_triage.md.CVlpctKS.lean.js → templates_triage.md.Co4UNzkZ.lean.js} +1 -1
  36. package/dist/docs/building-with-agents.html +4 -4
  37. package/dist/docs/concepts.html +4 -4
  38. package/dist/docs/deployment.html +4 -4
  39. package/dist/docs/evals.html +4 -4
  40. package/dist/docs/guides/agent-to-agent.html +4 -4
  41. package/dist/docs/guides/cloud-runtime.html +4 -4
  42. package/dist/docs/guides/convert-automation.html +4 -4
  43. package/dist/docs/guides/github.html +4 -4
  44. package/dist/docs/guides/human-in-the-loop.html +4 -4
  45. package/dist/docs/guides/mcp-oauth.html +4 -4
  46. package/dist/docs/guides/opentelemetry.html +4 -4
  47. package/dist/docs/guides/slack.html +4 -4
  48. package/dist/docs/guides/webhooks.html +4 -4
  49. package/dist/docs/hashmap.json +1 -1
  50. package/dist/docs/hillclimbing.html +4 -4
  51. package/dist/docs/index.html +6 -6
  52. package/dist/docs/index.md +3 -1
  53. package/dist/docs/llms-full.txt +90 -26
  54. package/dist/docs/llms.txt +1 -0
  55. package/dist/docs/quickstart.html +4 -4
  56. package/dist/docs/reference/agent-config.html +4 -4
  57. package/dist/docs/reference/artifacts.html +4 -4
  58. package/dist/docs/reference/channels.html +4 -4
  59. package/dist/docs/reference/cli.html +7 -6
  60. package/dist/docs/reference/cli.md +3 -1
  61. package/dist/docs/reference/connections.html +4 -4
  62. package/dist/docs/reference/hooks.html +4 -4
  63. package/dist/docs/reference/http-api.html +4 -4
  64. package/dist/docs/reference/instructions.html +4 -4
  65. package/dist/docs/reference/playground.html +4 -4
  66. package/dist/docs/reference/project-layout.html +4 -4
  67. package/dist/docs/reference/prompt.html +4 -4
  68. package/dist/docs/reference/schedules.html +4 -4
  69. package/dist/docs/reference/sessions.html +4 -4
  70. package/dist/docs/reference/skills.html +7 -7
  71. package/dist/docs/reference/subagents.html +4 -4
  72. package/dist/docs/reference/tools.html +4 -4
  73. package/dist/docs/scaffolding-agents.html +4 -4
  74. package/dist/docs/storage.html +4 -4
  75. package/dist/docs/templates/agentic-owners.html +7 -7
  76. package/dist/docs/templates/agentic-owners.md +1 -1
  77. package/dist/docs/templates/demo.html +6 -6
  78. package/dist/docs/templates/demo.md +3 -2
  79. package/dist/docs/templates/pr-autofixer.html +6 -6
  80. package/dist/docs/templates/pr-autofixer.md +7 -13
  81. package/dist/docs/templates/security-help.html +30 -0
  82. package/dist/docs/templates/security-help.md +65 -0
  83. package/dist/docs/templates/security-reviewer.html +6 -6
  84. package/dist/docs/templates/security-reviewer.md +1 -2
  85. package/dist/docs/templates/triage.html +7 -7
  86. package/dist/docs/templates/triage.md +2 -6
  87. package/dist/docs/troubleshooting.html +4 -4
  88. package/dist/files-backends/cursor-hosted.d.ts +6 -2
  89. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  90. package/dist/files-backends/cursor-hosted.js +2 -2
  91. package/dist/files.d.ts +2 -0
  92. package/dist/files.d.ts.map +1 -1
  93. package/dist/files.js +5 -0
  94. package/dist/internal/cli-deploy.d.ts.map +1 -1
  95. package/dist/internal/cli-deploy.js +135 -6
  96. package/dist/internal/conversation-mirror.d.ts +82 -0
  97. package/dist/internal/conversation-mirror.d.ts.map +1 -0
  98. package/dist/internal/conversation-mirror.js +251 -0
  99. package/dist/internal/deferred-channel-session.d.ts +20 -0
  100. package/dist/internal/deferred-channel-session.d.ts.map +1 -0
  101. package/dist/internal/deferred-channel-session.js +62 -0
  102. package/dist/internal/deploy-client.d.ts +13 -1
  103. package/dist/internal/deploy-client.d.ts.map +1 -1
  104. package/dist/internal/deploy-client.js +11 -1
  105. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  106. package/dist/internal/deploy-manifest.js +11 -5
  107. package/dist/internal/discovery.d.ts.map +1 -1
  108. package/dist/internal/discovery.js +22 -2
  109. package/dist/internal/framework-storage-selection.d.ts +32 -16
  110. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  111. package/dist/internal/framework-storage-selection.js +51 -17
  112. package/dist/internal/hosted-admission-context.d.ts +20 -0
  113. package/dist/internal/hosted-admission-context.d.ts.map +1 -0
  114. package/dist/internal/hosted-admission-context.js +31 -0
  115. package/dist/internal/hosted-delivery-protocol.d.ts +5 -0
  116. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  117. package/dist/internal/hosted-delivery-protocol.js +33 -1
  118. package/dist/internal/hosted-delivery.d.ts +4 -2
  119. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  120. package/dist/internal/hosted-delivery.js +75 -35
  121. package/dist/internal/hosted-managed-automation.d.ts +30 -0
  122. package/dist/internal/hosted-managed-automation.d.ts.map +1 -0
  123. package/dist/internal/hosted-managed-automation.js +58 -0
  124. package/dist/internal/review-comments.d.ts.map +1 -1
  125. package/dist/internal/review-comments.js +10 -98
  126. package/dist/internal/scm/pr-url.d.ts +14 -0
  127. package/dist/internal/scm/pr-url.d.ts.map +1 -0
  128. package/dist/internal/scm/pr-url.js +65 -0
  129. package/dist/internal/sdk-runner.d.ts +14 -0
  130. package/dist/internal/sdk-runner.d.ts.map +1 -1
  131. package/dist/internal/sdk-runner.js +41 -2
  132. package/dist/internal/server.d.ts.map +1 -1
  133. package/dist/internal/server.js +10 -2
  134. package/dist/internal/session-engine.d.ts.map +1 -1
  135. package/dist/internal/session-engine.js +9 -0
  136. package/dist/playground/assets/index-CF7hUDnQ.css +1 -0
  137. package/dist/playground/assets/{index-B3JCyigB.js → index-CZA1uEWA.js} +48 -48
  138. package/dist/playground/index.html +2 -2
  139. package/dist/storage-backends/cursor-hosted.d.ts +7 -2
  140. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  141. package/dist/storage-backends/cursor-hosted.js +2 -2
  142. package/docs/README.md +3 -1
  143. package/docs/reference/cli.md +3 -1
  144. package/docs/templates/agentic-owners.md +1 -1
  145. package/docs/templates/demo.md +3 -2
  146. package/docs/templates/pr-autofixer.md +7 -13
  147. package/docs/templates/security-help.md +70 -0
  148. package/docs/templates/security-reviewer.md +1 -2
  149. package/docs/templates/triage.md +2 -6
  150. package/package.json +1 -1
  151. package/src/channels/github/github-channel.ts +23 -8
  152. package/src/channels/github/types.ts +19 -2
  153. package/src/channels/origin/origin-channel.ts +13 -1
  154. package/src/channels/origin/origin-webhook.ts +27 -3
  155. package/src/channels/origin/types.ts +8 -0
  156. package/src/channels/slack/pr-ref.ts +56 -25
  157. package/src/files-backends/cursor-hosted.ts +9 -3
  158. package/src/files.ts +11 -0
  159. package/src/internal/cli-deploy.ts +171 -7
  160. package/src/internal/conversation-mirror.ts +330 -0
  161. package/src/internal/deferred-channel-session.ts +61 -0
  162. package/src/internal/deploy-client.ts +24 -1
  163. package/src/internal/deploy-manifest.ts +10 -5
  164. package/src/internal/discovery.ts +25 -2
  165. package/src/internal/framework-storage-selection.ts +61 -19
  166. package/src/internal/hosted-admission-context.ts +37 -0
  167. package/src/internal/hosted-delivery-protocol.ts +44 -1
  168. package/src/internal/hosted-delivery.ts +95 -40
  169. package/src/internal/hosted-managed-automation.ts +72 -0
  170. package/src/internal/review-comments.ts +10 -113
  171. package/src/internal/scm/pr-url.ts +95 -0
  172. package/src/internal/sdk-runner.ts +57 -2
  173. package/src/internal/server.ts +10 -0
  174. package/src/internal/session-engine.ts +12 -0
  175. package/src/storage-backends/cursor-hosted.ts +10 -3
  176. package/templates/agentic-owners/README.md +1 -1
  177. package/templates/agentic-owners/agent/agent.ts +0 -10
  178. package/templates/agentic-owners/agent/channels/github.ts +5 -14
  179. package/templates/agentic-owners/agent/lib/config.ts +0 -8
  180. package/templates/agentic-owners/agent/lib/review.ts +2 -15
  181. package/templates/agentic-owners/agent/tools/record_review.ts +2 -4
  182. package/templates/demo/agent/agent.ts +0 -10
  183. package/templates/pr-autofixer/README.md +0 -2
  184. package/templates/pr-autofixer/agent/agent.ts +0 -11
  185. package/templates/pr-autofixer/agent/channels/slack.ts +0 -7
  186. package/templates/pr-autofixer/agent/lib/pr-state.ts +5 -17
  187. package/templates/pr-autofixer/agent/lib/repos.ts +0 -1
  188. package/templates/security-help/README.md +2 -2
  189. package/templates/security-help/agent/agent.ts +1 -2
  190. package/templates/security-help/agent/channels/slack.ts +0 -3
  191. package/templates/security-help/agent/instructions.md +9 -10
  192. package/templates/security-help/agent/skills/access-request.md +1 -1
  193. package/templates/security-help/agent/skills/faq.md +31 -0
  194. package/templates/security-help/agent/skills/security-playbooks.md +1 -1
  195. package/templates/security-help/package.json +1 -2
  196. package/templates/security-reviewer/agent/agent.ts +0 -10
  197. package/templates/triage/agent/agent.ts +0 -10
  198. package/templates/triage/agent/channels/queue.ts +1 -1
  199. package/templates/triage/agent/channels/webhook.ts +1 -3
  200. package/dist/docs/assets/chunks/@localSearchIndexroot.DU3U2Ij2.js +0 -1
  201. package/dist/docs/assets/index.md.DRakGHFe.js +0 -5
  202. package/dist/playground/assets/index-D9N7-q97.css +0 -1
  203. package/templates/security-help/agent/knowledge/faq/approvals.md +0 -5
  204. package/templates/security-help/agent/knowledge/faq/channels.md +0 -6
  205. package/templates/security-help/agent/knowledge/faq/phishing.md +0 -10
  206. package/templates/security-help/agent/skills/security-first-pass.md +0 -15
  207. /package/dist/docs/assets/{reference_skills.md.8son6Hjm.lean.js → reference_skills.md.BFW9retM.lean.js} +0 -0
  208. /package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.lean.js → templates_agentic-owners.md.BZSH4N9z.lean.js} +0 -0
  209. /package/dist/docs/assets/{templates_demo.md.DhFcWN6j.lean.js → templates_demo.md.BeQX9V3H.lean.js} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import type { HostGitHubClient } from "../types.js";
3
+ import { parsePrUrl } from "./scm/pr-url.js";
3
4
 
4
5
  export type ReviewCommentPublisher = "local_gh" | "host_github" | "host_origin";
5
6
 
@@ -198,8 +199,15 @@ export type ReviewThreadResolverImplementation = {
198
199
  export function resolveScmReviewTarget(
199
200
  prUrl: string
200
201
  ): ScmReviewTarget | undefined {
201
- const parsed = parseReviewPrUrl(prUrl);
202
- return parsed.kind === "valid" ? parsed.value : undefined;
202
+ const parsed = parsePrUrl(prUrl);
203
+ if (parsed === undefined) {
204
+ return undefined;
205
+ }
206
+ return {
207
+ provider: parsed.provider,
208
+ repo: `${parsed.owner}/${parsed.repo}`,
209
+ prNumber: parsed.number,
210
+ };
203
211
  }
204
212
 
205
213
  /**
@@ -1243,117 +1251,6 @@ function skippedResolve(detail: string): ResolveReviewThreadsResult {
1243
1251
  };
1244
1252
  }
1245
1253
 
1246
- const ORIGIN_PR_PAGE_HOST_PATTERN = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
1247
-
1248
- /**
1249
- * Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
1250
- * `parsePrTarget` in security-review-lib accepts. Duplicated here so
1251
- * agent-serve does not import that package.
1252
- */
1253
- function parseReviewPrUrl(
1254
- prUrl: string
1255
- ):
1256
- | { kind: "valid"; value: ScmReviewTarget }
1257
- | { kind: "invalid"; detail: string } {
1258
- let url: URL;
1259
- try {
1260
- url = new URL(prUrl);
1261
- } catch (error) {
1262
- return {
1263
- kind: "invalid",
1264
- detail: `unparseable PR URL: ${error instanceof Error ? error.message : String(error)}`,
1265
- };
1266
- }
1267
-
1268
- const parts = url.pathname.split("/").filter(Boolean);
1269
- const hostname = url.hostname;
1270
-
1271
- if (hostname === "github.com" && parts.length >= 4 && parts[2] === "pull") {
1272
- const prNumber = Number(parts[3]);
1273
- if (Number.isInteger(prNumber) && prNumber > 0) {
1274
- return {
1275
- kind: "valid",
1276
- value: {
1277
- provider: "github",
1278
- repo: `${parts[0]}/${parts[1]}`,
1279
- prNumber,
1280
- },
1281
- };
1282
- }
1283
- }
1284
-
1285
- if (
1286
- ORIGIN_PR_PAGE_HOST_PATTERN.test(hostname) &&
1287
- parts.length >= 4 &&
1288
- parts[2] === "github"
1289
- ) {
1290
- const prNumber = Number(parts[3]);
1291
- if (Number.isInteger(prNumber) && prNumber > 0) {
1292
- return {
1293
- kind: "valid",
1294
- value: {
1295
- provider: "origin",
1296
- repo: `${parts[0]}/${parts[1]}`,
1297
- prNumber,
1298
- },
1299
- };
1300
- }
1301
- }
1302
-
1303
- if (
1304
- /^(www\.)?cursor\.com$/i.test(hostname) &&
1305
- parts[0] === "codebase" &&
1306
- parts.length >= 5 &&
1307
- parts[3] === "pull"
1308
- ) {
1309
- const prNumber = Number(parts[4]);
1310
- if (Number.isInteger(prNumber) && prNumber > 0) {
1311
- return {
1312
- kind: "valid",
1313
- value: {
1314
- provider: "origin",
1315
- repo: `${parts[1]}/${parts[2]}`,
1316
- prNumber,
1317
- },
1318
- };
1319
- }
1320
- }
1321
-
1322
- if (hostname === "review.cursor.com" && parts[0] === "pr") {
1323
- if (parts.length >= 5 && parts[3] === "github") {
1324
- const prNumber = Number(parts[4]);
1325
- if (Number.isInteger(prNumber) && prNumber > 0) {
1326
- return {
1327
- kind: "valid",
1328
- value: {
1329
- provider: "origin",
1330
- repo: `${parts[1]}/${parts[2]}`,
1331
- prNumber,
1332
- },
1333
- };
1334
- }
1335
- }
1336
- if (parts.length >= 4) {
1337
- const prNumber = Number(parts[3]);
1338
- if (Number.isInteger(prNumber) && prNumber > 0) {
1339
- return {
1340
- kind: "valid",
1341
- value: {
1342
- provider: "origin",
1343
- repo: `${parts[1]}/${parts[2]}`,
1344
- prNumber,
1345
- },
1346
- };
1347
- }
1348
- }
1349
- }
1350
-
1351
- return {
1352
- kind: "invalid",
1353
- detail: "unparseable PR URL: not a GitHub or Origin pull request",
1354
- };
1355
- }
1356
-
1357
1254
  function parseGithubPrUrl(
1358
1255
  prUrl: string
1359
1256
  ):
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
3
+ * humans paste: cursor.com/codebase, review.cursor.com/pr, origin.cursor.com.
4
+ */
5
+
6
+ export type PrProvider = "github" | "origin";
7
+
8
+ export type ParsedPrUrl = {
9
+ provider: PrProvider;
10
+ owner: string;
11
+ repo: string;
12
+ number: number;
13
+ url: string;
14
+ };
15
+
16
+ const ORIGIN_PAGE = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
17
+
18
+ function positiveInt(raw: string | undefined): number | undefined {
19
+ if (raw === undefined) {
20
+ return undefined;
21
+ }
22
+ const n = Number(raw);
23
+ if (!Number.isInteger(n) || n <= 0) {
24
+ return undefined;
25
+ }
26
+ return n;
27
+ }
28
+
29
+ function parsed(
30
+ provider: PrProvider,
31
+ owner: string | undefined,
32
+ repo: string | undefined,
33
+ number: number | undefined
34
+ ): ParsedPrUrl | undefined {
35
+ if (owner === undefined || repo === undefined || number === undefined) {
36
+ return undefined;
37
+ }
38
+ const url =
39
+ provider === "origin"
40
+ ? `https://cursor.com/codebase/${owner}/${repo}/pull/${number}`
41
+ : `https://github.com/${owner}/${repo}/pull/${number}`;
42
+ return { provider, owner, repo, number, url };
43
+ }
44
+
45
+ /** Strip Slack mrkdwn wrappers and trailing punctuation before `URL`. */
46
+ function normalizeRaw(raw: string): string {
47
+ return raw
48
+ .trim()
49
+ .replace(/^[<[]+/, "")
50
+ .replace(/[>\].,;]+$/, "");
51
+ }
52
+
53
+ export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
54
+ let url: URL;
55
+ try {
56
+ url = new URL(normalizeRaw(raw));
57
+ } catch {
58
+ return undefined;
59
+ }
60
+
61
+ const parts = url.pathname.split("/").filter(Boolean);
62
+ const host = url.hostname.toLowerCase();
63
+
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
+ if (ORIGIN_PAGE.test(host) && parts.length >= 4 && parts[2] === "github") {
73
+ return parsed("origin", parts[0], parts[1], positiveInt(parts[3]));
74
+ }
75
+
76
+ if (
77
+ (host === "cursor.com" || host === "www.cursor.com") &&
78
+ parts[0] === "codebase" &&
79
+ parts.length >= 5 &&
80
+ parts[3] === "pull"
81
+ ) {
82
+ return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
83
+ }
84
+
85
+ if (host === "review.cursor.com" && parts[0] === "pr") {
86
+ if (parts.length >= 5 && parts[3] === "github") {
87
+ return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
88
+ }
89
+ if (parts.length >= 4) {
90
+ return parsed("origin", parts[1], parts[2], positiveInt(parts[3]));
91
+ }
92
+ }
93
+
94
+ return undefined;
95
+ }
@@ -29,6 +29,7 @@ import {
29
29
  type TurnUsage,
30
30
  } from "../types.js";
31
31
  import { mapSdkTokenUsage, usageTokenSum } from "./cloud-turn-cost.js";
32
+ import type { ConversationMirror } from "./conversation-mirror.js";
32
33
  import { resolveApiKeySync } from "./cursor/credentials.js";
33
34
 
34
35
  /** Internal seam between the session engine and the Cursor SDK harness. */
@@ -117,6 +118,13 @@ export interface CursorSdkRunnerOptions {
117
118
  * `LocalAgentStore` for heavy multi-session concurrency.
118
119
  */
119
120
  store?: LocalAgentStore;
121
+ /**
122
+ * Durable mirror for local conversations (v2 hosted engines). Hydrated
123
+ * before `Agent.resume` so a follow-up landing on a fresh pod finds the
124
+ * conversation, and persisted — awaited — after each local turn settles,
125
+ * because a hosted pod can be parked right after the execution reply.
126
+ */
127
+ conversationMirror?: ConversationMirror;
120
128
  /** Diagnostics for degraded paths (resume-miss fallback). */
121
129
  logger?: (line: string) => void;
122
130
  /**
@@ -316,6 +324,13 @@ export class CursorSdkRunner implements AgentRunner {
316
324
  }
317
325
  try {
318
326
  const result = await run.wait();
327
+ // Mirror the conversation before replying: on hosted v2 the pod can
328
+ // be parked as soon as the execution reply lands, so a
329
+ // fire-and-forget write would race VM teardown. Failed turns still
330
+ // checkpoint the user message, so they persist too.
331
+ if (request.runtime === "local") {
332
+ await this.persistConversation(request, agent.agentId);
333
+ }
319
334
  let outcome = toTurnOutcome(result);
320
335
  if (outcome.status === "error") {
321
336
  // An error result usually means the handle's transport is poisoned
@@ -469,8 +484,7 @@ export class CursorSdkRunner implements AgentRunner {
469
484
  private async buildLocalOptions(
470
485
  request: RunnerPrewarmRequest
471
486
  ): Promise<AgentOptions> {
472
- const store =
473
- this.options.store ?? (await this.getStore(request.runnerStateDir));
487
+ const store = await this.resolveStore(request);
474
488
  return {
475
489
  ...this.buildSharedOptions(request),
476
490
  // The built-in tool allowlist rides AgentOptions, not `local`, and is
@@ -507,6 +521,15 @@ export class CursorSdkRunner implements AgentRunner {
507
521
  }
508
522
 
509
523
  const agentId = request.sdkAgentId ?? request.sessionId;
524
+ const mirror = this.options.conversationMirror;
525
+ if (mirror !== undefined) {
526
+ // Host replacement: restore the conversation from the durable mirror
527
+ // before resuming, so the thread continues instead of starting fresh.
528
+ // A sink failure propagates and fails the turn (retryable) — reading
529
+ // an outage as "nothing mirrored" would silently fork the thread.
530
+ const store = await this.resolveStore(request);
531
+ await mirror.hydrate(store, agentId);
532
+ }
510
533
  try {
511
534
  return await Agent.resume(agentId, options);
512
535
  } catch (error) {
@@ -570,6 +593,38 @@ export class CursorSdkRunner implements AgentRunner {
570
593
  return Agent.create(options);
571
594
  }
572
595
 
596
+ private resolveStore(
597
+ request: Pick<RunnerPrewarmRequest, "runnerStateDir">
598
+ ): Promise<LocalAgentStore> {
599
+ return this.options.store !== undefined
600
+ ? Promise.resolve(this.options.store)
601
+ : this.getStore(request.runnerStateDir);
602
+ }
603
+
604
+ /**
605
+ * Mirror the settled turn's conversation. Persist failures are logged and
606
+ * dropped — a durable-mirror outage must not fail a turn that already ran.
607
+ */
608
+ private async persistConversation(
609
+ request: RunnerTurnRequest,
610
+ sdkAgentId: string
611
+ ): Promise<void> {
612
+ const mirror = this.options.conversationMirror;
613
+ if (mirror === undefined) {
614
+ return;
615
+ }
616
+ try {
617
+ const store = await this.resolveStore(request);
618
+ await mirror.persist(store, sdkAgentId);
619
+ } catch (error) {
620
+ this.log(
621
+ `[agent-sdk] conversation mirror: persist for ${sdkAgentId} failed: ${
622
+ error instanceof Error ? error.message : String(error)
623
+ }`
624
+ );
625
+ }
626
+ }
627
+
573
628
  private getStore(stateDir: string): Promise<LocalAgentStore> {
574
629
  let storePromise = this.stores.get(stateDir);
575
630
  if (storePromise === undefined) {
@@ -51,6 +51,7 @@ import type {
51
51
  ServeOptions,
52
52
  } from "../types.js";
53
53
  import { V2EngineError } from "./continuation-identity.js";
54
+ import { selectConversationMirror } from "./conversation-mirror.js";
54
55
  import {
55
56
  type CursorAccountConnectionRef,
56
57
  CursorAccountMcpBridge,
@@ -516,12 +517,21 @@ export async function startServer(
516
517
  "[agent-sdk] warning: --allow-anonymous admits every HTTP caller as the same principal; use only on a trusted network (e.g. Tailscale) and prefer --bearer-token for shared hosts"
517
518
  );
518
519
  }
520
+ // v2 hosted engines mirror the SDK conversation store to the deployment's
521
+ // shared file storage so a fresh pod resumes threads instead of forking
522
+ // them. Hosted serve is single-agent; a multi-mount serve (dev) has no
523
+ // single agent name to mint keys under and does not mirror.
524
+ const conversationMirror =
525
+ mounts.length === 1 && mounts[0] !== undefined
526
+ ? selectConversationMirror({ project: mounts[0].project, logger })
527
+ : undefined;
519
528
  const sdkRunner =
520
529
  options.runner ??
521
530
  new CursorSdkRunner({
522
531
  ...(options.apiKey === undefined || options.apiKey.trim() === ""
523
532
  ? {}
524
533
  : { apiKey: options.apiKey }),
534
+ ...(conversationMirror === undefined ? {} : { conversationMirror }),
525
535
  logger,
526
536
  });
527
537
  // Grok Bot turns run on Cursor's hosted Grok Bot (Sand) harness through
@@ -113,6 +113,10 @@ import {
113
113
  createHostSlackClient,
114
114
  type HostPlatforms,
115
115
  } from "./host-platforms.js";
116
+ import {
117
+ HostedAdmissionLocalSendError,
118
+ isDuringHostedAdmission,
119
+ } from "./hosted-admission-context.js";
116
120
  import {
117
121
  formatContinuationToken,
118
122
  newContinuationKey,
@@ -465,6 +469,7 @@ export class SessionEngine {
465
469
  // for why the choice does not belong to the agent author.
466
470
  const frameworkStorage = selectFrameworkStorageDefinition({
467
471
  authored: options.project.storage,
472
+ architecture: options.project.agent.architecture,
468
473
  });
469
474
  this.storage =
470
475
  frameworkStorage === undefined
@@ -811,6 +816,13 @@ export class SessionEngine {
811
816
  message: string,
812
817
  options: EngineSendOptions = {}
813
818
  ): Promise<ChannelSession> {
819
+ if (isDuringHostedAdmission()) {
820
+ // Only sends that bypassed the admission capture stub (typically a
821
+ // startup-captured `onStart` send) reach the engine inside the
822
+ // admission window; running one would leak an invisible local session
823
+ // on a sessionless pod.
824
+ throw new HostedAdmissionLocalSendError();
825
+ }
814
826
  const identity =
815
827
  channelId === HTTP_CHANNEL_ID
816
828
  ? httpContinuationIdentity(this.project.httpChannel)
@@ -74,8 +74,13 @@ export interface CursorHostedStorageOptions {
74
74
  * `https://api.cursor.com`).
75
75
  */
76
76
  baseUrl?: string;
77
- /** Bearer credential (default: `CURSOR_API_KEY`). */
78
- apiKey?: string;
77
+ /**
78
+ * Bearer credential (default: `CURSOR_API_KEY`). A function is
79
+ * re-resolved on every call: hosted freeze snapshots listen unsigned and
80
+ * bind the key file after serve start, so a value captured at construct
81
+ * time would be permanently absent.
82
+ */
83
+ apiKey?: string | (() => string | undefined);
79
84
  /** Injected for tests. */
80
85
  fetch?: typeof fetch;
81
86
  }
@@ -180,7 +185,9 @@ function toCasResult(response: KvCasResponse): KvCasResult {
180
185
  }
181
186
 
182
187
  function resolveApiKey(options: CursorHostedStorageOptions): string {
183
- const key = (options.apiKey ?? process.env.CURSOR_API_KEY)?.trim();
188
+ const configured =
189
+ typeof options.apiKey === "function" ? options.apiKey() : options.apiKey;
190
+ const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
184
191
  if (key === undefined || key === "") {
185
192
  throw new Error(
186
193
  "cursorHostedStorage requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
@@ -58,7 +58,7 @@ policy only) opts paths out entirely.
58
58
  - One model turn per PR head assesses the diff against the base rubric
59
59
  (`agent/lib/owners/prompts/base-review.md`) plus matched rule prompts.
60
60
  - The model records a verdict with `record_review`; the host recomputes
61
- the policy and applies the deterministic decision approve, or request
61
+ the policy and applies the deterministic decision: approve, or request
62
62
  the policy owners (max 2, never the author) and leave one comment it
63
63
  keeps updated.
64
64
  - The model never writes to GitHub. Broken policy files fail closed with
@@ -1,18 +1,8 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
1
  import { defineAgent } from "@cursor/july";
4
2
 
5
3
  export default defineAgent({
6
- model: {
7
- id: "grok-4.5",
8
- params: [
9
- { id: "effort", value: "high" },
10
- { id: "fast", value: "true" },
11
- ],
12
- },
13
4
  tools: ["read", "grep", "glob", "ls"],
14
5
  local: {
15
6
  sandbox: true,
16
- cwd: join(homedir(), ".cache", "agent-serve", "agentic-owners"),
17
7
  },
18
8
  });
@@ -22,10 +22,8 @@ const REVIEW_ACTIONS = new Set([
22
22
  let log: (line: string) => void = () => {};
23
23
 
24
24
  export default githubChannel({
25
- // With repos configured, the signed-in Cursor account supplies both the
26
- // PR event stream and a repo-scoped GitHub credential — no webhook or
27
- // GitHub App setup. Empty repos boots without the binding so a fresh
28
- // scaffold's `dev` / `eval` run; use `agent-sdk github replay` there.
25
+ // Empty `repos` skips the Cursor GitHub binding so `dev` / `eval` boot
26
+ // on a fresh scaffold.
29
27
  cursorAccount: config.repos.length > 0 ? { repos: config.repos } : undefined,
30
28
  // The model never writes to GitHub; record_review actuates host-side.
31
29
  deliverReplies: false,
@@ -33,9 +31,7 @@ export default githubChannel({
33
31
  log = (line) => args.logger(`[agentic-owners] ${line}`);
34
32
  },
35
33
  onPullRequest: async (ctx, pr) => {
36
- // A base retarget re-resolves the policy against the new base: the diff
37
- // and the governing policy files both changed, so the old verdict — and
38
- // any approval it produced — no longer covers this PR. The fresh
34
+ // A base retarget re-resolves policy against the new base. The fresh
39
35
  // actuation dismisses a leftover approve when the new decision holds.
40
36
  const baseRetargeted =
41
37
  pr.action === "edited" && pr.baseRefChangedFrom !== undefined;
@@ -54,9 +50,8 @@ export default githubChannel({
54
50
  try {
55
51
  const state = await loadReviewState(forge, log);
56
52
  if (!state.enrolled) {
57
- // Enrollment can end while an agent approval is live (base retarget,
58
- // owned files dropped); dismiss it instead of leaving it counting
59
- // toward branch protection under a policy that never granted it.
53
+ // Enrollment can end while an agent approval is live; dismiss it
54
+ // instead of leaving it counting toward branch protection.
60
55
  return {
61
56
  task: () =>
62
57
  dismissStaleApprovals(
@@ -69,14 +64,10 @@ export default githubChannel({
69
64
  auth: defaultGitHubAuth(ctx),
70
65
  message: renderReviewWake(state),
71
66
  title: `Review ${ctx.repository.fullName}#${pr.number}`,
72
- // Freeze the assessed head host-side: record_review reads this file
73
- // instead of trusting a model-supplied SHA, so a mid-turn push (or a
74
- // prompt-injected SHA) can never line up with the live head.
75
67
  workspaceFiles: { "pr/head.sha": state.pr.headSha },
76
68
  };
77
69
  } catch (error) {
78
70
  if (error instanceof AgenticOwnersConfigValidationError) {
79
- // Fail closed but visibly: comment the issues, review nothing.
80
71
  return { task: () => reportPolicyError(forge, error) };
81
72
  }
82
73
  throw error;
@@ -1,11 +1,3 @@
1
- /**
2
- * The one file to edit: which GitHub repositories the agent reviews.
3
- *
4
- * Setting it streams PR events and a repo-scoped GitHub credential through
5
- * your signed-in Cursor account (`agent-sdk login`) — no webhook or GitHub
6
- * App setup. While empty, the agent still runs locally against replayed
7
- * PRs (`agent-sdk github replay <pr-url>`).
8
- */
9
1
  const config = {
10
2
  repos: [] as string[], // e.g. ["acme/api"]
11
3
  };
@@ -1,8 +1,5 @@
1
- // Policy resolution and actuation over the GitHub API no repo checkout.
2
- // Every trusted input (policy files, rule prompts) is read from the PR's
3
- // **base ref**, so a PR cannot exempt itself by editing its own governance
4
- // files. Mirrors the Agentic Owners pipeline's `resolvePolicyForPr`, minus
5
- // the linguist-generated partition and content caching.
1
+ // Policy files and rule prompts are read from the PR's base ref so a PR
2
+ // cannot exempt itself by editing its own governance files.
6
3
 
7
4
  import {
8
5
  decideApproval,
@@ -297,12 +294,6 @@ export async function loadReviewState(
297
294
  };
298
295
  }
299
296
 
300
- /**
301
- * The model turn's wake message: composed rubric, PR context, and the
302
- * host-fetched diff. Fenced so author-controlled patch bytes cannot escape
303
- * into the trusted markdown (the fence outruns any backtick run inside the
304
- * patch).
305
- */
306
297
  /**
307
298
  * Filenames are author-controlled and render outside the diff fence: strip
308
299
  * control characters and backticks so a hostile path cannot fabricate
@@ -648,9 +639,6 @@ export async function actuateReview(input: {
648
639
  }).filter(
649
640
  (handle) => normalizeHandle(handle) !== normalizeHandle(pr.authorLogin ?? "")
650
641
  );
651
- // The full decision — policy verdict plus the governance / evidence
652
- // fail-closes — is computed in one place; from here on this function
653
- // only writes.
654
642
  const decision = decideApproval({
655
643
  matchedRules: state.matchedRules,
656
644
  riskLevel: verdict.riskLevel,
@@ -742,7 +730,6 @@ export async function actuateReview(input: {
742
730
  const alreadyRequested = new Set(
743
731
  pr.requestedReviewers.map(normalizeHandle)
744
732
  );
745
- // Adds fill only the remaining slots under the max-of-two rule.
746
733
  const newReviewers = decision.requestReviewers
747
734
  .filter((handle) => !alreadyRequested.has(normalizeHandle(handle)))
748
735
  .slice(
@@ -41,10 +41,8 @@ export default defineTool({
41
41
  "not a GitHub pull-request session — reviews start from the github channel (agent-sdk github replay <pr-url> to test locally)",
42
42
  };
43
43
  }
44
- // The channel wake froze the assessed head into the session workspace.
45
- // Reading it here (instead of accepting a model-supplied SHA) means a
46
- // mid-turn push can never line up with the live head: the actuation
47
- // compares wake-observed against live and skips on any mismatch.
44
+ // Wake froze the assessed head into the session workspace. A model-supplied
45
+ // SHA is never trusted.
48
46
  let assessedHeadSha: string;
49
47
  try {
50
48
  assessedHeadSha = (
@@ -1,18 +1,8 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
1
  import { defineAgent } from "@cursor/july";
4
2
 
5
3
  export default defineAgent({
6
- model: {
7
- id: "grok-4.5",
8
- params: [
9
- { id: "effort", value: "high" },
10
- { id: "fast", value: "true" },
11
- ],
12
- },
13
4
  tools: ["read", "grep"],
14
5
  local: {
15
6
  sandbox: true,
16
- cwd: join(homedir(), ".cache", "agent-serve", "demo"),
17
7
  },
18
8
  });
@@ -34,8 +34,6 @@ enables auto-merge, or force-pushes.
34
34
  GitHub wakes (`cursorAccount`) resume an already-driven PR on comment,
35
35
  review, and failing CI. They do not start a session on `pull_request.opened`.
36
36
 
37
- Affinity and the webhook buffer live in `host.kv` (`agent/storage.ts`).
38
-
39
37
  ## Files to edit
40
38
 
41
39
  - `agent/lib/repos.ts`: repos Cursor relays SCM events for
@@ -1,19 +1,8 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
1
  import { defineAgent } from "@cursor/july";
4
2
 
5
3
  export default defineAgent({
6
- model: {
7
- id: "grok-4.5",
8
- params: [
9
- { id: "effort", value: "high" },
10
- { id: "fast", value: "true" },
11
- ],
12
- },
13
- runtime: "local",
14
4
  local: {
15
5
  sandbox: true,
16
- cwd: join(homedir(), ".cache", "agent-serve", "pr-autofixer"),
17
6
  },
18
7
  cloud: {
19
8
  env: { type: "cloud" },
@@ -1,12 +1,5 @@
1
1
  import { slackChannel } from "@cursor/july/channels/slack";
2
2
 
3
- /**
4
- * Slack channel (Socket Mode).
5
- *
6
- * Tokens: PR_AUTOFIXER_SLACK_BOT_TOKEN + PR_AUTOFIXER_SLACK_APP_TOKEN
7
- *
8
- * Full setup: agent-sdk slack create
9
- */
10
3
  export default slackChannel({
11
4
  envPrefix: "PR_AUTOFIXER",
12
5
  suggestedPromptsTitle: "Try asking",