@cursor/july 0.1.75 → 0.1.78

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 (143) hide show
  1. package/dist/channels/origin/origin-webhook.d.ts +23 -2
  2. package/dist/channels/origin/origin-webhook.d.ts.map +1 -1
  3. package/dist/channels/origin/origin-webhook.js +82 -20
  4. package/dist/docs/404.html +2 -2
  5. package/dist/docs/ab.html +3 -3
  6. package/dist/docs/assets/{app.b8bhB2SV.js → app.BsA7t7rS.js} +1 -1
  7. package/dist/docs/assets/chunks/@localSearchIndexroot.Dcs6boxD.js +1 -0
  8. package/dist/docs/assets/chunks/{VPLocalSearchBox.lLSeIyWw.js → VPLocalSearchBox.B1hHhktM.js} +1 -1
  9. package/dist/docs/assets/chunks/{theme.C_owesa-.js → theme.DLpLSsQh.js} +2 -2
  10. package/dist/docs/assets/{storage.md.D8NQ389l.js → storage.md.ks1u64_R.js} +3 -3
  11. package/dist/docs/assets/storage.md.ks1u64_R.lean.js +1 -0
  12. package/dist/docs/building-with-agents.html +3 -3
  13. package/dist/docs/concepts.html +3 -3
  14. package/dist/docs/deployment.html +3 -3
  15. package/dist/docs/evals.html +3 -3
  16. package/dist/docs/example-agents/approval-buddy.html +3 -3
  17. package/dist/docs/example-agents/benny.html +3 -3
  18. package/dist/docs/example-agents/bugbot.html +3 -3
  19. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  20. package/dist/docs/example-agents/codeowners-review.html +3 -3
  21. package/dist/docs/example-agents/concierge.html +3 -3
  22. package/dist/docs/example-agents/fsd.html +3 -3
  23. package/dist/docs/example-agents/index.html +3 -3
  24. package/dist/docs/example-agents/knowledge-base.html +3 -3
  25. package/dist/docs/example-agents/oncall.html +3 -3
  26. package/dist/docs/example-agents/security-reviewer.html +3 -3
  27. package/dist/docs/example-agents/slack-agent.html +3 -3
  28. package/dist/docs/example-agents/weather-agent.html +3 -3
  29. package/dist/docs/guides/agent-to-agent.html +3 -3
  30. package/dist/docs/guides/cloud-runtime.html +3 -3
  31. package/dist/docs/guides/convert-automation.html +3 -3
  32. package/dist/docs/guides/github.html +3 -3
  33. package/dist/docs/guides/human-in-the-loop.html +3 -3
  34. package/dist/docs/guides/mcp-oauth.html +3 -3
  35. package/dist/docs/guides/opentelemetry.html +3 -3
  36. package/dist/docs/guides/slack.html +3 -3
  37. package/dist/docs/guides/webhooks.html +3 -3
  38. package/dist/docs/hashmap.json +1 -1
  39. package/dist/docs/hillclimbing.html +3 -3
  40. package/dist/docs/index.html +3 -3
  41. package/dist/docs/quickstart.html +3 -3
  42. package/dist/docs/reference/agent-config.html +3 -3
  43. package/dist/docs/reference/artifacts.html +3 -3
  44. package/dist/docs/reference/channels.html +3 -3
  45. package/dist/docs/reference/cli.html +3 -3
  46. package/dist/docs/reference/connections.html +3 -3
  47. package/dist/docs/reference/hooks.html +3 -3
  48. package/dist/docs/reference/http-api.html +3 -3
  49. package/dist/docs/reference/instructions.html +3 -3
  50. package/dist/docs/reference/playground.html +3 -3
  51. package/dist/docs/reference/project-layout.html +3 -3
  52. package/dist/docs/reference/prompt.html +3 -3
  53. package/dist/docs/reference/schedules.html +3 -3
  54. package/dist/docs/reference/sessions.html +3 -3
  55. package/dist/docs/reference/skills.html +3 -3
  56. package/dist/docs/reference/subagents.html +3 -3
  57. package/dist/docs/reference/tools.html +3 -3
  58. package/dist/docs/scaffolding-agents.html +3 -3
  59. package/dist/docs/storage.html +7 -7
  60. package/dist/docs/templates/agentic-owners.html +3 -3
  61. package/dist/docs/templates/demo.html +3 -3
  62. package/dist/docs/templates/pr-autofixer.html +3 -3
  63. package/dist/docs/templates/security-reviewer.html +3 -3
  64. package/dist/docs/templates/triage.html +3 -3
  65. package/dist/docs/troubleshooting.html +3 -3
  66. package/dist/index.d.ts +0 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +0 -1
  69. package/dist/internal/authored-alias-hooks.d.ts +12 -0
  70. package/dist/internal/authored-alias-hooks.d.ts.map +1 -1
  71. package/dist/internal/authored-alias-hooks.js +60 -8
  72. package/dist/internal/authored-loaders.d.ts +5 -3
  73. package/dist/internal/authored-loaders.d.ts.map +1 -1
  74. package/dist/internal/authored-loaders.js +13 -4
  75. package/dist/internal/deployment-scm-event-stream.js +4 -4
  76. package/dist/internal/discovery.d.ts.map +1 -1
  77. package/dist/internal/discovery.js +3 -21
  78. package/dist/internal/distribution.d.ts.map +1 -1
  79. package/dist/internal/distribution.js +0 -1
  80. package/dist/internal/review-comments.d.ts +62 -0
  81. package/dist/internal/review-comments.d.ts.map +1 -1
  82. package/dist/internal/review-comments.js +199 -0
  83. package/dist/internal/session-engine.d.ts +1 -60
  84. package/dist/internal/session-engine.d.ts.map +1 -1
  85. package/dist/internal/session-engine.js +10 -109
  86. package/dist/kv/memory-kv.d.ts +1 -10
  87. package/dist/kv/memory-kv.d.ts.map +1 -1
  88. package/dist/kv/memory-kv.js +1 -10
  89. package/dist/kv.d.ts +2 -3
  90. package/dist/kv.d.ts.map +1 -1
  91. package/dist/kv.js +2 -3
  92. package/dist/playground/assets/index-DJzcIeGt.css +1 -0
  93. package/dist/playground/assets/{index-BJy9UJrF.js → index-K3Sxyx0I.js} +48 -45
  94. package/dist/playground/index.html +2 -2
  95. package/dist/storage-backends/cursor-hosted.js +3 -3
  96. package/dist/storage-protocol.d.ts +3 -8
  97. package/dist/storage-protocol.d.ts.map +1 -1
  98. package/dist/storage-protocol.js +4 -10
  99. package/dist/storage.d.ts +5 -11
  100. package/dist/storage.d.ts.map +1 -1
  101. package/dist/storage.js +3 -4
  102. package/dist/types.d.ts +5 -34
  103. package/dist/types.d.ts.map +1 -1
  104. package/docs/storage.md +11 -44
  105. package/package.json +1 -8
  106. package/src/channels/origin/origin-webhook.ts +111 -25
  107. package/src/index.ts +0 -6
  108. package/src/internal/authored-alias-hooks.ts +75 -7
  109. package/src/internal/authored-loaders.ts +15 -4
  110. package/src/internal/deployment-scm-event-stream.ts +4 -4
  111. package/src/internal/discovery.ts +3 -27
  112. package/src/internal/distribution.ts +0 -1
  113. package/src/internal/review-comments.ts +286 -0
  114. package/src/internal/session-engine.ts +1 -168
  115. package/src/kv/memory-kv.ts +1 -10
  116. package/src/kv.ts +2 -3
  117. package/src/storage-backends/cursor-hosted.ts +3 -3
  118. package/src/storage-protocol.ts +4 -10
  119. package/src/storage.ts +6 -12
  120. package/src/types.ts +5 -35
  121. package/dist/docs/assets/chunks/@localSearchIndexroot.DGZeXVqn.js +0 -1
  122. package/dist/docs/assets/storage.md.D8NQ389l.lean.js +0 -1
  123. package/dist/internal/durable-session-store.d.ts +0 -60
  124. package/dist/internal/durable-session-store.d.ts.map +0 -1
  125. package/dist/internal/durable-session-store.js +0 -131
  126. package/dist/internal/ledger-serving.d.ts +0 -169
  127. package/dist/internal/ledger-serving.d.ts.map +0 -1
  128. package/dist/internal/ledger-serving.js +0 -393
  129. package/dist/ledger/janitor.d.ts +0 -60
  130. package/dist/ledger/janitor.d.ts.map +0 -1
  131. package/dist/ledger/janitor.js +0 -137
  132. package/dist/ledger/session-ledger.d.ts +0 -293
  133. package/dist/ledger/session-ledger.d.ts.map +0 -1
  134. package/dist/ledger/session-ledger.js +0 -614
  135. package/dist/ledger.d.ts +0 -12
  136. package/dist/ledger.d.ts.map +0 -1
  137. package/dist/ledger.js +0 -9
  138. package/dist/playground/assets/index-BrSd5vff.css +0 -1
  139. package/src/internal/durable-session-store.ts +0 -134
  140. package/src/internal/ledger-serving.ts +0 -531
  141. package/src/ledger/janitor.ts +0 -193
  142. package/src/ledger/session-ledger.ts +0 -839
  143. package/src/ledger.ts +0 -44
