@cursor/july 0.1.76 → 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 (128) 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/discovery.d.ts.map +1 -1
  70. package/dist/internal/discovery.js +3 -21
  71. package/dist/internal/distribution.d.ts.map +1 -1
  72. package/dist/internal/distribution.js +0 -1
  73. package/dist/internal/session-engine.d.ts +1 -60
  74. package/dist/internal/session-engine.d.ts.map +1 -1
  75. package/dist/internal/session-engine.js +10 -109
  76. package/dist/kv/memory-kv.d.ts +1 -10
  77. package/dist/kv/memory-kv.d.ts.map +1 -1
  78. package/dist/kv/memory-kv.js +1 -10
  79. package/dist/kv.d.ts +2 -3
  80. package/dist/kv.d.ts.map +1 -1
  81. package/dist/kv.js +2 -3
  82. package/dist/playground/assets/{index-Bfwz2yLN.css → index-DJzcIeGt.css} +1 -1
  83. package/dist/playground/assets/{index-BWqjJcRo.js → index-K3Sxyx0I.js} +48 -45
  84. package/dist/playground/index.html +2 -2
  85. package/dist/storage-backends/cursor-hosted.js +3 -3
  86. package/dist/storage-protocol.d.ts +3 -8
  87. package/dist/storage-protocol.d.ts.map +1 -1
  88. package/dist/storage-protocol.js +4 -10
  89. package/dist/storage.d.ts +5 -11
  90. package/dist/storage.d.ts.map +1 -1
  91. package/dist/storage.js +3 -4
  92. package/dist/types.d.ts +5 -34
  93. package/dist/types.d.ts.map +1 -1
  94. package/docs/storage.md +11 -44
  95. package/package.json +1 -8
  96. package/src/channels/origin/origin-webhook.ts +111 -25
  97. package/src/index.ts +0 -6
  98. package/src/internal/discovery.ts +3 -27
  99. package/src/internal/distribution.ts +0 -1
  100. package/src/internal/session-engine.ts +1 -168
  101. package/src/kv/memory-kv.ts +1 -10
  102. package/src/kv.ts +2 -3
  103. package/src/storage-backends/cursor-hosted.ts +3 -3
  104. package/src/storage-protocol.ts +4 -10
  105. package/src/storage.ts +6 -12
  106. package/src/types.ts +5 -35
  107. package/dist/docs/assets/chunks/@localSearchIndexroot.DGZeXVqn.js +0 -1
  108. package/dist/docs/assets/storage.md.D8NQ389l.lean.js +0 -1
  109. package/dist/internal/durable-session-store.d.ts +0 -60
  110. package/dist/internal/durable-session-store.d.ts.map +0 -1
  111. package/dist/internal/durable-session-store.js +0 -131
  112. package/dist/internal/ledger-serving.d.ts +0 -169
  113. package/dist/internal/ledger-serving.d.ts.map +0 -1
  114. package/dist/internal/ledger-serving.js +0 -393
  115. package/dist/ledger/janitor.d.ts +0 -60
  116. package/dist/ledger/janitor.d.ts.map +0 -1
  117. package/dist/ledger/janitor.js +0 -137
  118. package/dist/ledger/session-ledger.d.ts +0 -293
  119. package/dist/ledger/session-ledger.d.ts.map +0 -1
  120. package/dist/ledger/session-ledger.js +0 -614
  121. package/dist/ledger.d.ts +0 -12
  122. package/dist/ledger.d.ts.map +0 -1
  123. package/dist/ledger.js +0 -9
  124. package/src/internal/durable-session-store.ts +0 -134
  125. package/src/internal/ledger-serving.ts +0 -531
  126. package/src/ledger/janitor.ts +0 -193
  127. package/src/ledger/session-ledger.ts +0 -839
  128. package/src/ledger.ts +0 -44
@@ -32,8 +32,6 @@ const V1ED_TIMESTAMP_TOLERANCE_SECONDS = 5 * 60;
32
32
  */
33
33
  const MAX_V1ED_CANDIDATE_SIGNATURES = 1;
34
34
 
35
- const JWKS_CACHE_TTL_MS = 5 * 60 * 1000;
36
-
37
35
  export type OriginJwk = Record<string, unknown>;
38
36
 
