@cursor/july 0.1.31 → 0.1.33

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 (75) hide show
  1. package/dist/docs/404.html +1 -1
  2. package/dist/docs/ab.html +2 -2
  3. package/dist/docs/assets/{app.DigB_9cQ.js → app.CgZqLyWL.js} +1 -1
  4. package/dist/docs/assets/chunks/@localSearchIndexroot.B1uHGxBZ.js +1 -0
  5. package/dist/docs/assets/chunks/{VPLocalSearchBox.BCMX25Bv.js → VPLocalSearchBox.C4KigoQn.js} +1 -1
  6. package/dist/docs/assets/chunks/{theme.BDDWeELx.js → theme.C_Ty4yT7.js} +2 -2
  7. package/dist/docs/building-with-agents.html +2 -2
  8. package/dist/docs/concepts.html +2 -2
  9. package/dist/docs/deployment.html +2 -2
  10. package/dist/docs/evals.html +2 -2
  11. package/dist/docs/example-agents/approval-buddy.html +2 -2
  12. package/dist/docs/example-agents/benny.html +2 -2
  13. package/dist/docs/example-agents/bugbot.html +2 -2
  14. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  15. package/dist/docs/example-agents/codeowners-review.html +2 -2
  16. package/dist/docs/example-agents/concierge.html +2 -2
  17. package/dist/docs/example-agents/fsd.html +2 -2
  18. package/dist/docs/example-agents/index.html +2 -2
  19. package/dist/docs/example-agents/knowledge-base.html +2 -2
  20. package/dist/docs/example-agents/oncall.html +2 -2
  21. package/dist/docs/example-agents/security-reviewer.html +2 -2
  22. package/dist/docs/example-agents/slack-agent.html +2 -2
  23. package/dist/docs/example-agents/weather-agent.html +2 -2
  24. package/dist/docs/guides/agent-to-agent.html +2 -2
  25. package/dist/docs/guides/cloud-runtime.html +2 -2
  26. package/dist/docs/guides/github.html +2 -2
  27. package/dist/docs/guides/human-in-the-loop.html +2 -2
  28. package/dist/docs/guides/mcp-oauth.html +2 -2
  29. package/dist/docs/guides/slack.html +2 -2
  30. package/dist/docs/guides/webhooks.html +2 -2
  31. package/dist/docs/hillclimbing.html +2 -2
  32. package/dist/docs/index.html +2 -2
  33. package/dist/docs/quickstart.html +2 -2
  34. package/dist/docs/reference/agent-config.html +2 -2
  35. package/dist/docs/reference/channels.html +2 -2
  36. package/dist/docs/reference/cli.html +2 -2
  37. package/dist/docs/reference/connections.html +2 -2
  38. package/dist/docs/reference/hooks.html +2 -2
  39. package/dist/docs/reference/http-api.html +2 -2
  40. package/dist/docs/reference/instructions.html +2 -2
  41. package/dist/docs/reference/playground.html +2 -2
  42. package/dist/docs/reference/project-layout.html +2 -2
  43. package/dist/docs/reference/prompt.html +2 -2
  44. package/dist/docs/reference/schedules.html +2 -2
  45. package/dist/docs/reference/sessions.html +2 -2
  46. package/dist/docs/reference/skills.html +2 -2
  47. package/dist/docs/reference/subagents.html +2 -2
  48. package/dist/docs/reference/tools.html +2 -2
  49. package/dist/docs/scaffolding-agents.html +2 -2
  50. package/dist/docs/storage.html +2 -2
  51. package/dist/docs/troubleshooting.html +2 -2
  52. package/dist/files-backends/agent-store-presigned-url.d.ts +9 -98
  53. package/dist/files-backends/agent-store-presigned-url.d.ts.map +1 -1
  54. package/dist/files-backends/agent-store-presigned-url.js +199 -314
  55. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  56. package/dist/files-backends/cursor-hosted.js +25 -2
  57. package/dist/internal/cursor-relay-core.d.ts +12 -0
  58. package/dist/internal/cursor-relay-core.d.ts.map +1 -1
  59. package/dist/internal/cursor-relay-core.js +42 -8
  60. package/dist/internal/review-comments.d.ts +9 -1
  61. package/dist/internal/review-comments.d.ts.map +1 -1
  62. package/dist/internal/review-comments.js +76 -0
  63. package/dist/internal/server.js +8 -1
  64. package/dist/playground/assets/index-BRhBbnd4.css +1 -0
  65. package/dist/playground/assets/index-CVlCAcUx.js +85 -0
  66. package/dist/playground/index.html +2 -2
  67. package/package.json +3 -23
  68. package/src/files-backends/agent-store-presigned-url.ts +9 -401
  69. package/src/files-backends/cursor-hosted.ts +32 -6
  70. package/src/internal/cursor-relay-core.ts +62 -3
  71. package/src/internal/review-comments.ts +102 -2
  72. package/src/internal/server.ts +9 -1
  73. package/dist/docs/assets/chunks/@localSearchIndexroot.CnFFl07y.js +0 -1
  74. package/dist/playground/assets/index-50PKeJlG.css +0 -1
  75. package/dist/playground/assets/index-C0f2Wl1q.js +0 -85