@@ -110,6 +110,43 @@ export type ReviewCommentReaderImplementation = {
110
110
  ) => Promise<ExistingReviewComment[]>;
111
111
  };
112
112
 
113
+ export type ResolveReviewThreadsArgs = {
114
+ target: ScmReviewTarget;
115
+ /** REST comment ids whose threads should be resolved. */
116
+ commentIds: readonly number[];
117
+ signal?: AbortSignal;
118
+ };
119
+
120
+ /**
121
+ * Outcome of resolving review threads for a set of comment ids. Callers
122
+ * treat this as best-effort actuation: a failed resolve must not fail the
123
+ * review that already posted.
124
+ */
125
+ export type ResolveReviewThreadsResult = {
126
+ requested: number;
127
+ resolved: number;
128
+ alreadyResolved: number;
129
+ notFound: number;
130
+ failed: number;
131
+ detail: string;
132
+ };
133
+
134
+ /**
135
+ * Write side of thread resolution, the counterpart of
136
+ * {@link ReviewCommentReaderImplementation}. Bugbot's equivalent is
137
+ * `IGitProvider.resolveReviewThreadForComment` / `resolveThreads`.
138
+ *
139
+ * GitHub only exposes resolve via GraphQL (`resolveReviewThread`). REST
140
+ * has no equivalent. The mutation runs as whatever identity minted the
141
+ * bound GitHub client — for `githubChannel({ cursorAccount })` that is
142
+ * the Cursor installation, the same identity that posted the comments.
143
+ */
144
+ export type ReviewThreadResolverImplementation = {
145
+ resolveReviewThreadsForComments: (
146
+ args: ResolveReviewThreadsArgs
147
+ ) => Promise<ResolveReviewThreadsResult>;
148
+ };
149
+
113
150
  /** Parse a PR URL into the SCM target to read comments from. */
