@cursor/july 0.1.83 → 0.1.85

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 (118) hide show
  1. package/dist/channels/deployments/deployments-channel.d.ts +2 -2
  2. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  3. package/dist/channels/deployments/deployments-channel.js +7 -6
  4. package/dist/channels/deployments/index.d.ts +2 -3
  5. package/dist/channels/deployments/index.d.ts.map +1 -1
  6. package/dist/channels/deployments/index.js +2 -3
  7. package/dist/channels/deployments/types.d.ts +14 -14
  8. package/dist/channels/deployments/types.d.ts.map +1 -1
  9. package/dist/docs/404.html +2 -2
  10. package/dist/docs/ab.html +3 -3
  11. package/dist/docs/assets/{app.6THHGRH-.js → app.DUOPbN18.js} +1 -1
  12. package/dist/docs/assets/chunks/@localSearchIndexroot.CxCtxfDE.js +1 -0
  13. package/dist/docs/assets/chunks/{VPLocalSearchBox.BpHB9ulP.js → VPLocalSearchBox.CmWbGcGk.js} +1 -1
  14. package/dist/docs/assets/chunks/{theme.DBJvJznv.js → theme.S57OeOLA.js} +2 -2
  15. package/dist/docs/assets/{reference_channels.md.G5GysZ7e.js → reference_channels.md.CboFd5IH.js} +1 -1
  16. package/dist/docs/building-with-agents.html +3 -3
  17. package/dist/docs/concepts.html +3 -3
  18. package/dist/docs/deployment.html +3 -3
  19. package/dist/docs/evals.html +3 -3
  20. package/dist/docs/example-agents/approval-buddy.html +3 -3
  21. package/dist/docs/example-agents/benny.html +3 -3
  22. package/dist/docs/example-agents/bugbot.html +3 -3
  23. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  24. package/dist/docs/example-agents/codeowners-review.html +3 -3
  25. package/dist/docs/example-agents/concierge.html +3 -3
  26. package/dist/docs/example-agents/fsd.html +3 -3
  27. package/dist/docs/example-agents/index.html +3 -3
  28. package/dist/docs/example-agents/knowledge-base.html +3 -3
  29. package/dist/docs/example-agents/oncall.html +3 -3
  30. package/dist/docs/example-agents/security-reviewer.html +3 -3
  31. package/dist/docs/example-agents/slack-agent.html +3 -3
  32. package/dist/docs/example-agents/weather-agent.html +3 -3
  33. package/dist/docs/guides/agent-to-agent.html +3 -3
  34. package/dist/docs/guides/cloud-runtime.html +3 -3
  35. package/dist/docs/guides/convert-automation.html +3 -3
  36. package/dist/docs/guides/github.html +3 -3
  37. package/dist/docs/guides/human-in-the-loop.html +3 -3
  38. package/dist/docs/guides/mcp-oauth.html +3 -3
  39. package/dist/docs/guides/opentelemetry.html +3 -3
  40. package/dist/docs/guides/slack.html +3 -3
  41. package/dist/docs/guides/webhooks.html +3 -3
  42. package/dist/docs/hashmap.json +1 -1
  43. package/dist/docs/hillclimbing.html +3 -3
  44. package/dist/docs/index.html +3 -3
  45. package/dist/docs/quickstart.html +3 -3
  46. package/dist/docs/reference/agent-config.html +3 -3
  47. package/dist/docs/reference/artifacts.html +3 -3
  48. package/dist/docs/reference/channels.html +5 -5
  49. package/dist/docs/reference/cli.html +3 -3
  50. package/dist/docs/reference/connections.html +3 -3
  51. package/dist/docs/reference/hooks.html +3 -3
  52. package/dist/docs/reference/http-api.html +3 -3
  53. package/dist/docs/reference/instructions.html +3 -3
  54. package/dist/docs/reference/playground.html +3 -3
  55. package/dist/docs/reference/project-layout.html +3 -3
  56. package/dist/docs/reference/prompt.html +3 -3
  57. package/dist/docs/reference/schedules.html +3 -3
  58. package/dist/docs/reference/sessions.html +3 -3
  59. package/dist/docs/reference/skills.html +3 -3
  60. package/dist/docs/reference/subagents.html +3 -3
  61. package/dist/docs/reference/tools.html +3 -3
  62. package/dist/docs/scaffolding-agents.html +3 -3
  63. package/dist/docs/storage.html +3 -3
  64. package/dist/docs/templates/agentic-owners.html +3 -3
  65. package/dist/docs/templates/demo.html +3 -3
  66. package/dist/docs/templates/pr-autofixer.html +3 -3
  67. package/dist/docs/templates/security-reviewer.html +3 -3
  68. package/dist/docs/templates/triage.html +3 -3
  69. package/dist/docs/troubleshooting.html +3 -3
  70. package/dist/internal/cursor-deployment-relay.d.ts +4 -4
  71. package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
  72. package/dist/internal/cursor-deployment-relay.js +33 -32
  73. package/dist/internal/discovery.d.ts.map +1 -1
  74. package/dist/internal/discovery.js +16 -1
  75. package/dist/internal/event-mapper.d.ts +7 -1
  76. package/dist/internal/event-mapper.d.ts.map +1 -1
  77. package/dist/internal/event-mapper.js +8 -8
  78. package/dist/internal/http-channel.d.ts.map +1 -1
  79. package/dist/internal/http-channel.js +1 -1
  80. package/dist/internal/mcp-endpoint.js +9 -0
  81. package/dist/internal/prompt-context.d.ts +4 -1
  82. package/dist/internal/prompt-context.d.ts.map +1 -1
  83. package/dist/internal/prompt-context.js +20 -2
  84. package/dist/internal/review-comments.js +15 -7
  85. package/dist/internal/server.js +13 -13
  86. package/dist/internal/session-engine.d.ts +28 -0
  87. package/dist/internal/session-engine.d.ts.map +1 -1
  88. package/dist/internal/session-engine.js +99 -42
  89. package/dist/internal/tool-policy.d.ts +50 -0
  90. package/dist/internal/tool-policy.d.ts.map +1 -0
  91. package/dist/internal/tool-policy.js +51 -0
  92. package/dist/playground/assets/index-CMe7imuc.css +1 -0
  93. package/dist/playground/assets/{index-Cspgk9jC.js → index-JaFLlugp.js} +45 -45
  94. package/dist/playground/index.html +2 -2
  95. package/dist/tools.d.ts +16 -2
  96. package/dist/tools.d.ts.map +1 -1
  97. package/dist/types.d.ts +67 -0
  98. package/dist/types.d.ts.map +1 -1
  99. package/docs/reference/channels.md +13 -11
  100. package/package.json +1 -1
  101. package/src/channels/deployments/deployments-channel.ts +7 -6
  102. package/src/channels/deployments/index.ts +2 -3
  103. package/src/channels/deployments/types.ts +14 -14
  104. package/src/internal/cursor-deployment-relay.ts +38 -36
  105. package/src/internal/discovery.ts +20 -0
  106. package/src/internal/event-mapper.ts +10 -1
  107. package/src/internal/http-channel.ts +1 -0
  108. package/src/internal/mcp-endpoint.ts +9 -0
  109. package/src/internal/prompt-context.ts +25 -2
  110. package/src/internal/review-comments.ts +17 -7
  111. package/src/internal/server.ts +13 -13
  112. package/src/internal/session-engine.ts +120 -31
  113. package/src/internal/tool-policy.ts +92 -0
  114. package/src/tools.ts +19 -0
  115. package/src/types.ts +73 -0
  116. package/dist/docs/assets/chunks/@localSearchIndexroot.D9uScPWb.js +0 -1
  117. package/dist/playground/assets/index-ChETdG-_.css +0 -1
  118. /package/dist/docs/assets/{reference_channels.md.G5GysZ7e.lean.js → reference_channels.md.CboFd5IH.lean.js} +0 -0