@@ -151,6 +151,42 @@ export interface CursorRelayCoreOptions<TEvent extends { offset: string }> {
151
151
  fetchImpl?: typeof fetch;
152
152
  /** Test seam: override long-poll wait (default 25s). */
153
153
  pollWaitMs?: number;
154
+ /**
155
+ * Desired-revision generation for hosted engines. Sent as
156
+ * `x-agent-serve-generation` so the control plane can fence zombies after
157
+ * a redeploy. Defaults to `process.env.AGENT_SERVE_GENERATION` when set.
158
+ */
159
+ deploymentGeneration?: number;
160
+ }
161
+
162
+ /** Must match server-shared `AGENT_SERVE_GENERATION_HEADER`. */
163
+ export const AGENT_SERVE_GENERATION_HEADER = "x-agent-serve-generation";
164
+
165
+ /** Must match server-shared `AGENT_SERVE_GENERATION_ENV_VAR`. */
166
+ export const AGENT_SERVE_GENERATION_ENV_VAR = "AGENT_SERVE_GENERATION";
167
+
168
+ export function resolveAgentServeDeploymentGeneration(
169
+ explicit?: number
170
+ ): number | undefined {
171
+ if (
172
+ explicit !== undefined &&
173
+ Number.isSafeInteger(explicit) &&
174
+ explicit > 0
175
+ ) {
176
+ return explicit;
177
+ }
178
+ const raw = process.env[AGENT_SERVE_GENERATION_ENV_VAR]?.trim();
179
+ if (raw === undefined || raw.length === 0) {
180
+ return undefined;
181
+ }
182
+ if (!/^\d+$/.test(raw)) {
183
+ return undefined;
184
+ }
185
+ const parsed = Number(raw);
186
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
187
+ return undefined;
188
+ }
189
+ return parsed;
154
190
  }
155
191
 
