@cursor/july 0.1.12 → 0.1.15

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 (168) hide show
  1. package/dist/bin/agent-serve.js +32 -9
  2. package/dist/channels/github/api.d.ts.map +1 -1
  3. package/dist/channels/github/api.js +11 -4
  4. package/dist/channels/github/cursor-account.d.ts +21 -1
  5. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  6. package/dist/channels/github/cursor-account.js +29 -1
  7. package/dist/channels/github/github-channel.d.ts.map +1 -1
  8. package/dist/channels/github/github-channel.js +1 -1
  9. package/dist/channels/slack/api.d.ts +9 -0
  10. package/dist/channels/slack/api.d.ts.map +1 -1
  11. package/dist/channels/slack/api.js +40 -7
  12. package/dist/channels/slack/eval-directive.d.ts +3 -0
  13. package/dist/channels/slack/eval-directive.d.ts.map +1 -1
  14. package/dist/channels/slack/eval-directive.js +20 -1
  15. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  16. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  17. package/dist/docs/404.html +2 -2
  18. package/dist/docs/ab.html +3 -3
  19. package/dist/docs/assets/{app.Dh_JkJjI.js → app.DpdGZTLe.js} +1 -1
  20. package/dist/docs/assets/chunks/@localSearchIndexroot.D2d8CfFS.js +1 -0
  21. package/dist/docs/assets/chunks/{VPLocalSearchBox.CptQNLkR.js → VPLocalSearchBox.IB-0QqIT.js} +1 -1
  22. package/dist/docs/assets/chunks/{theme.DWflH-LQ.js → theme.-fYBoNtR.js} +2 -2
  23. package/dist/docs/assets/{evals.md.DAgEc_hL.js → evals.md.DYOjkRCX.js} +10 -4
  24. package/dist/docs/assets/{evals.md.DAgEc_hL.lean.js → evals.md.DYOjkRCX.lean.js} +1 -1
  25. package/dist/docs/assets/{reference_cli.md.B74NrVzb.js → reference_cli.md.DnYfr5V2.js} +4 -1
  26. package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.js → reference_http-api.md.BncLd3PZ.js} +2 -2
  27. package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.lean.js → reference_http-api.md.BncLd3PZ.lean.js} +1 -1
  28. package/dist/docs/building-with-agents.html +3 -3
  29. package/dist/docs/concepts.html +3 -3
  30. package/dist/docs/deployment.html +3 -3
  31. package/dist/docs/evals.html +13 -7
  32. package/dist/docs/example-agents/approval-buddy.html +3 -3
  33. package/dist/docs/example-agents/benny.html +3 -3
  34. package/dist/docs/example-agents/bugbot.html +3 -3
  35. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  36. package/dist/docs/example-agents/codeowners-review.html +3 -3
  37. package/dist/docs/example-agents/concierge.html +3 -3
  38. package/dist/docs/example-agents/fsd.html +3 -3
  39. package/dist/docs/example-agents/index.html +3 -3
  40. package/dist/docs/example-agents/knowledge-base.html +3 -3
  41. package/dist/docs/example-agents/oncall.html +3 -3
  42. package/dist/docs/example-agents/security-reviewer.html +3 -3
  43. package/dist/docs/example-agents/slack-agent.html +3 -3
  44. package/dist/docs/example-agents/weather-agent.html +3 -3
  45. package/dist/docs/guides/agent-to-agent.html +3 -3
  46. package/dist/docs/guides/cloud-runtime.html +3 -3
  47. package/dist/docs/guides/github.html +3 -3
  48. package/dist/docs/guides/human-in-the-loop.html +3 -3
  49. package/dist/docs/guides/mcp-oauth.html +3 -3
  50. package/dist/docs/guides/slack.html +3 -3
  51. package/dist/docs/guides/webhooks.html +3 -3
  52. package/dist/docs/hashmap.json +1 -1
  53. package/dist/docs/hillclimbing.html +3 -3
  54. package/dist/docs/index.html +3 -3
  55. package/dist/docs/quickstart.html +3 -3
  56. package/dist/docs/reference/agent-config.html +3 -3
  57. package/dist/docs/reference/channels.html +3 -3
  58. package/dist/docs/reference/cli.html +8 -5
  59. package/dist/docs/reference/connections.html +3 -3
  60. package/dist/docs/reference/hooks.html +3 -3
  61. package/dist/docs/reference/http-api.html +5 -5
  62. package/dist/docs/reference/instructions.html +3 -3
  63. package/dist/docs/reference/playground.html +3 -3
  64. package/dist/docs/reference/project-layout.html +3 -3
  65. package/dist/docs/reference/prompt.html +3 -3
  66. package/dist/docs/reference/schedules.html +3 -3
  67. package/dist/docs/reference/sessions.html +3 -3
  68. package/dist/docs/reference/skills.html +3 -3
  69. package/dist/docs/reference/subagents.html +3 -3
  70. package/dist/docs/reference/tools.html +3 -3
  71. package/dist/docs/scaffolding-agents.html +3 -3
  72. package/dist/docs/storage.html +3 -3
  73. package/dist/docs/troubleshooting.html +3 -3
  74. package/dist/evals.d.ts +49 -2
  75. package/dist/evals.d.ts.map +1 -1
  76. package/dist/evals.js +53 -16
  77. package/dist/internal/cli-ax.d.ts +4 -0
  78. package/dist/internal/cli-ax.d.ts.map +1 -1
  79. package/dist/internal/cli-ax.js +166 -33
  80. package/dist/internal/cli-deploy.d.ts +8 -0
  81. package/dist/internal/cli-deploy.d.ts.map +1 -1
  82. package/dist/internal/cli-deploy.js +92 -12
  83. package/dist/internal/cli-mcp-oauth.d.ts +14 -2
  84. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
  85. package/dist/internal/cli-mcp-oauth.js +95 -2
  86. package/dist/internal/cursor/backend-client.d.ts +34 -0
  87. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  88. package/dist/internal/cursor/backend-client.js +73 -9
  89. package/dist/internal/cursor/credentials.d.ts +7 -1
  90. package/dist/internal/cursor/credentials.d.ts.map +1 -1
  91. package/dist/internal/cursor/credentials.js +9 -3
  92. package/dist/internal/cursor-account-mcp-auth.d.ts +55 -0
  93. package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -0
  94. package/dist/internal/cursor-account-mcp-auth.js +175 -0
  95. package/dist/internal/deploy-client.d.ts +6 -0
  96. package/dist/internal/deploy-client.d.ts.map +1 -1
  97. package/dist/internal/deploy-client.js +13 -0
  98. package/dist/internal/eval-run-store.d.ts +15 -0
  99. package/dist/internal/eval-run-store.d.ts.map +1 -1
  100. package/dist/internal/eval-run-store.js +94 -8
  101. package/dist/internal/eval-runner.d.ts +14 -0
  102. package/dist/internal/eval-runner.d.ts.map +1 -1
  103. package/dist/internal/eval-runner.js +111 -22
  104. package/dist/internal/evals-client.d.ts +9 -1
  105. package/dist/internal/evals-client.d.ts.map +1 -1
  106. package/dist/internal/evals-client.js +44 -1
  107. package/dist/internal/http-channel.d.ts.map +1 -1
  108. package/dist/internal/http-channel.js +74 -7
  109. package/dist/internal/map-pool.d.ts +6 -1
  110. package/dist/internal/map-pool.d.ts.map +1 -1
  111. package/dist/internal/map-pool.js +8 -1
  112. package/dist/internal/mcp-host.d.ts +13 -0
  113. package/dist/internal/mcp-host.d.ts.map +1 -1
  114. package/dist/internal/mcp-host.js +68 -13
  115. package/dist/internal/persistence-coordinator.d.ts +127 -0
  116. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  117. package/dist/internal/run-client.d.ts +5 -1
  118. package/dist/internal/run-client.d.ts.map +1 -1
  119. package/dist/internal/run-client.js +26 -5
  120. package/dist/internal/server.d.ts.map +1 -1
  121. package/dist/internal/server.js +57 -4
  122. package/dist/internal/session-engine.d.ts +13 -0
  123. package/dist/internal/session-engine.d.ts.map +1 -1
  124. package/dist/internal/session-engine.js +49 -0
  125. package/dist/internal/storage-coordinator.d.ts +7 -0
  126. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  127. package/dist/internal/storage-coordinator.js +29 -4
  128. package/dist/persistence.d.ts +184 -0
  129. package/dist/persistence.d.ts.map +1 -0
  130. package/dist/playground/assets/{index-1dzluIcW.js → index-BVMOoiek.js} +44 -44
  131. package/dist/playground/assets/index-DYmP4b--.css +1 -0
  132. package/dist/playground/index.html +2 -2
  133. package/dist/types.d.ts +10 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/docs/evals.md +46 -11
  136. package/docs/reference/cli.md +3 -0
  137. package/docs/reference/http-api.md +2 -1
  138. package/package.json +1 -1
  139. package/skills/evals/SKILL.md +10 -0
  140. package/src/bin/agent-serve.ts +38 -8
  141. package/src/channels/github/api.ts +12 -1
  142. package/src/channels/github/cursor-account.ts +51 -1
  143. package/src/channels/github/github-channel.ts +4 -1
  144. package/src/channels/slack/api.ts +57 -8
  145. package/src/channels/slack/eval-directive.ts +24 -1
  146. package/src/evals.ts +112 -25
  147. package/src/internal/cli-ax.ts +195 -16
  148. package/src/internal/cli-deploy.ts +140 -13
  149. package/src/internal/cli-mcp-oauth.ts +122 -3
  150. package/src/internal/cursor/backend-client.ts +104 -1
  151. package/src/internal/cursor/credentials.ts +10 -2
  152. package/src/internal/cursor-account-mcp-auth.ts +241 -0
  153. package/src/internal/deploy-client.ts +16 -0
  154. package/src/internal/eval-run-store.ts +97 -6
  155. package/src/internal/eval-runner.ts +129 -18
  156. package/src/internal/evals-client.ts +82 -0
  157. package/src/internal/http-channel.ts +87 -6
  158. package/src/internal/map-pool.ts +8 -1
  159. package/src/internal/mcp-host.ts +74 -16
  160. package/src/internal/run-client.ts +32 -1
  161. package/src/internal/server.ts +69 -2
  162. package/src/internal/session-engine.ts +58 -0
  163. package/src/internal/storage-coordinator.ts +32 -6
  164. package/src/types.ts +10 -0
  165. package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +0 -1
  166. package/dist/internal/json-dir-store.js +0 -100
  167. package/dist/playground/assets/index-kZjeJs1v.css +0 -1
  168. /package/dist/docs/assets/{reference_cli.md.B74NrVzb.lean.js → reference_cli.md.DnYfr5V2.lean.js} +0 -0