@@ -22,13 +22,13 @@ const DEFAULT_EVENTS: readonly DeploymentEventType[] = ["deployment.succeeded"];
22
22
  export type DeploymentRelayEvent = DeploymentEvent & { offset: string };
23
23
 
24
24
  export interface ResolvedSubscription {
25
- repo: string;
25
+ deploySourceUri: string;
26
26
  environments: string[];
27
27
  events: Set<DeploymentEventType>;
28
28
  }
29
29
 
30
30
  interface SubscriptionStatus {
31
- repo: string;
31
+ deploySourceUri: string;
32
32
  isSubscribed: boolean;
33
33
  detail?: string;
34
34
  }
@@ -44,22 +44,22 @@ export interface DeploymentEventRelayOptions {
44
44
  pollWaitMs?: number;
45
45
  }
46
46
 
47
- /** Normalize the channel's `repos` option into concrete subscriptions. */
47
+ /** Normalize the channel's `deploySourceUris` option into concrete subscriptions. */
48
48
  export function resolveDeploymentSubscriptions(options: {
49
- repos: readonly (string | DeploymentSubscription)[];
49
+ deploySourceUris: readonly (string | DeploymentSubscription)[];
50
50
  environments?: readonly string[];
51
51
  events?: readonly DeploymentEventType[];
52
52
  }): ResolvedSubscription[] {
53
- const byRepo = new Map<string, ResolvedSubscription>();
54
- for (const entry of options.repos) {
53
+ const bySource = new Map<string, ResolvedSubscription>();
54
+ for (const entry of options.deploySourceUris) {
55
55
  const subscription: DeploymentSubscription =
56
- typeof entry === "string" ? { repo: entry } : entry;
57
- // Case-folded only: `Deployment.repository` has no documented format, and
58
- // the server keys subscriptions the same way.
59
- const repo = subscription.repo.trim().toLowerCase();
60
- if (repo === "") {
56
+ typeof entry === "string" ? { deploySourceUri: entry } : entry;
57
+ // Case-folded only: `Deployment.deploy_source_uri` has no documented
58
+ // format, and the server keys subscriptions the same way.
59
+ const deploySourceUri = subscription.deploySourceUri.trim().toLowerCase();
60
+ if (deploySourceUri === "") {
61
61
  throw new Error(
62
- `deploymentsChannel: invalid repo "${subscription.repo}".`
62
+ `deploymentsChannel: invalid deploy source "${subscription.deploySourceUri}".`
63
63
  );
64
64
  }
65
65
  const environments = [
@@ -74,14 +74,14 @@ export function resolveDeploymentSubscriptions(options: {
74
74
  );
75
75
  if (events.size === 0) {
76
76
  throw new Error(
77
- `deploymentsChannel: repo "${repo}" subscribes to no events.`
77
+ `deploymentsChannel: deploy source "${deploySourceUri}" subscribes to no events.`
78
78
  );
79
79
  }
80
- // Later entries for the same repo widen the earlier one rather than
81
- // silently replacing it.
82
- const existing = byRepo.get(repo);
80
+ // Later entries for the same deploy source widen the earlier one rather
81
+ // than silently replacing it.
82
+ const existing = bySource.get(deploySourceUri);
83
83
  if (existing === undefined) {
84
- byRepo.set(repo, { repo, environments, events });
84
+ bySource.set(deploySourceUri, { deploySourceUri, environments, events });
85
85
  continue;
86
86
  }
87
87
  for (const event of events) existing.events.add(event);
@@ -91,9 +91,10 @@ export function resolveDeploymentSubscriptions(options: {
91
91
  : [...new Set([...existing.environments, ...environments])];
92
92
  }
93
93
  // Empty is legal here and means "this channel wants nothing", which is how
94
- // an env-configured channel stays inert without a repo list. The wire-level
95
- // empty list means deregister, so the host simply never registers it.
96
- return [...byRepo.values()];
94
+ // an env-configured channel stays inert without a deploy-source list. The
95
+ // wire-level empty list means deregister, so the host simply never
96
+ // registers it.
97
+ return [...bySource.values()];
97
98
  }
98
99
 
99
100
  /** Whether one confirmed subscription wants this event. */
@@ -136,7 +137,7 @@ export async function fanOutDeploymentEvent(input: {
136
137
  const matched = input.targets.filter((target) =>
137
138
  target.subscriptions.some(
138
139
  (subscription) =>
139
- subscription.repo === event.repo &&
140
+ subscription.deploySourceUri === event.deploySourceUri &&
140
141
  deploymentEventMatches(subscription, event)
141
142
  )
142
143
  );
@@ -154,7 +155,7 @@ export async function fanOutDeploymentEvent(input: {
154
155
  }
155
156
  }
156
157
  logger(
157
- `[agent-sdk] deployment-events: ${event.eventType} ${event.repo}@${event.sha} (${event.environment}) → ${matched.length - failures.length}/${matched.length} channel(s)`
158
+ `[agent-sdk] deployment-events: ${event.eventType} ${event.deploySourceUri}@${event.deployVersion} (${event.environment}) → ${matched.length - failures.length}/${matched.length} channel(s)`
158
159
  );
159
160
  if (failures.length > 0 && failures.length === matched.length) {
160
161
  throw new Error(
@@ -166,7 +167,7 @@ export async function fanOutDeploymentEvent(input: {
166
167
  export class DeploymentEventRelay {
167
168
  private readonly core: CursorRelayCore<DeploymentRelayEvent>;
168
169
  private subscribed = new Map<string, ResolvedSubscription>();
169
- private readonly loggedSkippedRepos = new Set<string>();
170
+ private readonly loggedSkippedSources = new Set<string>();
170
171
 
171
172
  constructor(private readonly options: DeploymentEventRelayOptions) {
172
173
  const adapter: RelayAdapter<DeploymentRelayEvent> = {
@@ -176,7 +177,7 @@ export class DeploymentEventRelay {
176
177
  buildRegistrationBody: (consumerId) => ({
177
178
  consumerId,
178
179
  subscriptions: options.subscriptions.map((subscription) => ({
179
- repo: subscription.repo,
180
+ deploySourceUri: subscription.deploySourceUri,
180
181
  environments: subscription.environments,
181
182
  events: [...subscription.events],
182
183
  })),
@@ -188,23 +189,24 @@ export class DeploymentEventRelay {
188
189
  subscriptions: [],
189
190
  }),
190
191
  applyRegistrationResponse: (json) => {
191
- const result = json as { repos?: SubscriptionStatus[] };
192
+ const result = json as { deploySourceUris?: SubscriptionStatus[] };
192
193
  this.subscribed = new Map();
193
- this.loggedSkippedRepos.clear();
194
+ this.loggedSkippedSources.clear();
194
195
  const lines: string[] = [];
195
196
  const confirmed: string[] = [];
196
- for (const status of result.repos ?? []) {
197
+ for (const status of result.deploySourceUris ?? []) {
197
198
  const wanted = options.subscriptions.find(
198
- (subscription) => subscription.repo === status.repo
199
+ (subscription) =>
200
+ subscription.deploySourceUri === status.deploySourceUri
199
201
  );
200
202
  if (!status.isSubscribed || wanted === undefined) {
201
203
  lines.push(
202
- `not subscribed to ${status.repo}: ${status.detail ?? "unknown reason"}`
204
+ `not subscribed to ${status.deploySourceUri}: ${status.detail ?? "unknown reason"}`
203
205
  );
204
206
  continue;
205
207
  }
206
- this.subscribed.set(status.repo, wanted);
207
- confirmed.push(status.repo);
208
+ this.subscribed.set(status.deploySourceUri, wanted);
209
+ confirmed.push(status.deploySourceUri);
208
210
  }
209
211
  if (confirmed.length > 0) {
210
212
  const kinds = [
@@ -247,16 +249,16 @@ export class DeploymentEventRelay {
247
249
  if (
248
250
  envelope === null ||
249
251
  typeof envelope !== "object" ||
250
- typeof envelope.repo !== "string"
252
+ typeof envelope.deploySourceUri !== "string"
251
253
  ) {
252
254
  return false;
253
255
  }
254
- const subscription = this.subscribed.get(envelope.repo);
256
+ const subscription = this.subscribed.get(envelope.deploySourceUri);
255
257
  if (subscription === undefined) {
256
- if (!this.loggedSkippedRepos.has(envelope.repo)) {
257
- this.loggedSkippedRepos.add(envelope.repo);
258
+ if (!this.loggedSkippedSources.has(envelope.deploySourceUri)) {
259
+ this.loggedSkippedSources.add(envelope.deploySourceUri);
258
260
  this.options.logger(
259
- `[agent-sdk] deployment-events: skipping events for ${envelope.repo}: not in this host's subscription (another consumer of this API key likely subscribed it)`
261
+ `[agent-sdk] deployment-events: skipping events for ${envelope.deploySourceUri}: not in this host's subscription (another consumer of this API key likely subscribed it)`
260
262
  );
261
263
  }
262
264
  return false;
@@ -59,6 +59,7 @@ import {
59
59
  type SkillDefinition,
60
60
  type SkillFileSource,
61
61
  type ToolDefinition,
62
+ type ToolEffect,
62
63
  toPublicCloudInfo,
63
64
  } from "../types.js";
64
65
  import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
@@ -870,6 +871,7 @@ async function loadTools(
870
871
  const execution = tool.execution === "agent" ? "agent" : "server";
871
872
  const needsApprovalConfigured =
872
873
  tool.needsApproval === true || typeof tool.needsApproval === "function";
874
+ const effect = declaredEffect(tool);
873
875
  if (execution === "agent") {
874
876
  if (isBlank(tool.script)) {
875
877
  ctx.diagnostics.push({
@@ -935,6 +937,7 @@ async function loadTools(
935
937
  ...(outputSchema === undefined ? {} : { outputSchema }),
936
938
  execution,
937
939
  needsApproval: needsApprovalConfigured,
940
+ ...(effect === undefined ? {} : { effect }),
938
941
  ...(execution === "agent" && tool.script !== undefined
939
942
  ? { script: tool.script }
940
943
  : {}),
@@ -944,6 +947,23 @@ async function loadTools(
944
947
  return tools;
945
948
  }
946
949
 
950
+ /**
951
+ * Static view of a tool's `effect` for the manifest: the literal when it is
952
+ * one, `"dynamic"` for a per-call predicate, `undefined` when undeclared. A
953
+ * malformed value is left undeclared rather than guessed — undeclared is the
954
+ * safe state (a dry run stubs it), where a guess could be the unsafe one.
955
+ */
956
+ function declaredEffect(
957
+ tool: ToolDefinition
958
+ ): ToolEffect | "dynamic" | undefined {
959
+ if (typeof tool.effect === "function") {
960
+ return "dynamic";
961
+ }
962
+ return tool.effect === "read" || tool.effect === "write"
963
+ ? tool.effect
964
+ : undefined;
965
+ }
966
+
947
967
  // ============================================================================
948
968
  // Skills
949
969
  // ============================================================================
@@ -88,7 +88,15 @@ export class TurnEventMapper {
88
88
  /** Per-parent-call accumulators for nested subagent streams. */
89
89
  private readonly nested = new Map<string, NestedAccumulators>();
90
90
 
91
- constructor(private readonly emit: (payload: SessionEventPayload) => void) {}
91
+ /**
92
+ * @param isStubbed Reports whether a finished tool call was answered by the
93
+ * host rather than executed. The harness reports an answered call exactly
94
+ * like a real one, so the engine is the only place that knows.
95
+ */
96
+ constructor(
97
+ private readonly emit: (payload: SessionEventPayload) => void,
98
+ private readonly isStubbed: (callId: string) => boolean = () => false
99
+ ) {}
92
100
 
93
101
  /** Per-turn token usage summed across model steps. */
94
102
  get usage(): TurnUsage | undefined {
@@ -303,6 +311,7 @@ export class TurnEventMapper {
303
311
  toolName: description.toolName,
304
312
  output: result.output,
305
313
  isError: result.isError,
314
+ ...(this.isStubbed(callId) ? { stubbed: true as const } : {}),
306
315
  parentCallId,
307
316
  },
308
317
  });
@@ -109,6 +109,7 @@ export function buildHttpChannelRoutes(args: {
109
109
  ...(body.state === undefined ? {} : { state: body.state as JsonValue }),
110
110
  ...(typeof body.title === "string" ? { title: body.title } : {}),
111
111
  ...(body.purpose === "eval" ? { purpose: "eval" as const } : {}),
112
+ ...(body.dryRun === true ? { dryRun: true } : {}),
112
113
  ...(workspaceFiles === undefined ? {} : { workspaceFiles }),
113
114
  ...(cloud === undefined ? {} : { cloud }),
114
115
  });
@@ -312,6 +312,15 @@ function buildConnectionBridgeMcpServer(
312
312
  if (reason !== undefined) {
313
313
  return errorResult(reason);
314
314
  }
315
+ // The one tool path that never reaches `executeServerTool`, so it asks
316
+ // the policy itself.
317
+ const answer = engine.bridgedCallAnswer({
318
+ sessionId: sessionId ?? "",
319
+ toolName: request.params.name,
320
+ });
321
+ if (answer.answered) {
322
+ return sdkResultToMcpResult(answer.result);
323
+ }
315
324
  try {
316
325
  const result = await engine.host.mcp.callTool(
317
326
  connectionName,
@@ -39,17 +39,40 @@ export interface SessionContextInput {
39
39
  */
40
40
  channelKind?: string;
41
41
  environment: PromptEnvironment;
42
- /** Injectable clock for tests; defaults to now. */
42
+ /**
43
+ * Injectable clock for tests; defaults to the mocked prompt time when
44
+ * `CURSOR_SDK_MOCK_PROMPT_TIME` is set, then to now.
45
+ */
43
46
  now?: Date;
44
47
  }
45
48
 
49
+ /**
50
+ * Development-only override for the wall clock rendered into prompt content.
51
+ * Same variable the Cursor SDK harness reads for its prompt clock (the
52
+ * user-message <timestamp> prefix and user_info date), so one knob keeps the
53
+ * session context block and the harness-rendered clock consistent. Read on
54
+ * every render so a simulation harness can advance the instant between
55
+ * sessions. Never affects runtime clocks (schedules, reminders) or tools.
56
+ */
57
+ const MOCK_PROMPT_TIME_ENV = "CURSOR_SDK_MOCK_PROMPT_TIME";
58
+
59
+ function readMockPromptTimeFromEnv(): Date | undefined {
60
+ const raw = process.env[MOCK_PROMPT_TIME_ENV];
61
+ if (raw === undefined || raw === "") {
62
+ return undefined;
63
+ }
64
+ const parsed = new Date(raw);
65
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
66
+ }
67
+
46
68
  export function buildSessionContextBlock(input: SessionContextInput): string {
69
+ const now = input.now ?? readMockPromptTimeFromEnv() ?? new Date();
47
70
  const lines = [
48
71
  "<agentkit_context>",
49
72
  `session: ${input.sessionId}`,
50
73
  `channel: ${input.channelId}`,
51
74
  `environment: ${input.environment}`,
52
- `time: ${(input.now ?? new Date()).toISOString()}`,
75
+ `time: ${now.toISOString()}`,
53
76
  "</agentkit_context>",
54
77
  ];
55
78
  const presence = channelPresenceLine(input);
@@ -690,10 +690,13 @@ async function postReviewViaHostGithub(
690
690
  /**
691
691
  * Ids of the inline comments a just-created review owns.
692
692
  *
693
- * `pulls.createReview` returns the review, not its comments, so they come
694
- * from the review-scoped listing. Scoping to the review matters: the
695
- * PR-wide list is ordered oldest-first and runs to thousands on a busy PR,
696
- * so the comments we just created are the ones a page cap would drop.
693
+ * `pulls.createReview` returns the review, not its comments.
694
+ * `listCommentsForReview` either omits those rows or returns them with
695
+ * `line` / `original_line` unset, so the line filter drops every id, the
696
+ * store records no findings, and the next run cannot judge or resolve
697
+ * those threads. Read the PR-wide list instead and keep only this
698
+ * review's rows. Newest-first so a busy PR's older comments cannot push
699
+ * ours past the page cap.
697
700
  * Best-effort: any failure yields an empty list, which costs the next run
698
701
  * its prior findings rather than risking a wrong id. Never throws — the
699
702
  * review is already posted by this point.
@@ -714,22 +717,29 @@ async function readPostedComments(args: {
714
717
  const posted: PostedReviewComment[] = [];
715
718
  for (let page = 1; page <= REVIEW_COMMENTS_MAX_PAGES; page++) {
716
719
  args.signal?.throwIfAborted();
717
- const response = await args.octokit.rest.pulls.listCommentsForReview({
720
+ const response = await args.octokit.rest.pulls.listReviewComments({
718
721
  owner: args.owner,
719
722
  repo: args.repo,
720
723
  pull_number: args.prNumber,
721
- review_id: args.reviewId,
724
+ sort: "created",
725
+ direction: "desc",
722
726
  per_page: REVIEW_COMMENTS_PAGE_SIZE,
723
727
  page,
724
728
  });
725
729
  for (const comment of response.data) {
730
+ if (comment.pull_request_review_id !== args.reviewId) {
731
+ continue;
732
+ }
726
733
  const line = comment.original_line ?? comment.line;
727
734
  if (line === null || line === undefined) {
728
735
  continue;
729
736
  }
730
737
  posted.push({ id: comment.id, file: comment.path, line });
731
738
  }
732
- if (response.data.length < REVIEW_COMMENTS_PAGE_SIZE) {
739
+ if (
740
+ posted.length >= args.expected ||
741
+ response.data.length < REVIEW_COMMENTS_PAGE_SIZE
742
+ ) {
733
743
  break;
734
744
  }
735
745
  }
@@ -2291,7 +2291,7 @@ function collectCursorDeploymentTargets(
2291
2291
  }
2292
2292
  const subscriptions = resolveDeploymentSubscriptions(options);
2293
2293
  if (subscriptions.length === 0) {
2294
- // Declared the channel but named no repositories — usually an unset
2294
+ // Declared the channel but named no deploy sources — usually an unset
2295
2295
  // env var. Say so rather than starting a relay that receives nothing.
2296
2296
  skipped.push(`${runtime.slug || runtime.project.name}/${channel.id}`);
2297
2297
  continue;
@@ -2305,7 +2305,7 @@ function collectCursorDeploymentTargets(
2305
2305
  /**
2306
2306
  * Cursor deployment-events relay: automatic when a mounted agent authored
2307
2307
  * `deploymentsChannel({ ... })`. One relay per process reads
2308
- * `/v0/deployment-events` for the union of every channel's repositories, and
2308
+ * `/v0/deployment-events` for the union of every channel's deploy sources, and
2309
2309
  * each event is routed to the channels that asked for it — the same ownership
2310
2310
  * `startCursorSlackRelay` and `startCursorEventRelay` have.
2311
2311
  */
@@ -2320,7 +2320,7 @@ function startCursorDeploymentRelay(input: {
2320
2320
  const targets = collectCursorDeploymentTargets(runtimes, skipped);
2321
2321
  if (skipped.length > 0) {
2322
2322
  logger(
2323
- `[agent-sdk] deployment-events: no repositories declared on ${skipped.join(", ")}; those channels receive nothing`
2323
+ `[agent-sdk] deployment-events: no deploy sources declared on ${skipped.join(", ")}; those channels receive nothing`
2324
2324
  );
2325
2325
  }
2326
2326
  if (targets.length === 0) {
@@ -2328,19 +2328,19 @@ function startCursorDeploymentRelay(input: {
2328
2328
  }
2329
2329
  if (accountKey === undefined) {
2330
2330
  throw new Error(
2331
- `This project has channels subscribed to deployment events (deploymentsChannel({ repos })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`
2331
+ `This project has channels subscribed to deployment events (deploymentsChannel({ deploySourceUris })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`
2332
2332
  );
2333
2333
  }
2334
- // Union across channels: one registration per repo for the whole process,
2335
- // however many channels named it. Per-channel environment and event-type
2336
- // filtering happens at dispatch.
2337
- const byRepo = new Map<string, ResolvedSubscription>();
2334
+ // Union across channels: one registration per deploy source for the whole
2335
+ // process, however many channels named it. Per-channel environment and
2336
+ // event-type filtering happens at dispatch.
2337
+ const bySource = new Map<string, ResolvedSubscription>();
2338
2338
  for (const target of targets) {
2339
2339
  for (const subscription of target.subscriptions) {
2340
- const existing = byRepo.get(subscription.repo);
2340
+ const existing = bySource.get(subscription.deploySourceUri);
2341
2341
  if (existing === undefined) {
2342
- byRepo.set(subscription.repo, {
2343
- repo: subscription.repo,
2342
+ bySource.set(subscription.deploySourceUri, {
2343
+ deploySourceUri: subscription.deploySourceUri,
2344
2344
  environments: [...subscription.environments],
2345
2345
  events: new Set(subscription.events),
2346
2346
  });
@@ -2369,7 +2369,7 @@ function startCursorDeploymentRelay(input: {
2369
2369
  baseUrl: cursorExternalApiUrl(),
2370
2370
  apiKey: accountKey.apiKey,
2371
2371
  stateDir: join(baseStateRoot, "deployment-events"),
2372
- subscriptions: [...byRepo.values()],
2372
+ subscriptions: [...bySource.values()],
2373
2373
  logger,
2374
2374
  dispatch: async (event) =>
2375
2375
  fanOutDeploymentEvent({
@@ -2392,7 +2392,7 @@ function startCursorDeploymentRelay(input: {
2392
2392
  });
2393
2393
  relay.start();
2394
2394
  logger(
2395
- `[agent-sdk] deployment-events: relaying ${byRepo.size} repository subscription(s) to ${targets.length} channel(s)`
2395
+ `[agent-sdk] deployment-events: relaying ${bySource.size} deploy-source subscription(s) to ${targets.length} channel(s)`
2396
2396
  );
2397
2397
  return relay;
2398
2398
  }