39
37
  export type OriginWebhookHeaders = {
@@ -131,41 +129,115 @@ export function verifyV1edWebhookPayload(args: {
131
129
  return false;
132
130
  }
133
131
 
132
+ function cacheControlDeltaMs(
133
+ cacheControl: string | null | undefined,
134
+ directive: "max-age" | "stale-if-error"
135
+ ): number | undefined {
136
+ if (cacheControl == null || cacheControl === "") {
137
+ return undefined;
138
+ }
139
+ const match = new RegExp(`(?:^|,)\\s*${directive}\\s*=\\s*(\\d+)`, "i").exec(
140
+ cacheControl
141
+ );
142
+ if (match === null) {
143
+ return undefined;
144
+ }
145
+ return Number(match[1]) * 1000;
146
+ }
147
+
148
+ /**
149
+ * RFC 9111 `max-age` (seconds → ms) from a Cache-Control header.
150
+ * Does not read `s-maxage`. Missing or unparsable → `undefined`.
151
+ */
152
+ export function cacheControlMaxAgeMs(
153
+ cacheControl: string | null | undefined
154
+ ): number | undefined {
155
+ return cacheControlDeltaMs(cacheControl, "max-age");
156
+ }
157
+
158
+ /**
159
+ * RFC 5861 `stale-if-error` (seconds → ms). Missing → `undefined` (no
160
+ * fail-open after `max-age`).
161
+ */
162
+ export function cacheControlStaleIfErrorMs(
163
+ cacheControl: string | null | undefined
164
+ ): number | undefined {
165
+ return cacheControlDeltaMs(cacheControl, "stale-if-error");
166
+ }
167
+
134
168
  let jwksCache:
135
- | { apiBaseUrl: string; keys: OriginJwk[]; fetchedAtMs: number }
169
+ | {
170
+ apiBaseUrl: string;
171
+ keys: OriginJwk[];
172
+ freshUntilMs: number;
173
+ staleUntilMs: number;
174
+ }
136
175
  | undefined;
137
176
 
138
- /** Fetch (and cache for 5 minutes) the Origin signing JWKS. */
177
+ export type FetchOriginJwksOptions = {
178
+ /** Skip the in-memory hit and call `/v1/origin/keys` (used after a verify miss). */
179
+ force?: boolean;
180
+ };
181
+
182
+ /**
183
+ * Return the in-memory Origin signing JWKS.
184
+ *
185
+ * Freshness is the JWKS response `Cache-Control: max-age`. After that, or
186
+ * when `force` is set, refetch. A failed refetch returns the last keys only
187
+ * while `stale-if-error` still applies; otherwise it throws (including a
188
+ * cold-cache miss).
189
+ */
139
190
  export async function fetchOriginJwks(
140
191
  apiBaseUrl: string,
141
- fetchImpl: typeof fetch = fetch
192
+ fetchImpl: typeof fetch = fetch,
193
+ options?: FetchOriginJwksOptions
142
194
  ): Promise<readonly OriginJwk[]> {
143
195
  const now = Date.now();
144
196
  if (
197
+ options?.force !== true &&
145
198
  jwksCache !== undefined &&
146
199
  jwksCache.apiBaseUrl === apiBaseUrl &&
147
- now - jwksCache.fetchedAtMs < JWKS_CACHE_TTL_MS
200
+ now < jwksCache.freshUntilMs
148
201
  ) {
149
202
  return jwksCache.keys;
150
203
  }
151
- const response = await fetchImpl(
152
- `${apiBaseUrl.replace(/\/+$/, "")}/v1/origin/keys`
153
- );
154
- if (!response.ok) {
155
- throw new Error(`Origin JWKS fetch failed: status=${response.status}`);
156
- }
157
- const body: unknown = await response.json().catch(() => undefined);
158
- const keys =
159
- typeof body === "object" &&
160
- body !== null &&
161
- Array.isArray((body as Record<string, unknown>).keys)
162
- ? ((body as Record<string, unknown>).keys as OriginJwk[])
163
- : [];
164
- if (keys.length === 0) {
165
- throw new Error("Origin JWKS fetch returned no keys");
204
+ try {
205
+ const response = await fetchImpl(
206
+ `${apiBaseUrl.replace(/\/+$/, "")}/v1/origin/keys`
207
+ );
208
+ if (!response.ok) {
209
+ throw new Error(`Origin JWKS fetch failed: status=${response.status}`);
210
+ }
211
+ const body: unknown = await response.json().catch(() => undefined);
212
+ const keys =
213
+ typeof body === "object" &&
214
+ body !== null &&
215
+ Array.isArray((body as Record<string, unknown>).keys)
216
+ ? ((body as Record<string, unknown>).keys as OriginJwk[])
217
+ : [];
218
+ if (keys.length === 0) {
219
+ throw new Error("Origin JWKS fetch returned no keys");
220
+ }
221
+ const cacheControl = response.headers.get("cache-control");
222
+ const maxAgeMs = cacheControlMaxAgeMs(cacheControl) ?? 0;
223
+ const staleIfErrorMs = cacheControlStaleIfErrorMs(cacheControl) ?? 0;
224
+ jwksCache = {
225
+ apiBaseUrl,
226
+ keys,
227
+ freshUntilMs: now + maxAgeMs,
228
+ staleUntilMs: now + maxAgeMs + staleIfErrorMs,
229
+ };
230
+ return keys;
231
+ } catch (error) {
232
+ if (
233
+ jwksCache !== undefined &&
234
+ jwksCache.apiBaseUrl === apiBaseUrl &&
235
+ now < jwksCache.staleUntilMs
236
+ ) {
237
+ return jwksCache.keys;
238
+ }
239
+ throw error;
166
240
  }
167
- jwksCache = { apiBaseUrl, keys, fetchedAtMs: now };
168
- return keys;
169
241
  }
170
242
 
171
243
  export interface VerifyOriginWebhookOptions {
@@ -183,14 +255,28 @@ export async function verifyOriginWebhook(
183
255
  options?: VerifyOriginWebhookOptions
184
256
  ): Promise<void> {
185
257
  const apiBaseUrl = options?.apiBaseUrl ?? DEFAULT_ORIGIN_API_BASE_URL;
186
- const keys = await fetchOriginJwks(apiBaseUrl, options?.fetchImpl ?? fetch);
258
+ const fetchImpl = options?.fetchImpl ?? fetch;
259
+ const keys = await fetchOriginJwks(apiBaseUrl, fetchImpl);
187
260
  if (
188
- !verifyV1edWebhookPayload({
261
+ verifyV1edWebhookPayload({
189
262
  publicJwks: keys,
190
263
  body,
191
264
  headers,
192
265
  nowMs: options?.nowMs,
193
266
  })
267
+ ) {
268
+ return;
269
+ }
270
+ const refreshed = await fetchOriginJwks(apiBaseUrl, fetchImpl, {
271
+ force: true,
272
+ });
273
+ if (
274
+ !verifyV1edWebhookPayload({
275
+ publicJwks: refreshed,
276
+ body,
277
+ headers,
278
+ nowMs: options?.nowMs,
279
+ })
194
280
  ) {
195
281
  throw new Error("Origin webhook signature verification failed");
196
282
  }
package/src/index.ts CHANGED
@@ -96,12 +96,6 @@ export {
96
96
  UnknownToolError,
97
97
  } from "./internal/session-engine.js";
98
98
  export { createMemoryKv, memoryCasTable } from "./kv/memory-kv.js";
99
- export {
100
- LedgerCasExhaustedError,
101
- LedgerFencedError,
102
- SessionLedger,
103
- supportsSessionLedgerKv,
104
- } from "./ledger.js";
105
99
  export type { MemoryBackend, TurnMemoryRecord } from "./memory.js";
106
100
  export {
107
101
  agentStoreMemoryBackend,
@@ -29,7 +29,6 @@ import {
29
29
  type AgentProject,
30
30
  type AgentProjectInfo,
31
31
  type AgentRuntime,
32
- type AgentServingMode,
33
32
  type ArtifactsDefinition,
34
33
  type BuiltinToolsConfig,
35
34
  type ChannelDefinition,
@@ -194,9 +193,9 @@ export async function loadAgentProjects(
194
193
  );
195
194
  }
196
195
  const results: Array<{ slug: string; project: AgentProject }> = [];
197
- // Storage namespaces (session mirror, host kv, durable-session ledger)
198
- // key on the project NAME, not the mount slug — two co-hosted agents
199
- // sharing a name would silently share sessions and coordination state.
196
+ // Storage namespaces (session mirror, host kv) key on the project
197
+ // NAME, not the mount slug — two co-hosted agents sharing a name would
198
+ // silently share sessions and KV state.
200
199
  const slugByName = new Map<string, string>();
201
200
  for (const child of children) {
202
201
  const project = await loadAgentProject(child.dir, options);
@@ -506,7 +505,6 @@ async function loadAgent(
506
505
  };
507
506
  const hosting = resolveHosting(ctx, agentDir, config?.hosting);
508
507
  const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
509
- const serving = resolveServing(ctx, agentDir, config?.serving);
510
508
 
511
509
  if (runtime === "cloud") {
512
510
  warnCloudRuntimeLimitations(ctx, agentDir, {
@@ -531,7 +529,6 @@ async function loadAgent(
531
529
  ...(allowedTools === undefined ? {} : { allowedTools }),
532
530
  hosting,
533
531
  concurrency,
534
- serving,
535
532
  tools,
536
533
  skills,
537
534
  connections,
@@ -540,27 +537,6 @@ async function loadAgent(
540
537
  };
541
538
  }
542
539
 
543
- /**
544
- * Validate the authored serving mode. An unrecognized value is an error
545
- * diagnostic and falls back to undefined (resident) — a typo must not
546
- * silently opt an agent into (or out of) ledger coordination.
547
- */
548
- function resolveServing(
549
- ctx: DiscoveryContext,
550
- agentDir: string,
551
- serving: AgentServingMode | undefined
552
- ): AgentServingMode | undefined {
553
- if (serving === undefined || serving === "resident" || serving === "ledger") {
554
- return serving;
555
- }
556
- ctx.diagnostics.push({
557
- severity: "error",
558
- path: displayPath(ctx, join(agentDir, "agent.ts")),
559
- message: `serving must be "resident" or "ledger"; got ${JSON.stringify(serving)}.`,
560
- });
561
- return undefined;
562
- }
563
-
564
540
  /** Absolute harness cwd parent, or undefined when unset / blank. */
565
541
  function resolveAgentCwd(
566
542
  ctx: DiscoveryContext,
@@ -112,7 +112,6 @@ export const AUTHORING_ENTRY_FILES: ReadonlyArray<
112
112
  ["artifacts", "artifacts.js"],
113
113
  ["kv", "kv.js"],
114
114
  ["kv/memory", "kv/memory-kv.js"],
115
- ["ledger", "ledger.js"],
116
115
  ["storage", "storage.js"],
117
116
  ["storage/file-kv", "storage-backends/file-kv.js"],
118
117
  ["storage/cursor-hosted", "storage-backends/cursor-hosted.js"],
@@ -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(
package/src/storage.ts CHANGED
@@ -7,9 +7,8 @@
7
7
  * only, and eval/A/B history stays in process memory.
8
8
  *
9
9
  * The sink is a plain key-value store — put/get/delete/list, no schema —
10
- * plus a conditional-write group ({@link StorageConfig.cas}, the substrate
11
- * for framework coordination; optional for plain storage, required by
12
- * coordination features). Implementing that small KV core is enough for
10
+ * plus a conditional-write group ({@link StorageConfig.cas}; optional).
11
+ * Implementing that small KV core is enough for
13
12
  * FULL functionality: eval-run and A/B history are derived over the core
14
13
  * when the optional dedicated tables ({@link StorageConfig.evals} and
15
14
  * {@link StorageConfig.abs}) are not authored — those groups exist as
@@ -178,10 +177,7 @@ export interface StorageABTable {
178
177
  /**
179
178
  * Conditional writes (CAS) — the coordination tier of the storage backend
180
179
  * contract. Plain storage works without it, so existing backends keep
181
- * working across a platform upgrade; framework coordination (the upcoming
182
- * session ledger — see `@cursor/july/kv` for the backend-agnostic
183
- * interface) requires it and fails at startup, with an actionable
184
- * message, when the backend lacks it. The built-in backends all include
180
+ * working across a platform upgrade. The built-in backends all include
185
181
  * it, so implementing this group yourself only comes up for BYO storage.
186
182
  *
187
183
  * Grouped under {@link StorageConfig.cas} rather than as loose methods —
@@ -243,10 +239,8 @@ export interface StorageConfig {
243
239
  abs?: StorageABTable;
244
240
  /**
245
241
  * Conditional writes (CAS). Optional for plain storage — existing
246
- * backends keep working without it but required by coordination
247
- * features (the session ledger), which fail fast when it is absent.
248
- * The built-in backends (`fileKv`, `cursorHostedStorage`) both include
249
- * it. See {@link StorageCasTable}.
242
+ * backends keep working without it. The built-in backends (`fileKv`,
243
+ * `cursorHostedStorage`) both include it. See {@link StorageCasTable}.
250
244
  */
251
245
  cas?: StorageCasTable;
252
246
  /**
@@ -321,7 +315,7 @@ export function defineStorage(config: StorageConfig): StorageDefinition {
321
315
  ] as const;
322
316
  const cas = config.cas;
323
317
  // A partial group is a bug, not a legacy config: half-implemented CAS
324
- // would pass the capability probe and corrupt coordination.
318
+ // would pass the capability probe.
325
319
  const missingCasHooks = casHooks.filter(
326
320
  (hook) => typeof cas[hook] !== "function"
327
321
  );