@@ -6,7 +6,11 @@
6
6
  * compact {@link Trajectory} plus the continuation token for follow-ups.
7
7
  */
8
8
 
9
- import type { SessionEvent, SessionPurpose } from "../types.js";
9
+ import type {
10
+ AgentCloudOptions,
11
+ SessionEvent,
12
+ SessionPurpose,
13
+ } from "../types.js";
10
14
  import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
11
15
  import { buildTrajectory, type Trajectory } from "./trajectory.js";
12
16
 
@@ -24,6 +28,8 @@ export interface RunClientOptions extends RequestAuth {
24
28
  baseUrl: string;
25
29
  /** Abort a hung turn. */
26
30
  timeoutMs?: number;
31
+ /** External cancel (combined with {@link timeoutMs} when both are set). */
32
+ signal?: AbortSignal;
27
33
  /** Called for each stream event as it arrives (for CLI progress). */
28
34
  onEvent?: (event: SessionEvent) => void;
29
35
  /**
@@ -61,6 +67,8 @@ export async function runSession(
61
67
  workspaceDir?: string;
62
68
  /** Applied only when creating a new session (first message). */
63
69
  workspaceFiles?: Record<string, string>;
70
+ /** Applied only when creating a new session (first message). */
71
+ cloud?: AgentCloudOptions;
64
72
  }