156
192
  export class CursorRelayCore<TEvent extends { offset: string }> {
@@ -160,6 +196,7 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
160
196
  private consumePromise: Promise<void> | undefined;
161
197
  private readonly offsetStore: RelayOffsetStore;
162
198
  private readonly fetchImpl: typeof fetch;
199
+ private readonly deploymentGeneration: number | undefined;
163
200
  private consumerId = "";
164
201
  /** undefined until the first registration, so a clean first success logs. */
165
202
  private lastRegistrationLog: string | undefined;
@@ -175,6 +212,9 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
175
212
  constructor(private readonly options: CursorRelayCoreOptions<TEvent>) {
176
213
  this.offsetStore = new RelayOffsetStore(options.stateDir);
177
214
  this.fetchImpl = options.fetchImpl ?? fetch;
215
+ this.deploymentGeneration = resolveAgentServeDeploymentGeneration(
216
+ options.deploymentGeneration
217
+ );
178
218
  }
179
219
 
180
220
  start(): void {
@@ -285,6 +325,13 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
285
325
  headers: {
286
326
  authorization: `Bearer ${this.options.apiKey}`,
287
327
  "content-type": "application/json",
328
+ ...(this.deploymentGeneration === undefined
329
+ ? {}
330
+ : {
331
+ [AGENT_SERVE_GENERATION_HEADER]: String(
332
+ this.deploymentGeneration
333
+ ),
334
+ }),
288
335
  },
289
336
  body: JSON.stringify(adapter.buildDeregistrationBody(this.consumerId)),
290
337
  signal: controller.signal,
@@ -322,6 +369,13 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
322
369
  headers: {
323
370
  authorization: `Bearer ${this.options.apiKey}`,
324
371
  ...(body === undefined ? {} : { "content-type": "application/json" }),
372
+ ...(this.deploymentGeneration === undefined
373
+ ? {}
374
+ : {
375
+ [AGENT_SERVE_GENERATION_HEADER]: String(
376
+ this.deploymentGeneration
377
+ ),
378
+ }),
325
379
  },
326
380
  ...(body === undefined ? {} : { body: JSON.stringify(body) }),
327
381
  ...(this.abort === undefined ? {} : { signal: this.abort.signal }),
@@ -514,14 +568,19 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
514
568
  response: Response,
515
569
  backoffMs: number
516
570
  ): Promise<number> {
517
- const isAuthFailure = response.status === 401 || response.status === 403;
518
- const sleepMs = isAuthFailure ? AUTH_FAILURE_BACKOFF_MS : backoffMs;
571
+ // 409 = generation fence (stale resident after redeploy). Same long
572
+ // backoff as auth: retrying every second cannot heal a wrong generation.
573
+ const isAuthOrFenceFailure =
574
+ response.status === 401 ||
575
+ response.status === 403 ||
576
+ response.status === 409;
577
+ const sleepMs = isAuthOrFenceFailure ? AUTH_FAILURE_BACKOFF_MS : backoffMs;
519
578
  const text = await response.text().catch(() => "");
520
579
  this.log(
521
580
  `poll failed: ${response.status} ${text.slice(0, 300)} (retrying in ${Math.round(sleepMs / 1000)}s)`
522
581
  );
523
582
  await this.sleep(sleepMs);
524
- return isAuthFailure
583
+ return isAuthOrFenceFailure
525
584
  ? AUTH_FAILURE_BACKOFF_MS
526
585
  : Math.min(backoffMs * 2, MAX_BACKOFF_MS);
527
586
  }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
+ import type { HostGitHubClient } from "../types.js";
2
3
 
3
- export type ReviewCommentPublisher = "local_gh";
4
+ export type ReviewCommentPublisher = "local_gh" | "host_github";
4
5
 
5
6
  export type ReviewCommentPostResult = {
6
7
  posted: boolean;
@@ -84,6 +85,80 @@ export function createLocalGhReviewPublisher(): ReviewCommentPublisherImplementa
84
85
  };
85
86
  }
86
87
 