114
151
  export function resolveScmReviewTarget(
115
152
  prUrl: string
@@ -141,9 +178,65 @@ export function createReviewCommentReader(args: {
141
178
  }
142
179
  }
143
180
 
181
+ /**
182
+ * Resolver for a resolved target. Returns undefined when the host cannot
183
+ * serve that provider, so callers degrade instead of throwing.
184
+ */
185
+ export function createReviewThreadResolver(args: {
186
+ target: ScmReviewTarget;
187
+ github?: HostGitHubClient;
188
+ }): ReviewThreadResolverImplementation | undefined {
189
+ switch (args.target.provider) {
190
+ case "github":
191
+ return args.github === undefined
192
+ ? undefined
193
+ : createHostGithubReviewThreadResolver(args.github);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Resolve the review threads that own `commentIds` on `prUrl`.
199
+ *
200
+ * No-ops (never throws) when the PR is on an SCM this host cannot write,
201
+ * no client is bound, or `commentIds` is empty. Same degrade-don't-fail
202
+ * posture as {@link createReviewCommentReader}.
203
+ */
204
+ export async function resolveReviewThreadsForComments(args: {
205
+ prUrl: string;
206
+ commentIds: readonly number[];
207
+ github?: HostGitHubClient;
208
+ resolver?: ReviewThreadResolverImplementation;
209
+ signal?: AbortSignal;
210
+ }): Promise<ResolveReviewThreadsResult> {
211
+ if (args.commentIds.length === 0) {
212
+ return skippedResolve("no comments to resolve");
213
+ }
214
+ const target = resolveScmReviewTarget(args.prUrl);
215
+ if (target === undefined) {
216
+ return skippedResolve("no SCM resolver for this PR URL");
217
+ }
218
+ const resolver =
219
+ args.resolver ??
220
+ createReviewThreadResolver({
221
+ target,
222
+ github: args.github,
223
+ });
224
+ if (resolver === undefined) {
225
+ return skippedResolve("no GitHub client bound to resolve threads");
226
+ }
227
+ return resolver.resolveReviewThreadsForComments({
228
+ target,
229
+ commentIds: args.commentIds,
230
+ signal: args.signal,
231
+ });
232
+ }
233
+
144
234
  /** Cap on pages pulled per PR; GitHub serves 100 comments per page. */
145
235
  const REVIEW_COMMENTS_MAX_PAGES = 5;
146
236
  const REVIEW_COMMENTS_PAGE_SIZE = 100;
237
+ /** Cap on review-thread pages when mapping comment ids to GraphQL thread ids. */
238
+ const REVIEW_THREADS_MAX_PAGES = 20;
239
+ const REVIEW_THREADS_PAGE_SIZE = 100;
147
240
 
148
241
  /**
149
242
  * Reads inline review comments through the host GitHub client (Cursor
@@ -182,6 +275,188 @@ export function createHostGithubReviewCommentReader(
182
275
  };
183
276
  }
184
277
 
278
+ type GithubReviewThreadNode = {
279
+ id?: string;
280
+ isResolved?: boolean;
281
+ comments?: {
282
+ nodes?: Array<{ databaseId?: number | null } | null>;
283
+ };
284
+ };
285
+
286
+ type GithubReviewThreadsQuery = {
287
+ repository?: {
288
+ pullRequest?: {
289
+ reviewThreads?: {
290
+ nodes?: Array<GithubReviewThreadNode | null>;
291
+ pageInfo?: { hasNextPage?: boolean; endCursor?: string | null };
292
+ };
293
+ } | null;
294
+ } | null;
295
+ };
296
+
297
+ type GithubResolveThreadMutation = {
298
+ resolveReviewThread?: {
299
+ thread?: { isResolved?: boolean };
300
+ };
301
+ };
302
+
303
+ /**
304
+ * Resolves inline review threads through the host GitHub client (Cursor
305
+ * account lease, App installation token, or PAT). GitHub GraphQL has no
306
+ * comment→thread field, so this pages the PR's review threads and matches
307
+ * each REST comment id by `databaseId` — the same approach as Bugbot's
308
+ * `resolveReviewThreadForComment`.
309
+ */
310
+ export function createHostGithubReviewThreadResolver(
311
+ github: HostGitHubClient
312
+ ): ReviewThreadResolverImplementation {
313
+ return {
314
+ async resolveReviewThreadsForComments({ target, commentIds, signal }) {
315
+ const uniqueIds = [...new Set(commentIds.filter((id) => id > 0))];
316
+ if (uniqueIds.length === 0) {
317
+ return skippedResolve("no comments to resolve");
318
+ }
319
+ const [owner, repo] = target.repo.split("/", 2);
320
+ if (owner === undefined || repo === undefined || repo === "") {
321
+ return skippedResolve("unparseable owner/repo from PR URL");
322
+ }
323
+
324
+ try {
325
+ const octokit = await github.getOctokit();
326
+ const wanted = new Set(uniqueIds);
327
+ const matched = new Map<
328
+ number,
329
+ { threadId: string; isResolved: boolean }
330
+ >();
331
+
332
+ let cursor: string | null = null;
333
+ for (let page = 0; page < REVIEW_THREADS_MAX_PAGES; page++) {
334
+ signal?.throwIfAborted();
335
+ const response = (await octokit.graphql(REVIEW_THREADS_FOR_PR_QUERY, {
336
+ owner,
337
+ repo,
338
+ number: target.prNumber,
339
+ cursor,
340
+ })) as GithubReviewThreadsQuery;
341
+ const reviewThreads = response.repository?.pullRequest?.reviewThreads;
342
+ for (const thread of reviewThreads?.nodes ?? []) {
343
+ if (thread?.id === undefined || thread.id === "") {
344
+ continue;
345
+ }
346
+ for (const comment of thread.comments?.nodes ?? []) {
347
+ const databaseId = comment?.databaseId;
348
+ if (
349
+ databaseId !== undefined &&
350
+ databaseId !== null &&
351
+ wanted.has(databaseId) &&
352
+ !matched.has(databaseId)
353
+ ) {
354
+ matched.set(databaseId, {
355
+ threadId: thread.id,
356
+ isResolved: thread.isResolved === true,
357
+ });
358
+ }
359
+ }
360
+ }
361
+ if (matched.size === wanted.size) {
362
+ break;
363
+ }
364
+ const pageInfo = reviewThreads?.pageInfo;
365
+ if (pageInfo?.hasNextPage !== true || !pageInfo.endCursor) {
366
+ break;
367
+ }
368
+ cursor = pageInfo.endCursor;
369
+ }
370
+
371
+ const toResolve = [
372
+ ...new Map(
373
+ [...matched.values()]
374
+ .filter((thread) => !thread.isResolved)
375
+ .map((thread) => [thread.threadId, thread.threadId])
376
+ ).values(),
377
+ ];
378
+ let resolved = 0;
379
+ let failed = 0;
380
+ for (const threadId of toResolve) {
381
+ signal?.throwIfAborted();
382
+ try {
383
+ const mutation = (await octokit.graphql(
384
+ RESOLVE_REVIEW_THREAD_MUTATION,
385
+ { threadId }
386
+ )) as GithubResolveThreadMutation;
387
+ if (mutation.resolveReviewThread?.thread?.isResolved === true) {
388
+ resolved += 1;
389
+ } else {
390
+ failed += 1;
391
+ }
392
+ } catch {
393
+ failed += 1;
394
+ }
395
+ }
396
+
397
+ const alreadyResolved = new Set(
398
+ [...matched.values()]
399
+ .filter((thread) => thread.isResolved)
400
+ .map((thread) => thread.threadId)
401
+ ).size;
402
+ const notFound = wanted.size - matched.size;
403
+ return {
404
+ requested: uniqueIds.length,
405
+ resolved,
406
+ alreadyResolved,
407
+ notFound,
408
+ failed,
409
+ detail: `resolved ${resolved} of ${toResolve.length} open thread(s) for ${uniqueIds.length} comment(s) via host GitHub (${alreadyResolved} already resolved, ${notFound} not found, ${failed} failed)`,
410
+ };
411
+ } catch (error) {
412
+ if (signal?.aborted) {
413
+ throw error;
414
+ }
415
+ return {
416
+ requested: uniqueIds.length,
417
+ resolved: 0,
418
+ alreadyResolved: 0,
419
+ notFound: 0,
420
+ failed: uniqueIds.length,
421
+ detail: `host GitHub thread resolve failed: ${error instanceof Error ? error.message : String(error)}`,
422
+ };
423
+ }
424
+ },
425
+ };
426
+ }
427
+
428
+ const REVIEW_THREADS_FOR_PR_QUERY = `
429
+ query ReviewThreadsForPr(
430
+ $owner: String!
431
+ $repo: String!
432
+ $number: Int!
433
+ $cursor: String
434
+ ) {
435
+ repository(owner: $owner, name: $repo) {
436
+ pullRequest(number: $number) {
437
+ reviewThreads(first: ${REVIEW_THREADS_PAGE_SIZE}, after: $cursor) {
438
+ nodes {
439
+ id
440
+ isResolved
441
+ comments(first: ${REVIEW_THREADS_PAGE_SIZE}) {
442
+ nodes { databaseId }
443
+ }
444
+ }
445
+ pageInfo { hasNextPage endCursor }
446
+ }
447
+ }
448
+ }
449
+ }
450
+ `;
451
+
452
+ const RESOLVE_REVIEW_THREAD_MUTATION = `
453
+ mutation ResolveReviewThread($threadId: ID!) {
454
+ resolveReviewThread(input: { threadId: $threadId }) {
455
+ thread { isResolved }
456
+ }
457
+ }
458
+ `;
459
+
185
460
  type GithubReviewCommentLike = {
186
461
  id: number;
187
462
  path: string;
@@ -504,6 +779,17 @@ function skippedHost(detail: string): ReviewCommentPostResult {
504
779
  };
505
780
  }
506
781
 
782
+ function skippedResolve(detail: string): ResolveReviewThreadsResult {
783
+ return {
784
+ requested: 0,
785
+ resolved: 0,
786
+ alreadyResolved: 0,
787
+ notFound: 0,
788
+ failed: 0,
789
+ detail,
790
+ };
791
+ }
792
+
507
793
  function parseGithubPrUrl(
508
794
  prUrl: string
509
795
  ):
@@ -25,12 +25,6 @@ import {
25
25
  } from "../channels/slack/nudge-store.js";
26
26
  import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
27
27
  import { isCursorHostedFilesAvailable } from "../files-backends/cursor-hosted.js";
28
- import {
29
- SessionLedger,
30
- type SessionLedgerKv,
31
- supportsSessionLedgerKv,
32
- } from "../ledger/session-ledger.js";
33
- import { fileKv } from "../storage-backends/file-kv.js";
34
28
  import {
35
29
  type AgentProject,
36
30
  type ApprovalDecision,
@@ -48,7 +42,6 @@ import {
48
42
  DIRECT_TOOL_CALL_CHANNEL_ID,
49
43
  type HookHandler,
50
44
  type HostContext,
51
- type HostKvApi,
52
45
  type HttpChannelDefinition,
53
46
  isHostOauthConnectionTransport,
54
47
  isSymbolicConnectionTransport,
@@ -88,11 +81,6 @@ import {
88
81
  mapSdkTokenUsage,
89
82
  } from "./cloud-turn-cost.js";
90
83
  import { isDevMode } from "./dev-mode.js";
91
- import {
92
- ledgerKeyPrefix,
93
- namespacedKv,
94
- storageCasKv,
95
- } from "./durable-session-store.js";
96
84
  import { type EventLogMemoryStats, EventLogStore } from "./event-log.js";
97
85
  import { TurnEventMapper } from "./event-mapper.js";
98
86
  import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
@@ -111,7 +99,6 @@ import {
111
99
  newToolCallId,
112
100
  } from "./ids.js";
113
101
  import { validateToolInput } from "./json-schema.js";
114
- import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
115
102
  import { McpHost } from "./mcp-host.js";
116
103
  import type { OtelCollector } from "./otel/collector.js";
117
104
  import {
@@ -158,7 +145,7 @@ export class SessionBusyError extends Error {
158
145
  }
159
146
  }
160
147
 
161
- // Defined in auth.js (the ledger pump needs it without importing this
148
+ // Defined in auth.js (HTTP/session-store callers import it without this
162
149
  // module); re-exported here because this is its historical public home.
163
150
  export { SessionAuthError } from "./auth.js";
164
151
 
@@ -235,22 +222,6 @@ export interface SessionEngineOptions {
235
222
  * stub; production uses billed usage events when an agent id is known.
236
223
  */
237
224
  estimateTurnCost?: EstimateTurnCostFn;
238
- /**
239
- * Explicit durable-session store for `serving: "ledger"` agents — a KV
240
- * dedicated to the ledger (session identity, leases, commit heads),
241
- * separate from agent state. Must implement the full CAS-capable KV
242
- * surface (`HostKvCasApi`: the CAS trio plus `get`/`put`/`delete` for
243
- * mailbox payload blobs and `listKeys` for the janitor scan);
244
- * construction fails fast otherwise. Supply the **raw** store: the
245
- * engine owns key scoping and namespaces every ledger key under
246
- * `agentkit/v1/<agent>/durable-session/` itself — a self-hoster never
247
- * writes prefixes. Default when omitted: the authored storage backend
248
- * (`agent/storage.ts`) when it implements the `cas` group, else — with
249
- * no authored storage — a file store under `<stateRoot>/ledger-kv`
250
- * (single-process correctness only). An authored storage backend
251
- * *without* `cas` fails fast. Ignored for resident-mode agents.
252
- */
253
- durableSessionStore?: HostKvApi;
254
225
  /** Process-level OTel collector; observe-only, never fatal. */
255
226
  otel?: OtelCollector;
256
227
  /** Process-level meter for {@link HostContext.otel}. Absent → no-op counters. */
@@ -290,14 +261,6 @@ interface ActiveToolContext {
290
261
 
291
262
  export interface EngineSendOptions extends SendMessageOptions {
292
263
  mode?: "chat" | "task";
293
- /**
294
- * Internal (ledger serving): set only by the ledger pump when it
295
- * executes an already-parked event under its lease, so the call must
296
- * run the resident path. Without the flag, every send on a
297
- * `serving: "ledger"` agent routes through the ledger (append → claim
298
- * → drain → turn → commit) and the pump would recurse into itself.
299
- */
300
- fromLedgerPump?: boolean;
301
264
  }
302
265
 
303
266
  interface CoalescedQueuedTurn {
@@ -400,12 +363,6 @@ export class SessionEngine {
400
363
  private readonly cloudCostBackfillDone = new Set<string>();
401
364
  /** Deduped in-flight cloud cost backfills, keyed by session id. */
402
365
  private readonly cloudCostBackfill = new Map<string, Promise<void>>();
403
- /**
404
- * Ledger-mode delivery seam; set only when the agent declares
405
- * `serving: "ledger"`. When present, {@link send} routes every
406
- * non-delivery call through the ledger's claim → turn → commit.
407
- */
408
- private readonly ledgerServing: LedgerServing | undefined;
409
366
 
410
367
  constructor(options: SessionEngineOptions) {
411
368
  this.project = options.project;
@@ -499,8 +456,6 @@ export class SessionEngine {
499
456
  for (const channel of options.project.channels) {
500
457
  this.channelsById.set(channel.id, channel.definition);
501
458
  }
502
- this.ledgerServing = this.buildLedgerServing(options);
503
- this.ledgerServing?.start();
504
459
  this.stopMemoryTelemetry = shouldStartProcessMemoryTelemetry()
505
460
  ? startProcessMemoryTelemetry({
506
461
  agentName: options.project.name,
@@ -511,113 +466,6 @@ export class SessionEngine {
511
466
  : undefined;
512
467
  }
513
468
 
514
- /**
515
- * Ledger-mode wiring (`serving: "ledger"`): resolve a CAS-capable
516
- * durable-session store and build the event pump. Resident agents
517
- * (the default) return undefined and never touch ledger keys.
518
- */
519
- private buildLedgerServing(
520
- options: SessionEngineOptions
521
- ): LedgerServing | undefined {
522
- if (options.project.agent.serving !== "ledger") {
523
- return undefined;
524
- }
525
- // The handle is already agent-scoped (resolveDurableSessionStore):
526
- // two ledger-mode agents sharing one backend never resolve to each
527
- // other's sessions or janitor-claim each other's parked envelopes.
528
- const ledgerKv = this.resolveDurableSessionStore(options);
529
- return new LedgerServing({
530
- ledger: new SessionLedger({ kv: ledgerKv, podId: newLedgerPodId() }),
531
- deliverTurn: (envelope) =>
532
- this.send(envelope.channelId, envelope.message, {
533
- continuationToken: envelope.continuationToken,
534
- auth: envelope.auth,
535
- ...(envelope.title === undefined ? {} : { title: envelope.title }),
536
- ...(envelope.state === undefined ? {} : { state: envelope.state }),
537
- ...(envelope.mode === undefined ? {} : { mode: envelope.mode }),
538
- ...(envelope.purpose === undefined
539
- ? {}
540
- : { purpose: envelope.purpose }),
541
- // Isolation overrides ride the envelope: without them the
542
- // drained turn would run local with the default harness cwd
543
- // instead of the cloud VM / channel worktree the sender bound.
544
- ...(envelope.workspaceDir === undefined
545
- ? {}
546
- : { workspaceDir: envelope.workspaceDir }),
547
- ...(envelope.cloud === undefined ? {} : { cloud: envelope.cloud }),
548
- fromLedgerPump: true,
549
- }),
550
- findSession: async (channelId, continuationToken) => {
551
- let record = await this.sessions.findByContinuation(
552
- channelId,
553
- continuationToken
554
- );
555
- if (record === undefined) {
556
- record = await this.restoreSessionByContinuation(
557
- channelId,
558
- continuationToken
559
- );
560
- }
561
- return record === undefined
562
- ? undefined
563
- : this.channelSession(record.sessionId, record, false);
564
- },
565
- logger: this.logger,
566
- });
567
- }
568
-
569
- /**
570
- * Pick the KV the ledger coordinates on, in precedence order:
571
- *
572
- * 1. `options.durableSessionStore` — an explicit store separate from
573
- * agent state (advanced deployments, tests).
574
- * 2. The authored storage backend (`agent/storage.ts`) when it
575
- * implements the `cas` group — one backend serves both the storage
576
- * API and the durable session API.
577
- * 3. No authored storage: a file store under `<stateRoot>/ledger-kv`
578
- * (same place resident state lives; single-process only).
579
- *
580
- * An authored backend without `cas` fails fast: silently falling back
581
- * to local files would fork coordination away from the deployment's
582
- * durable store. The check demands the full surface the inbox uses —
583
- * the CAS trio plus `get`/`put`/`delete` for mailbox payload blobs and
584
- * `listKeys` for the janitor scan and the orphan sweep.
585
- *
586
- * Whatever store wins, the returned handle is scoped here — once —
587
- * under `agentkit/v1/<agent>/durable-session/`. The ledger speaks bare
588
- * `session/…` / `ledger/…` / `mailbox/…` keys; namespacing is this
589
- * boundary's job, exactly like `host.kv`'s agent scoping.
590
- */
591
- private resolveDurableSessionStore(
592
- options: SessionEngineOptions
593
- ): SessionLedgerKv {
594
- const namespace = ledgerKeyPrefix(options.project.name);
595
- const explicit = options.durableSessionStore;
596
- if (explicit !== undefined) {
597
- if (!supportsSessionLedgerKv(explicit)) {
598
- throw new Error(
599
- `Agent "${options.project.name}" declares serving: "ledger", but the configured durableSessionStore is not CAS-capable (getWithVersion / putIfAbsent / putIfVersion / listKeys). Provide a store implementing HostKvCasApi (see @cursor/july/kv).`
600
- );
601
- }
602
- return namespacedKv(explicit, namespace);
603
- }
604
- const casArgs = {
605
- agentName: options.project.name,
606
- projectRoot: options.project.rootDir,
607
- };
608
- const authored = options.project.storage;
609
- if (authored !== undefined) {
610
- if (authored.cas === undefined) {
611
- throw new Error(
612
- `Agent "${options.project.name}" declares serving: "ledger", which needs conditional writes, but the storage backend in agent/storage.ts does not implement the cas group. Add cas: {...} to defineStorage (fileKv and cursorHostedStorage already include it), or pass an explicit durableSessionStore.`
613
- );
614
- }
615
- return namespacedKv(storageCasKv(authored, casArgs), namespace);
616
- }
617
- const fallback = fileKv({ dir: join(options.stateRoot, "ledger-kv") });
618
- return namespacedKv(storageCasKv(fallback, casArgs), namespace);
619
- }
620
-
621
469
  /**
622
470
  * Attach serve-time resolved connections (peers, Cursor account bridge).
623
471
  * They join the per-turn SDK `mcpServers` map (loopback URL for local
@@ -892,12 +740,6 @@ export class SessionEngine {
892
740
  message: string,
893
741
  options: EngineSendOptions = {}
894
742
  ): Promise<ChannelSession> {
895
- // Ledger-mode agents park the event durably and coordinate through
896
- // the session lease before any turn runs; only the pump's own
897
- // lease-holding deliveries fall through to the resident path.
898
- if (this.ledgerServing !== undefined && options.fromLedgerPump !== true) {
899
- return this.ledgerServing.deliver(channelId, message, options);
900
- }
901
743
  const auth = options.auth ?? null;
902
744
  let existing =
903
745
  options.continuationToken === undefined
@@ -1008,13 +850,6 @@ export class SessionEngine {
1008
850
  };
1009
851
  await this.sessions.save(record);
1010
852
  this.storage?.sessionRecord(record);
1011
- // Ledger serving: another pod can follow up mid-turn, so the session
1012
- // identity (record + continuation index) must hit the shared store
1013
- // before we return a session id. Default turnEnd flush is too late.
1014
- if (this.ledgerServing !== undefined && this.storage !== undefined) {
1015
- this.storage.flushSession(record.sessionId);
1016
- await this.storage.whenIdle();
1017
- }
1018
853
  await this.appendEvent(record.sessionId, {
1019
854
  type: "session.started",
1020
855
  data: { channelId },
@@ -3841,8 +3676,6 @@ export class SessionEngine {
3841
3676
  async close(): Promise<void> {
3842
3677
  this.stopMemoryTelemetry?.();
3843
3678
  this.approvals.cancelAll("deny");
3844
- // Ledger deliveries spawn turns, so settle them before activeTurns.
3845
- await this.ledgerServing?.close();
3846
3679
  await Promise.allSettled([...this.activeTurns.values()]);
3847
3680
  await Promise.allSettled([...this.backgroundWork]);
3848
3681
  await Promise.allSettled([...this.dispatchChains.values()]);
@@ -4,8 +4,7 @@
4
4
  * `@cursor/july/kv/memory`.
5
5
  *
6
6
  * The memory KV *is* the dev shim: tests and local runs exercise the
7
- * same CAS coordination paths that hosted serving uses, not a mock of an
8
- * RPC. Values are deep-copied on both write and
7
+ * CAS contract, not a mock of an RPC. Values are deep-copied on both write and
9
8
  * read so callers can never observe (or cause) shared-reference mutation.
10
9
  */
11
10
 
@@ -86,14 +85,6 @@ export function createMemoryKv(): HostKvCasApi {
86
85
  * fixtures, an off-hosting no-op sink). Non-durable and process-local by
87
86
  * construction; a real deployment backend should implement CAS against
88
87
  * its own store instead.
89
- *
90
- * **Never pair this with `serving: "ledger"` on a real backend.** The
91
- * ledger derives its coordination store from the whole storage config,
92
- * so a durable plain backend with a memory `cas` group silently voids
93
- * ack-after-durable: leases, heads, and pending indexes live only in
94
- * this process and vanish on restart while payload blobs go through the
95
- * plain methods. Ledger state must live in the same durable store as
96
- * the blobs.
97
88
  */
98
89
  export function memoryCasTable(): StorageCasTable {
99
90
  const kv = createMemoryKv();
package/src/kv.ts CHANGED
@@ -2,9 +2,8 @@
2
2
  * The CAS-capable KV **interface** — `@cursor/july/kv`.
3
3
  *
4
4
  * This module is implementation-free on purpose: it is the contract a
5
- * bring-your-own KV implements so coordination protocols (conditional
6
- * writes are the substrate for the upcoming session ledger) can run on
7
- * it. {@link HostKvCasApi} is the conditional-write superset of the plain
5
+ * bring-your-own KV implements so conditional writes can run on it.
6
+ * {@link HostKvCasApi} is the conditional-write superset of the plain
8
7
  * {@link HostKvApi}; {@link supportsKvCas} is the runtime probe for
9
8
  * stores of unknown provenance — a `defineStorage` backend declares CAS
10
9
  * through its `cas` group instead, so it never needs probing.
@@ -313,9 +313,9 @@ export function cursorHostedStorage(
313
313
  { prefix }
314
314
  );
315
315
  if (result.truncated === true) {
316
- // Callers (the ledger janitor above all) treat the result as
317
- // the complete key set; a silently capped scan means silently
318
- // unrecovered sessions. Fail loudly until pagination lands.
316
+ // Callers treat the result as the complete key set; a silently
317
+ // capped scan hides missing keys. Fail loudly until pagination
318
+ // lands.
319
319
  throw new Error(
320
320
  `cursorHostedStorage: listKeys("${prefix}") exceeded the server's row cap and was truncated; the result is incomplete`
321
321
  );
@@ -4,9 +4,7 @@
4
4
  * `backend/server/src/agentServe/durableSessionProtocol.ts`) so a dashboard
5
5
  * list can read Bugbot KV without importing `@cursor/july`.
6
6
  *
7
- * Additive JSON only — unknown fields are ignored. Do not put ledger
8
- * (`durable-session/`) types here; those are coordination-only and must not
9
- * appear in the CP session list.
7
+ * Additive JSON only — unknown fields are ignored.
10
8
  */
11
9
 
12
10
  import { createHash } from "node:crypto";
@@ -19,8 +17,7 @@ export const STORAGE_KEY_ROOT = "agentkit/v1" as const;
19
17
 
20
18
  /**
21
19
  * Exact session-record keys: `agentkit/v1/{agent}/session/{sessionId}`.
22
- * Rejects `session-events/`, `durable-session/`, and the CP-owned `_cp`
23
- * agent name.
20
+ * Rejects `session-events/` and the CP-owned `_cp` agent name.
24
21
  */
25
22
  const DURABLE_SESSION_RECORD_KEY_RE =
26
23
  /^agentkit\/v1\/([^/]+)\/session\/([^/]+)$/;
@@ -109,9 +106,6 @@ export const MAX_KEY_SEGMENT_BYTES = 256;
109
106
  * digest of the raw value when the encoding exceeds
110
107
  * {@link MAX_KEY_SEGMENT_BYTES}. Deterministic, so key construction on the
111
108
  * write path and the continuation-lookup path always agree.
112
- *
113
- * Exported for the session ledger, which builds its `session/` and
114
- * `ledger/` keys with the same encoding rules.
115
109
  */
116
110
  export function keySegment(raw: string): string {
117
111
  const encoded = encodeURIComponent(raw);
@@ -163,8 +157,8 @@ function stringValue(value: unknown): string | undefined {
163
157
  }
164
158
 
165
159
  /**
166
- * Tolerant `SessionRecord` decoder. Extra fields (`auth`, `purpose`, ledger
167
- * extras, future keys) are ignored. Returns undefined when required list
160
+ * Tolerant `SessionRecord` decoder. Extra fields (`auth`, `purpose`,
161
+ * future keys) are ignored. Returns undefined when required list
168
162
  * fields are missing or the wrong type.
169
163
  */
170
164
  export function parseDurableSessionRecord(