65
73
  ): Promise<RunSessionResult> {
66
74
  if (options.messages.length === 0) {
@@ -86,11 +94,13 @@ export async function runSession(
86
94
  startIndex: sessionId === undefined ? 0 : nextEventIndex,
87
95
  auth,
88
96
  timeoutMs: options.timeoutMs,
97
+ signal: options.signal,
89
98
  onEvent: options.onEvent,
90
99
  purpose: sessionId === undefined ? options.purpose : undefined,
91
100
  workspaceDir: sessionId === undefined ? options.workspaceDir : undefined,
92
101
  workspaceFiles:
93
102
  sessionId === undefined ? options.workspaceFiles : undefined,
103
+ cloud: sessionId === undefined ? options.cloud : undefined,
94
104
  });
95
105
  sessionId = turn.sessionId;
96
106
  continuationToken = turn.continuationToken;
@@ -127,9 +137,11 @@ interface OneTurnArgs {
127
137
  purpose?: SessionPurpose;
128
138
  workspaceDir?: string;
129
139
  workspaceFiles?: Record<string, string>;
140
+ cloud?: AgentCloudOptions;
130
141
  startIndex: number;
131
142
  auth?: RequestAuth;
132
143
  timeoutMs?: number;
144
+ signal?: AbortSignal;
133
145
  onEvent?: (event: SessionEvent) => void;
134
146
  }
135
147
 
@@ -155,6 +167,9 @@ async function runOneTurn(args: OneTurnArgs): Promise<OneTurnResult> {
155
167
  if (args.workspaceFiles !== undefined) {
156
168
  body.workspaceFiles = args.workspaceFiles;
157
169
  }
170
+ if (args.cloud !== undefined) {
171
+ body.cloud = args.cloud;
172
+ }
158
173
  const created = await postJson(
159
174
  args.baseUrl,
160
175
  "/v1/session",
@@ -193,6 +208,7 @@ async function runOneTurn(args: OneTurnArgs): Promise<OneTurnResult> {
193
208
  startIndex: args.startIndex,
194
209
  auth: args.auth,
195
210
  timeoutMs: args.timeoutMs,
211
+ signal: args.signal,
196
212
  onEvent: args.onEvent,
197
213
  });
198
214
 
@@ -212,9 +228,20 @@ async function collectStream(args: {
212
228
  startIndex: number;
213
229
  auth?: RequestAuth;
214
230
  timeoutMs?: number;
231
+ signal?: AbortSignal;
215
232
  onEvent?: (event: SessionEvent) => void;
216
233
  }): Promise<SessionEvent[]> {
217
234
  const controller = new AbortController();
235
+ const onExternalAbort = (): void => {
236
+ controller.abort();
237
+ };
238
+ if (args.signal !== undefined) {
239
+ if (args.signal.aborted) {
240
+ controller.abort();
241
+ } else {
242
+ args.signal.addEventListener("abort", onExternalAbort, { once: true });
243
+ }
244
+ }
218
245
  const timer =
219
246
  args.timeoutMs === undefined
220
247
  ? undefined
@@ -271,6 +298,9 @@ async function collectStream(args: {
271
298
  return events;
272
299
  } catch (error) {
273
300
  if (error instanceof Error && error.name === "AbortError") {
301
+ if (args.signal?.aborted) {
302
+ throw new Error("cancelled");
303
+ }
274
304
  throw new RunSessionTimeoutError(
275
305
  `turn timed out after ${args.timeoutMs ?? "?"}ms (session ${args.sessionId})`,
276
306
  args.sessionId
@@ -278,6 +308,7 @@ async function collectStream(args: {
278
308
  }
279
309
  throw error;
280
310
  } finally {
311
+ args.signal?.removeEventListener("abort", onExternalAbort);
281
312
  if (timer !== undefined) {
282
313
  clearTimeout(timer);
283
314
  }
@@ -21,6 +21,7 @@ import {
21
21
  getGithubCursorAccountRuntime,
22
22
  parseCursorGithubRepoScope,
23
23
  resolveCursorGithubPermissions,
24
+ resolveCursorGithubProxyUrl,
24
25
  } from "../channels/github/cursor-account.js";
25
26
  import { findGitHubChannelTargets } from "../channels/github/forward.js";
26
27
  import {
@@ -80,7 +81,9 @@ import {
80
81
  import {
81
82
  EvalRunConflictError,
82
83
  EvalRunEmptyError,
84
+ EvalRunNotRunningError,
83
85
  EvalRunStore,
86
+ EvalRunUnknownError,
84
87
  } from "./eval-run-store.js";
85
88
  import {
86
89
  collectGitHubTriggerTargets,
@@ -251,8 +254,35 @@ export async function startServer(
251
254
  logger,
252
255
  });
253
256
  cursorGithubCredentials = provider;
254
- for (const target of cursorGithubTargets) {
255
- target.accountRuntime.credentials.bind(() => provider.getToken());
257
+ const githubProxyUrl =
258
+ options.cursorGithubProxy === true
259
+ ? resolveCursorGithubProxyUrl(cursorEventsAuth.baseUrl)
260
+ : undefined;
261
+ if (githubProxyUrl !== undefined) {
262
+ // Proxy mode: channel Octokit calls carry the Cursor API key to the
263
+ // backend forwarder, which swaps in the installation token server-side
264
+ // and logs every call with per-deployment attribution. The provider
265
+ // still runs so child `gh` processes get a `GH_TOKEN` lease (v1).
266
+ const proxyTransport = {
267
+ apiBaseUrl: githubProxyUrl,
268
+ headers: {
269
+ "x-cursor-github-repos": cursorGithubConfig.scope.repos.join(","),
270
+ "x-cursor-github-permissions": cursorGithubConfig.scope.permissions,
271
+ },
272
+ };
273
+ for (const target of cursorGithubTargets) {
274
+ target.accountRuntime.credentials.bind(
275
+ async () => cursorEventsAuth.apiKey
276
+ );
277
+ target.accountRuntime.credentials.bindProxyTransport(proxyTransport);
278
+ }
279
+ logger(
280
+ `[agentkit] GitHub auth: channel API calls route through ${githubProxyUrl}`
281
+ );
282
+ } else {
283
+ for (const target of cursorGithubTargets) {
284
+ target.accountRuntime.credentials.bind(() => provider.getToken());
285
+ }
256
286
  }
257
287
  }
258
288
  const resolvedCursorGithubCredentials = cursorGithubCredentials;
@@ -392,6 +422,7 @@ export async function startServer(
392
422
  get: (runId) => evalRuns.get(runId),
393
423
  listRunsWithActive: () => evalRuns.listRunsWithActive(),
394
424
  start: (input) => evalRuns.start(input),
425
+ cancel: (runId) => evalRuns.cancel(runId),
395
426
  });
396
427
  const runtime: MountedRuntime = {
397
428
  slug,
@@ -1171,6 +1202,42 @@ function mountAgentRoutes(
1171
1202
  return jsonResponse(snap);
1172
1203
  },
1173
1204
  });
1205
+ router.add({
1206
+ method: "POST",
1207
+ pattern: `${base}/v1/dev/evals/runs/:runId/cancel`,
1208
+ auth: httpAuth,
1209
+ handler: async (_request, ctx) => {
1210
+ const runId = ctx.params.runId ?? "";
1211
+ try {
1212
+ const snap = await runtime.evalRuns.cancel(runId);
1213
+ return jsonResponse(snap);
1214
+ } catch (error) {
1215
+ if (error instanceof EvalRunUnknownError) {
1216
+ return jsonResponse(
1217
+ { ok: false, error: "unknown_eval_run" },
1218
+ { status: 404 }
1219
+ );
1220
+ }
1221
+ if (error instanceof EvalRunNotRunningError) {
1222
+ return jsonResponse(
1223
+ {
1224
+ ok: false,
1225
+ error: "eval_run_not_running",
1226
+ runId: error.runId,
1227
+ status: error.status,
1228
+ message: error.message,
1229
+ },
1230
+ { status: 409 }
1231
+ );
1232
+ }
1233
+ const message = error instanceof Error ? error.message : String(error);
1234
+ return jsonResponse(
1235
+ { ok: false, error: "eval_cancel_failed", message },
1236
+ { status: 400 }
1237
+ );
1238
+ }
1239
+ },
1240
+ });
1174
1241
 
1175
1242
  if (dev) {
1176
1243
  router.add({
@@ -2335,6 +2335,25 @@ export class SessionEngine {
2335
2335
  }
2336
2336
  }
2337
2337
 
2338
+ /**
2339
+ * Local session lookup with lazy restore by session id.
2340
+ *
2341
+ * `GET /v1/sessions` (cursor-hosted) can list durable rows that were not
2342
+ * bulk-restored into `--state-root` (restore caps, `restore: "off"`, or
2343
+ * eval Open-trace by id). Events/stream/follow-up must materialize that
2344
+ * one session before answering, or the playground shows an empty timeline
2345
+ * after redeploy.
2346
+ */
2347
+ async getOrRestoreSession(
2348
+ sessionId: string
2349
+ ): Promise<SessionRecord | undefined> {
2350
+ const local = await this.sessions.get(sessionId);
2351
+ if (local !== undefined) {
2352
+ return local;
2353
+ }
2354
+ return this.restoreSessionById(sessionId);
2355
+ }
2356
+
2338
2357
  /**
2339
2358
  * Lazy restore: a continuation lookup missed locally, so ask the sink
2340
2359
  * for that one session and materialize it before resuming. Disk grows
@@ -2367,6 +2386,24 @@ export class SessionEngine {
2367
2386
  return pending;
2368
2387
  }
2369
2388
 
2389
+ private restoreSessionById(
2390
+ sessionId: string
2391
+ ): Promise<SessionRecord | undefined> {
2392
+ const storage = this.storage;
2393
+ if (storage === undefined) {
2394
+ return Promise.resolve(undefined);
2395
+ }
2396
+ const key = `id\u0000${sessionId}`;
2397
+ let pending = this.restoreInFlight.get(key);
2398
+ if (pending === undefined) {
2399
+ pending = this.loadAndMaterializeById(storage, sessionId).finally(() =>
2400
+ this.restoreInFlight.delete(key)
2401
+ );
2402
+ this.restoreInFlight.set(key, pending);
2403
+ }
2404
+ return pending;
2405
+ }
2406
+
2370
2407
  private async loadAndMaterializeByContinuation(
2371
2408
  storage: StorageCoordinator,
2372
2409
  channelId: string,
@@ -2380,10 +2417,31 @@ export class SessionEngine {
2380
2417
  // Definitive miss: the caller starts a fresh session under this token.
2381
2418
  return undefined;
2382
2419
  }
2420
+ return this.materializeRestoredRecord(record);
2421
+ }
2422
+
2423
+ private async loadAndMaterializeById(
2424
+ storage: StorageCoordinator,
2425
+ sessionId: string
2426
+ ): Promise<SessionRecord | undefined> {
2427
+ const record = await storage.getSessionById(sessionId);
2428
+ if (record === undefined) {
2429
+ return undefined;
2430
+ }
2431
+ return this.materializeRestoredRecord(record);
2432
+ }
2433
+
2434
+ private async materializeRestoredRecord(
2435
+ record: SessionRecord
2436
+ ): Promise<SessionRecord> {
2383
2437
  const local = await this.sessions.get(record.sessionId);
2384
2438
  if (local !== undefined) {
2385
2439
  return local;
2386
2440
  }
2441
+ const storage = this.storage;
2442
+ if (storage === undefined) {
2443
+ return record;
2444
+ }
2387
2445
  const events = await storage.listSessionEvents(record.sessionId);
2388
2446
  await this.materializeSession(record, eventsNdjson(events));
2389
2447
  this.logger(
@@ -329,6 +329,36 @@ export class StorageCoordinator {
329
329
  return events;
330
330
  }
331
331
 
332
+ /**
333
+ * One saved session by id (lazy restore for playground Open-trace /
334
+ * events / stream after a host replacement). Sink errors **propagate**
335
+ * — same posture as {@link getSessionByContinuation}. A definitive miss
336
+ * (no row, or a write-only sink without `get`) resolves undefined.
337
+ */
338
+ async getSessionById(sessionId: string): Promise<SessionRecord | undefined> {
339
+ const get = this.definition.get;
340
+ if (get === undefined) {
341
+ return undefined;
342
+ }
343
+ if (typeof sessionId !== "string" || sessionId === "") {
344
+ return undefined;
345
+ }
346
+ const ctx = this.context("restore");
347
+ const record = (await get(
348
+ storageKeys.session(this.agentName, sessionId),
349
+ ctx
350
+ )) as SessionRecord | null | undefined;
351
+ if (record == null) {
352
+ return undefined;
353
+ }
354
+ if (record.sessionId !== sessionId) {
355
+ throw new Error(
356
+ `storage returned a session that does not match the requested id ${sessionId}`
357
+ );
358
+ }
359
+ return record;
360
+ }
361
+
332
362
  /**
333
363
  * One saved session by channel continuation key (lazy restore). Unlike
334
364
  * the startup reads, sink errors **propagate**: a failing store must
@@ -363,11 +393,8 @@ export class StorageCoordinator {
363
393
  `storage returned a malformed continuation entry for channel ${channelId}`
364
394
  );
365
395
  }
366
- const record = (await get(
367
- storageKeys.session(this.agentName, sessionId),
368
- ctx
369
- )) as SessionRecord | null | undefined;
370
- if (record == null) {
396
+ const record = await this.getSessionById(sessionId);
397
+ if (record === undefined) {
371
398
  // Index entry without its record (e.g. the record write was shed
372
399
  // under at-most-once). The session is unrecoverable; treat as a miss
373
400
  // so the conversation can continue rather than failing forever.
@@ -380,7 +407,6 @@ export class StorageCoordinator {
380
407
  // mismatched row is a broken store, not a miss; treating it as a miss
381
408
  // would fork the continuation token onto a new session.
382
409
  if (
383
- record.sessionId !== sessionId ||
384
410
  record.channelId !== channelId ||
385
411
  record.continuationKey !== continuationKey
386
412
  ) {
package/src/types.ts CHANGED
@@ -1974,6 +1974,16 @@ export interface ServeOptions {
1974
1974
  * SSO/Okta proxy in front of the host).
1975
1975
  */
1976
1976
  allowAnonymousCursorGithub?: boolean;
1977
+ /**
1978
+ * Route `githubChannel({ cursorAccount })` API calls through the Cursor
1979
+ * backend's GitHub forwarder (`/v0/github-proxy`) instead of minting raw
1980
+ * installation tokens into this process. The backend logs and meters every
1981
+ * call with per-deployment attribution. `AGENT_SERVE_GITHUB_PROXY_URL`
1982
+ * overrides the forwarder base URL (default: the resolved Cursor API base
1983
+ * URL + `/v0/github-proxy`). Child `gh` processes still use a minted
1984
+ * `GH_TOKEN` lease (proxying `gh` is a follow-up).
1985
+ */
1986
+ cursorGithubProxy?: boolean;
1977
1987
  /**
1978
1988
  * Allow {@link allowAnonymous} together with
1979
1989
  * `defineConnection({ cursorAccount: true })`. Without this opt-in, `serve`