88
+ /**
89
+ * Posts a PR review through the host GitHub client (Cursor account lease,
90
+ * App installation token, or PAT). Prefer this on Cursor-deployed agents —
91
+ * no separate GitHub write secret is required when `githubChannel({
92
+ * cursorAccount })` is bound.
93
+ */
94
+ export function createHostGithubReviewPublisher(
95
+ github: HostGitHubClient
96
+ ): ReviewCommentPublisherImplementation {
97
+ return {
98
+ async postReview(args) {
99
+ return postReviewViaHostGithub(github, args);
100
+ },
101
+ };
102
+ }
103
+
104
+ async function postReviewViaHostGithub(
105
+ github: HostGitHubClient,
106
+ args: ReviewCommentPostArgs
107
+ ): Promise<ReviewCommentPostResult> {
108
+ const prepared = prepareReviewCommentsRequest(args);
109
+ if (prepared.kind === "skipped") {
110
+ return {
111
+ ...prepared.result,
112
+ publisher: "host_github",
113
+ };
114
+ }
115
+
116
+ const { request } = prepared;
117
+ const [owner, repo] = request.repo.split("/", 2);
118
+ if (owner === undefined || repo === undefined || repo === "") {
119
+ return skippedHost("unparseable owner/repo from PR URL");
120
+ }
121
+
122
+ try {
123
+ args.signal?.throwIfAborted();
124
+ const octokit = await github.getOctokit();
125
+ args.signal?.throwIfAborted();
126
+ const response = await octokit.rest.pulls.createReview({
127
+ owner,
128
+ repo,
129
+ pull_number: request.prNumber,
130
+ commit_id: request.headSha,
131
+ body: request.message,
132
+ event: "COMMENT",
133
+ comments: request.comments.map(toGithubPullRequestReviewComment),
134
+ });
135
+ const htmlUrl =
136
+ typeof response.data.html_url === "string"
137
+ ? response.data.html_url
138
+ : undefined;
139
+ const inlineCommentCount = request.comments.length;
140
+ return {
141
+ posted: true,
142
+ publisher: "host_github",
143
+ mode:
144
+ inlineCommentCount > 0
145
+ ? "review_with_inline_comments"
146
+ : "review_body_only",
147
+ inlineCommentCount,
148
+ detail: htmlUrl
149
+ ? `posted via host GitHub: ${htmlUrl}`
150
+ : `posted via host GitHub with ${inlineCommentCount} inline comment(s)`,
151
+ };
152
+ } catch (error) {
153
+ if (args.signal?.aborted) {
154
+ throw error;
155
+ }
156
+ return skippedHost(
157
+ `host GitHub review post failed: ${error instanceof Error ? error.message : String(error)}`
158
+ );
159
+ }
160
+ }
161
+
87
162
  async function postReviewViaLocalGh(
88
163
  args: ReviewCommentPostArgs
89
164
  ): Promise<ReviewCommentPostResult> {
@@ -129,10 +204,25 @@ async function postReviewViaLocalGh(
129
204
  }
130
205
  }
131
206
 
207
+ type GithubPullRequestReviewComment = {
208
+ path: string;
209
+ body: string;
210
+ line: number;
211
+ side: "RIGHT";
212
+ start_line?: number;
213
+ start_side?: "RIGHT";
214
+ };
215
+
132
216
  function toGithubReviewComment(
133
217
  comment: ReviewInlineComment
134
218
  ): Record<string, string | number> {
135
- const body: Record<string, string | number> = {
219
+ return toGithubPullRequestReviewComment(comment);
220
+ }
221
+
222
+ function toGithubPullRequestReviewComment(
223
+ comment: ReviewInlineComment
224
+ ): GithubPullRequestReviewComment {
225
+ const body: GithubPullRequestReviewComment = {
136
226
  path: comment.file,
137
227
  body: comment.body,
138
228
  line: comment.line,
@@ -159,6 +249,16 @@ function skipped(detail: string): ReviewCommentPostResult {
159
249
  };
160
250
  }
161
251
 
252
+ function skippedHost(detail: string): ReviewCommentPostResult {
253
+ return {
254
+ posted: false,
255
+ publisher: "host_github",
256
+ mode: "skipped",
257
+ inlineCommentCount: 0,
258
+ detail,
259
+ };
260
+ }
261
+
162
262
  function parseGithubPrUrl(
163
263
  prUrl: string
164
264
  ):
@@ -959,7 +959,15 @@ function mountAgentRoutes(
959
959
  method: "GET",
960
960
  pattern: `${base}/v1/health`,
961
961
  auth: "none",
962
- handler: () => jsonResponse({ ok: true, name: project.name }),
962
+ // Temporal consumes this response before promoting a newly deployed pod.
963
+ // Include the framework build there so deploy observability gets the
964
+ // version from the process that will serve turns, with no second request.
965
+ handler: () =>
966
+ jsonResponse({
967
+ ok: true,
968
+ name: project.name,
969
+ version: packageVersion(),
970
+ }),
963
971
  });
964
972
 
965
973
  // Bootstrap the SPA can't derive from its own URL (agent name, dev mode, and