@cursor/july 0.1.43 → 0.1.46

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 (197) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +3 -1
  3. package/dist/channels/github/api.d.ts +25 -0
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +48 -0
  6. package/dist/channels/github/cursor-account.d.ts +7 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/github/cursor-account.js +18 -0
  9. package/dist/channels/github/defaults.d.ts +1 -1
  10. package/dist/channels/github/defaults.js +1 -1
  11. package/dist/channels/github/github-channel.d.ts.map +1 -1
  12. package/dist/channels/github/github-channel.js +15 -7
  13. package/dist/channels/github/progress.d.ts +6 -5
  14. package/dist/channels/github/progress.d.ts.map +1 -1
  15. package/dist/channels/github/progress.js +58 -16
  16. package/dist/channels/github/state.d.ts +11 -0
  17. package/dist/channels/github/state.d.ts.map +1 -1
  18. package/dist/channels/github/state.js +1 -1
  19. package/dist/channels/github/types.d.ts +11 -3
  20. package/dist/channels/github/types.d.ts.map +1 -1
  21. package/dist/channels.d.ts +17 -0
  22. package/dist/channels.d.ts.map +1 -1
  23. package/dist/channels.js +31 -0
  24. package/dist/docs/404.html +2 -2
  25. package/dist/docs/ab.html +3 -3
  26. package/dist/docs/assets/{app.DsTLo2j1.js → app.C2NFTb-h.js} +1 -1
  27. package/dist/docs/assets/chunks/@localSearchIndexroot.B-FxvuoI.js +1 -0
  28. package/dist/docs/assets/chunks/{VPLocalSearchBox.DoVfutcV.js → VPLocalSearchBox.o1_FJGip.js} +1 -1
  29. package/dist/docs/assets/chunks/{theme.DtuVtKkc.js → theme.BcGduE7g.js} +2 -2
  30. package/dist/docs/assets/{guides_github.md.DSg0j9qj.js → guides_github.md.B8QKtIpw.js} +7 -6
  31. package/dist/docs/assets/{guides_github.md.DSg0j9qj.lean.js → guides_github.md.B8QKtIpw.lean.js} +1 -1
  32. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.js +27 -0
  33. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.lean.js +1 -0
  34. package/dist/docs/assets/index.md.COeoSkcF.js +5 -0
  35. package/dist/docs/assets/index.md.COeoSkcF.lean.js +1 -0
  36. package/dist/docs/assets/{quickstart.md.rIbfahJM.js → quickstart.md.DZxBu44y.js} +18 -49
  37. package/dist/docs/assets/quickstart.md.DZxBu44y.lean.js +1 -0
  38. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.js → reference_agent-config.md.DHkkvcEZ.js} +9 -4
  39. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.lean.js → reference_agent-config.md.DHkkvcEZ.lean.js} +1 -1
  40. package/dist/docs/assets/{reference_cli.md.DqEqQpGn.js → reference_cli.md.BDmjPPAm.js} +2 -2
  41. package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.js → reference_hooks.md.DJE5DXcT.js} +1 -1
  42. package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.js → reference_tools.md.kJfJAP2m.js} +1 -1
  43. package/dist/docs/assets/{storage.md.DcGSnIxX.js → storage.md.CJYd5YmN.js} +2 -2
  44. package/dist/docs/assets/{storage.md.DcGSnIxX.lean.js → storage.md.CJYd5YmN.lean.js} +1 -1
  45. package/dist/docs/building-with-agents.html +3 -3
  46. package/dist/docs/concepts.html +3 -3
  47. package/dist/docs/deployment.html +3 -3
  48. package/dist/docs/evals.html +3 -3
  49. package/dist/docs/example-agents/approval-buddy.html +3 -3
  50. package/dist/docs/example-agents/benny.html +3 -3
  51. package/dist/docs/example-agents/bugbot.html +3 -3
  52. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  53. package/dist/docs/example-agents/codeowners-review.html +3 -3
  54. package/dist/docs/example-agents/concierge.html +3 -3
  55. package/dist/docs/example-agents/fsd.html +3 -3
  56. package/dist/docs/example-agents/index.html +3 -3
  57. package/dist/docs/example-agents/knowledge-base.html +3 -3
  58. package/dist/docs/example-agents/oncall.html +3 -3
  59. package/dist/docs/example-agents/security-reviewer.html +3 -3
  60. package/dist/docs/example-agents/slack-agent.html +3 -3
  61. package/dist/docs/example-agents/weather-agent.html +3 -3
  62. package/dist/docs/guides/agent-to-agent.html +3 -3
  63. package/dist/docs/guides/cloud-runtime.html +3 -3
  64. package/dist/docs/guides/github.html +10 -9
  65. package/dist/docs/guides/human-in-the-loop.html +3 -3
  66. package/dist/docs/guides/mcp-oauth.html +3 -3
  67. package/dist/docs/guides/opentelemetry.html +12 -5
  68. package/dist/docs/guides/slack.html +3 -3
  69. package/dist/docs/guides/webhooks.html +3 -3
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +3 -3
  72. package/dist/docs/index.html +8 -28
  73. package/dist/docs/quickstart.html +24 -55
  74. package/dist/docs/reference/agent-config.html +11 -6
  75. package/dist/docs/reference/artifacts.html +3 -3
  76. package/dist/docs/reference/channels.html +3 -3
  77. package/dist/docs/reference/cli.html +6 -6
  78. package/dist/docs/reference/connections.html +3 -3
  79. package/dist/docs/reference/hooks.html +5 -5
  80. package/dist/docs/reference/http-api.html +3 -3
  81. package/dist/docs/reference/instructions.html +3 -3
  82. package/dist/docs/reference/playground.html +3 -3
  83. package/dist/docs/reference/project-layout.html +3 -3
  84. package/dist/docs/reference/prompt.html +3 -3
  85. package/dist/docs/reference/schedules.html +3 -3
  86. package/dist/docs/reference/sessions.html +3 -3
  87. package/dist/docs/reference/skills.html +3 -3
  88. package/dist/docs/reference/subagents.html +3 -3
  89. package/dist/docs/reference/tools.html +5 -5
  90. package/dist/docs/scaffolding-agents.html +3 -3
  91. package/dist/docs/storage.html +5 -5
  92. package/dist/docs/templates/triage.html +3 -3
  93. package/dist/docs/troubleshooting.html +3 -3
  94. package/dist/index.d.ts +2 -2
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +2 -2
  97. package/dist/internal/auth.d.ts +4 -0
  98. package/dist/internal/auth.d.ts.map +1 -1
  99. package/dist/internal/auth.js +7 -0
  100. package/dist/internal/cli-ax.js +2 -2
  101. package/dist/internal/cli-deploy.d.ts.map +1 -1
  102. package/dist/internal/cli-deploy.js +38 -7
  103. package/dist/internal/deploy-client.d.ts +8 -0
  104. package/dist/internal/deploy-client.d.ts.map +1 -1
  105. package/dist/internal/deploy-client.js +5 -2
  106. package/dist/internal/discovery.d.ts.map +1 -1
  107. package/dist/internal/discovery.js +46 -9
  108. package/dist/internal/durable-session-store.d.ts +35 -23
  109. package/dist/internal/durable-session-store.d.ts.map +1 -1
  110. package/dist/internal/durable-session-store.js +71 -26
  111. package/dist/internal/host-otel.d.ts +25 -0
  112. package/dist/internal/host-otel.d.ts.map +1 -0
  113. package/dist/internal/host-otel.js +162 -0
  114. package/dist/internal/init-project.js +10 -10
  115. package/dist/internal/ledger-serving.d.ts +135 -67
  116. package/dist/internal/ledger-serving.d.ts.map +1 -1
  117. package/dist/internal/ledger-serving.js +326 -107
  118. package/dist/internal/otel/collector.d.ts +5 -0
  119. package/dist/internal/otel/collector.d.ts.map +1 -1
  120. package/dist/internal/otel/collector.js +68 -27
  121. package/dist/internal/sdk-runner.d.ts +1 -1
  122. package/dist/internal/sdk-runner.d.ts.map +1 -1
  123. package/dist/internal/sdk-runner.js +1 -1
  124. package/dist/internal/server.d.ts.map +1 -1
  125. package/dist/internal/server.js +3 -2
  126. package/dist/internal/session-engine.d.ts +23 -18
  127. package/dist/internal/session-engine.d.ts.map +1 -1
  128. package/dist/internal/session-engine.js +83 -32
  129. package/dist/kv/memory-kv.d.ts +8 -0
  130. package/dist/kv/memory-kv.d.ts.map +1 -1
  131. package/dist/kv/memory-kv.js +8 -0
  132. package/dist/ledger/janitor.d.ts +60 -0
  133. package/dist/ledger/janitor.d.ts.map +1 -0
  134. package/dist/ledger/janitor.js +137 -0
  135. package/dist/ledger/session-ledger.d.ts +132 -43
  136. package/dist/ledger/session-ledger.d.ts.map +1 -1
  137. package/dist/ledger/session-ledger.js +282 -50
  138. package/dist/ledger.d.ts +9 -8
  139. package/dist/ledger.d.ts.map +1 -1
  140. package/dist/ledger.js +7 -7
  141. package/dist/playground/assets/{index-D-DIYQzw.js → index-6wW6SAc4.js} +19 -19
  142. package/dist/playground/assets/index-CSRYDAFw.css +1 -0
  143. package/dist/playground/index.html +2 -2
  144. package/dist/storage-backends/cursor-hosted.d.ts +5 -0
  145. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  146. package/dist/storage-backends/cursor-hosted.js +6 -0
  147. package/dist/types.d.ts +100 -10
  148. package/dist/types.d.ts.map +1 -1
  149. package/docs/README.md +33 -106
  150. package/docs/guides/github.md +23 -18
  151. package/docs/guides/opentelemetry.md +30 -0
  152. package/docs/quickstart.md +89 -227
  153. package/docs/reference/agent-config.md +47 -0
  154. package/docs/reference/cli.md +2 -2
  155. package/docs/reference/hooks.md +1 -1
  156. package/docs/reference/tools.md +5 -4
  157. package/docs/storage.md +26 -9
  158. package/package.json +2 -2
  159. package/skills/otel/SKILL.md +20 -2
  160. package/src/channels/github/api.ts +122 -0
  161. package/src/channels/github/cursor-account.ts +23 -0
  162. package/src/channels/github/defaults.ts +1 -1
  163. package/src/channels/github/github-channel.ts +14 -1
  164. package/src/channels/github/progress.ts +84 -19
  165. package/src/channels/github/state.ts +14 -0
  166. package/src/channels/github/types.ts +7 -3
  167. package/src/channels.ts +38 -0
  168. package/src/index.ts +2 -0
  169. package/src/internal/auth.ts +10 -0
  170. package/src/internal/cli-ax.ts +2 -2
  171. package/src/internal/cli-deploy.ts +58 -3
  172. package/src/internal/deploy-client.ts +11 -0
  173. package/src/internal/discovery.ts +49 -0
  174. package/src/internal/durable-session-store.ts +66 -27
  175. package/src/internal/host-otel.ts +252 -0
  176. package/src/internal/init-project.ts +10 -10
  177. package/src/internal/ledger-serving.ts +439 -148
  178. package/src/internal/otel/collector.ts +135 -50
  179. package/src/internal/sdk-runner.ts +6 -0
  180. package/src/internal/server.ts +7 -2
  181. package/src/internal/session-engine.ts +127 -39
  182. package/src/kv/memory-kv.ts +8 -0
  183. package/src/ledger/janitor.ts +193 -0
  184. package/src/ledger/session-ledger.ts +372 -68
  185. package/src/ledger.ts +21 -6
  186. package/src/storage-backends/cursor-hosted.ts +17 -2
  187. package/src/types.ts +108 -10
  188. package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +0 -1
  189. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +0 -20
  190. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +0 -1
  191. package/dist/docs/assets/index.md.CMc5FP7E.js +0 -25
  192. package/dist/docs/assets/index.md.CMc5FP7E.lean.js +0 -1
  193. package/dist/docs/assets/quickstart.md.rIbfahJM.lean.js +0 -1
  194. package/dist/playground/assets/index-DQTH622f.css +0 -1
  195. /package/dist/docs/assets/{reference_cli.md.DqEqQpGn.lean.js → reference_cli.md.BDmjPPAm.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.lean.js → reference_hooks.md.DJE5DXcT.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.lean.js → reference_tools.md.kJfJAP2m.lean.js} +0 -0
@@ -30,7 +30,7 @@ import { foldABStream } from "./ab-fold.js";
30
30
  import { buildABSnapshot } from "./ab-snapshot.js";
31
31
  import { ApprovalGate } from "./approval-gate.js";
32
32
  import { ArtifactsStore } from "./artifacts-store.js";
33
- import { samePrincipal } from "./auth.js";
33
+ import { SessionAuthError, samePrincipal } from "./auth.js";
34
34
  import { mergeCloudOptions, resolveSessionRuntime } from "./cloud-merge.js";
35
35
  import { applyCloudUsageToEvents, cloudSdkAgentIdFromEvents, isUnpricedCompletedTurn, mapSdkTokenUsage, } from "./cloud-turn-cost.js";
36
36
  import { isDevMode } from "./dev-mode.js";
@@ -40,6 +40,7 @@ import { TurnEventMapper } from "./event-mapper.js";
40
40
  import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
41
41
  import { createHostFilesApi } from "./host-files.js";
42
42
  import { createHostKvApi } from "./host-kv.js";
43
+ import { createHostOtelApi } from "./host-otel.js";
43
44
  import { createHostPlatforms, createHostSlackClient, } from "./host-platforms.js";
44
45
  import { formatContinuationToken, newContinuationKey, newSessionId, newToolCallId, } from "./ids.js";
45
46
  import { validateToolInput } from "./json-schema.js";
@@ -61,12 +62,9 @@ export class SessionBusyError extends Error {
61
62
  this.name = "SessionBusyError";
62
63
  }
63
64
  }
64
- export class SessionAuthError extends Error {
65
- constructor(sessionId) {
66
- super(`Session ${sessionId} belongs to a different principal; pass the original caller auth on follow-up`);
67
- this.name = "SessionAuthError";
68
- }
69
- }
65
+ // Defined in auth.js (the ledger pump needs it without importing this
66
+ // module); re-exported here because this is its historical public home.
67
+ export { SessionAuthError } from "./auth.js";
70
68
  export class UnknownChannelError extends Error {
71
69
  constructor(channelId) {
72
70
  super(`Unknown channel "${channelId}"`);
@@ -112,7 +110,8 @@ export class ApprovalInterruptedError extends Error {
112
110
  }
113
111
  export class SessionEngine {
114
112
  constructor(options) {
115
- var _a, _b, _c, _d;
113
+ var _a;
114
+ var _b, _c, _d, _e;
116
115
  /** In-flight lazy restores, deduped per channel + continuation key. */
117
116
  this.restoreInFlight = new Map();
118
117
  this.channelsById = new Map();
@@ -149,10 +148,15 @@ export class SessionEngine {
149
148
  this.project = options.project;
150
149
  this.stateRoot = options.stateRoot;
151
150
  this.runner = options.runner;
152
- this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : (() => { });
153
- this.getCloudUsage = (_b = options.getCloudUsage) !== null && _b !== void 0 ? _b : defaultGetCloudUsage;
154
- this.estimateTurnCost = (_c = options.estimateTurnCost) !== null && _c !== void 0 ? _c : estimateTurnCostFromApi;
151
+ this.logger = (_b = options.logger) !== null && _b !== void 0 ? _b : (() => { });
152
+ this.getCloudUsage = (_c = options.getCloudUsage) !== null && _c !== void 0 ? _c : defaultGetCloudUsage;
153
+ this.estimateTurnCost = (_d = options.estimateTurnCost) !== null && _d !== void 0 ? _d : estimateTurnCostFromApi;
155
154
  this.otel = options.otel;
155
+ this.otelHost = createHostOtelApi({
156
+ agentName: options.project.name,
157
+ meter: options.otelMeter,
158
+ collector: options.otel,
159
+ });
156
160
  this.turnGovernor = new TurnGovernor(options.project.agent.concurrency);
157
161
  this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
158
162
  this.nudgeStore = new SlackNudgeStore(join(options.stateRoot, "slack-nudges"));
@@ -190,7 +194,7 @@ export class SessionEngine {
190
194
  // resolves them; the host-side MCP registry starts with the concrete
191
195
  // connections and gains the rest via setResolvedConnections.
192
196
  this.mcpHost = new McpHost(options.project.agent.connections.filter((connection) => !isSymbolicConnectionTransport(connection.transport)));
193
- const basePlatforms = (_d = options.platforms) !== null && _d !== void 0 ? _d : createHostPlatforms();
197
+ const basePlatforms = (_e = options.platforms) !== null && _e !== void 0 ? _e : createHostPlatforms();
194
198
  const slackBinding = resolveDedicatedSlackHostBinding(options.project.channels);
195
199
  this.platforms =
196
200
  slackBinding === undefined
@@ -206,20 +210,37 @@ export class SessionEngine {
206
210
  this.channelsById.set(channel.id, channel.definition);
207
211
  }
208
212
  this.ledgerServing = this.buildLedgerServing(options);
213
+ (_a = this.ledgerServing) === null || _a === void 0 ? void 0 : _a.start();
209
214
  }
210
215
  /**
211
216
  * Ledger-mode wiring (`serving: "ledger"`): resolve a CAS-capable
212
- * durable-session store and build the delivery seam. Resident agents
217
+ * durable-session store and build the event pump. Resident agents
213
218
  * (the default) return undefined and never touch ledger keys.
214
219
  */
215
220
  buildLedgerServing(options) {
216
221
  if (options.project.agent.serving !== "ledger") {
217
222
  return undefined;
218
223
  }
224
+ // The handle is already agent-scoped (resolveDurableSessionStore):
225
+ // two ledger-mode agents sharing one backend never resolve to each
226
+ // other's sessions or janitor-claim each other's parked envelopes.
219
227
  const ledgerKv = this.resolveDurableSessionStore(options);
220
228
  return new LedgerServing({
221
229
  ledger: new SessionLedger({ kv: ledgerKv, podId: newLedgerPodId() }),
222
- deliverTurn: (request) => this.send(request.channelId, request.message, Object.assign(Object.assign({}, request.options), { continuationToken: request.continuationToken, ledgerDelivery: true })),
230
+ deliverTurn: (envelope) => this.send(envelope.channelId, envelope.message, Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ continuationToken: envelope.continuationToken, auth: envelope.auth }, (envelope.title === undefined ? {} : { title: envelope.title })), (envelope.state === undefined ? {} : { state: envelope.state })), (envelope.mode === undefined ? {} : { mode: envelope.mode })), (envelope.purpose === undefined
231
+ ? {}
232
+ : { purpose: envelope.purpose })), (envelope.workspaceDir === undefined
233
+ ? {}
234
+ : { workspaceDir: envelope.workspaceDir })), (envelope.cloud === undefined ? {} : { cloud: envelope.cloud })), { fromLedgerPump: true })),
235
+ findSession: (channelId, continuationToken) => __awaiter(this, void 0, void 0, function* () {
236
+ let record = yield this.sessions.findByContinuation(channelId, continuationToken);
237
+ if (record === undefined) {
238
+ record = yield this.restoreSessionByContinuation(channelId, continuationToken);
239
+ }
240
+ return record === undefined
241
+ ? undefined
242
+ : this.channelSession(record.sessionId, record, false);
243
+ }),
223
244
  logger: this.logger,
224
245
  });
225
246
  }
@@ -236,20 +257,21 @@ export class SessionEngine {
236
257
  *
237
258
  * An authored backend without `cas` fails fast: silently falling back
238
259
  * to local files would fork coordination away from the deployment's
239
- * durable store. The check demands exactly the methods the
240
- * coordination-only ledger uses `listKeys` is not one of them.
260
+ * durable store. The check demands the full surface the inbox uses —
261
+ * the CAS trio plus `get`/`put`/`delete` for mailbox payload blobs and
262
+ * `listKeys` for the janitor scan and the orphan sweep.
241
263
  *
242
264
  * Whatever store wins, the returned handle is scoped here — once —
243
265
  * under `agentkit/v1/<agent>/durable-session/`. The ledger speaks bare
244
- * `session/…` / `ledger/…` keys; namespacing is this boundary's job,
245
- * exactly like `host.kv`'s agent scoping.
266
+ * `session/…` / `ledger/…` / `mailbox/…` keys; namespacing is this
267
+ * boundary's job, exactly like `host.kv`'s agent scoping.
246
268
  */
247
269
  resolveDurableSessionStore(options) {
248
270
  const namespace = ledgerKeyPrefix(options.project.name);
249
271
  const explicit = options.durableSessionStore;
250
272
  if (explicit !== undefined) {
251
273
  if (!supportsSessionLedgerKv(explicit)) {
252
- throw new Error(`Agent "${options.project.name}" declares serving: "ledger", but the configured durableSessionStore is not CAS-capable (getWithVersion / putIfAbsent / putIfVersion). Provide a store implementing those methods (see @cursor/july/kv).`);
274
+ throw new Error(`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).`);
253
275
  }
254
276
  return namespacedKv(explicit, namespace);
255
277
  }
@@ -287,7 +309,7 @@ export class SessionEngine {
287
309
  setCloudServerToolsConnection(connection) {
288
310
  this.cloudServerToolsConnection = connection;
289
311
  }
290
- /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / Slack nudges). */
312
+ /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / files / otel / Slack nudges). */
291
313
  get host() {
292
314
  return {
293
315
  mcp: this.mcpHost,
@@ -297,6 +319,7 @@ export class SessionEngine {
297
319
  evals: this.evalsApi,
298
320
  kv: this.kvApi,
299
321
  files: this.filesApi,
322
+ otel: this.otelHost,
300
323
  slackNudges: this.slackNudgeHostApi(),
301
324
  };
302
325
  }
@@ -310,7 +333,7 @@ export class SessionEngine {
310
333
  const host = this.host;
311
334
  return sessionId === undefined
312
335
  ? host
313
- : Object.assign(Object.assign({}, host), { files: this.filesApi.forSession(sessionId) });
336
+ : Object.assign(Object.assign({}, host), { files: this.filesApi.forSession(sessionId), otel: this.otelHost.forSession(sessionId) });
314
337
  }
315
338
  /** Unbound `ctx.artifacts` facade (channel handlers, scratch tool calls). */
316
339
  get artifacts() {
@@ -404,10 +427,10 @@ export class SessionEngine {
404
427
  return __awaiter(this, arguments, void 0, function* (channelId, message, options = {}) {
405
428
  var _a;
406
429
  var _b, _c, _d, _e, _f;
407
- // Ledger-mode agents acquire the session lease before any turn runs;
408
- // only the seam's own lease-holding deliveries fall through to the
409
- // resident path.
410
- if (this.ledgerServing !== undefined && options.ledgerDelivery !== true) {
430
+ // Ledger-mode agents park the event durably and coordinate through
431
+ // the session lease before any turn runs; only the pump's own
432
+ // lease-holding deliveries fall through to the resident path.
433
+ if (this.ledgerServing !== undefined && options.fromLedgerPump !== true) {
411
434
  return this.ledgerServing.deliver(channelId, message, options);
412
435
  }
413
436
  const auth = (_b = options.auth) !== null && _b !== void 0 ? _b : null;
@@ -448,10 +471,11 @@ export class SessionEngine {
448
471
  if (options.refreshState !== undefined) {
449
472
  record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { channelState: mergeChannelStateSkipNull(current.channelState, options.refreshState) })));
450
473
  }
474
+ const since = (yield this.logs.get(record.sessionId)).count;
451
475
  const coalesced = yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_c = options.admission) !== null && _c !== void 0 ? _c : "preempt" }, (options.coalesceSourceTs === undefined
452
476
  ? {}
453
477
  : { sourceTs: options.coalesceSourceTs })));
454
- return this.channelSession(record.sessionId, record, false, coalesced);
478
+ return this.channelSession(record.sessionId, record, false, coalesced, since);
455
479
  }
456
480
  const now = new Date().toISOString();
457
481
  const channelState = this.initialChannelState(channelId, options.state);
@@ -476,15 +500,23 @@ export class SessionEngine {
476
500
  };
477
501
  yield this.sessions.save(record);
478
502
  (_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(record);
503
+ // Ledger serving: another pod can follow up mid-turn, so the session
504
+ // identity (record + continuation index) must hit the shared store
505
+ // before we return a session id. Default turnEnd flush is too late.
506
+ if (this.ledgerServing !== undefined && this.storage !== undefined) {
507
+ this.storage.flushSession(record.sessionId);
508
+ yield this.storage.whenIdle();
509
+ }
479
510
  yield this.appendEvent(record.sessionId, {
480
511
  type: "session.started",
481
512
  data: { channelId },
482
513
  });
483
514
  record = yield this.enrollABs(record);
515
+ const since = (yield this.logs.get(record.sessionId)).count;
484
516
  yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_f = options.admission) !== null && _f !== void 0 ? _f : "preempt" }, (options.coalesceSourceTs === undefined
485
517
  ? {}
486
518
  : { sourceTs: options.coalesceSourceTs })));
487
- return this.channelSession(record.sessionId, record, true, false);
519
+ return this.channelSession(record.sessionId, record, true, false, since);
488
520
  });
489
521
  }
490
522
  /** Whether a continuation token already maps to a session on this channel. */
@@ -914,7 +946,9 @@ export class SessionEngine {
914
946
  }
915
947
  const startedAt = Date.now();
916
948
  try {
917
- const outcome = yield this.runner.prewarm(Object.assign({ workspaceDir, runnerStateDir: join(this.stateRoot, "runner"), sandbox: ((_b = agent.local) === null || _b === void 0 ? void 0 : _b.sandbox) === true, mcpServers: this.buildMcpServers("local"), subagents: buildRunnerSubagents(agent) }, (agent.model === undefined ? {} : { model: agent.model })));
949
+ const outcome = yield this.runner.prewarm(Object.assign(Object.assign(Object.assign({ workspaceDir, runnerStateDir: join(this.stateRoot, "runner"), sandbox: ((_b = agent.local) === null || _b === void 0 ? void 0 : _b.sandbox) === true }, (agent.allowedTools === undefined
950
+ ? {}
951
+ : { tools: agent.allowedTools })), { mcpServers: this.buildMcpServers("local"), subagents: buildRunnerSubagents(agent) }), (agent.model === undefined ? {} : { model: agent.model })));
918
952
  // Report what happened rather than that the call returned: a runner
919
953
  // that cannot warm resolves cleanly, and logging success there would
920
954
  // tell an operator the workspace is hot when nothing touched it.
@@ -1107,6 +1141,13 @@ export class SessionEngine {
1107
1141
  agentRuntime: agent.runtime,
1108
1142
  cloudOverride: record.cloudOverride,
1109
1143
  });
1144
+ // Fail closed: the SDK cannot enforce the built-in tool allowlist on
1145
+ // cloud agents yet, and a restriction that silently does not apply is
1146
+ // worse than a refused turn. Cloud-runtime agents already fail
1147
+ // discovery; this guards hybrid agents' per-send cloud sessions.
1148
+ if (runtime === "cloud" && agent.allowedTools !== undefined) {
1149
+ throw new Error(`Agent "${agent.name}" declares a built-in tool allowlist (tools), which cannot be enforced on cloud turns yet. Refusing the cloud session rather than running it unrestricted.`);
1150
+ }
1110
1151
  const mergedCloud = runtime === "cloud"
1111
1152
  ? mergeCloudOptions({
1112
1153
  base: agent.cloud,
@@ -1153,9 +1194,11 @@ export class SessionEngine {
1153
1194
  turnPrompt = `${preamble}\n\n${turnPrompt}`;
1154
1195
  }
1155
1196
  }
1156
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
1197
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
1157
1198
  ? {}
1158
- : { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), sandbox: ((_c = this.project.agent.local) === null || _c === void 0 ? void 0 : _c.sandbox) === true, runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), { customTools: runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {}, mcpServers: this.buildMcpServers(runtime, record.sessionId), subagents: buildRunnerSubagents(agent), onUpdate,
1199
+ : { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), sandbox: ((_c = this.project.agent.local) === null || _c === void 0 ? void 0 : _c.sandbox) === true }), (runtime === "local" && agent.allowedTools !== undefined
1200
+ ? { tools: agent.allowedTools }
1201
+ : {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), { customTools: runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {}, mcpServers: this.buildMcpServers(runtime, record.sessionId), subagents: buildRunnerSubagents(agent), onUpdate,
1159
1202
  signal,
1160
1203
  // Local follow-ups expire any lingering active run so a preempted turn
1161
1204
  // (or one orphaned by a crashed process) never blocks this send.
@@ -1398,6 +1441,7 @@ export class SessionEngine {
1398
1441
  }),
1399
1442
  send: (channelId, message, sendOptions = {}) => { var _a; return this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: (_a = sendOptions.auth) !== null && _a !== void 0 ? _a : args.record.auth })); },
1400
1443
  getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
1444
+ emit: args.emit,
1401
1445
  };
1402
1446
  try {
1403
1447
  const needsGate = yield evaluateNeedsApproval(args.tool.definition.needsApproval, validation.value);
@@ -1622,6 +1666,12 @@ export class SessionEngine {
1622
1666
  this.logger(`[session] tool ${toolName} start (direct ${callId})`);
1623
1667
  let result;
1624
1668
  let isError;
1669
+ const live = record;
1670
+ const emit = live === undefined
1671
+ ? undefined
1672
+ : (payload) => {
1673
+ void this.appendEvent(live.sessionId, Object.assign(Object.assign({}, payload), { turnId: callId })).catch(() => { });
1674
+ };
1625
1675
  try {
1626
1676
  result = normalizeToolResult(yield execute(validation.value, {
1627
1677
  toolCallId: callId,
@@ -1640,6 +1690,7 @@ export class SessionEngine {
1640
1690
  ? sendOptions.auth
1641
1691
  : session.auth })),
1642
1692
  getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
1693
+ emit,
1643
1694
  }));
1644
1695
  isError = isErrorEnvelope(result);
1645
1696
  }
@@ -2216,8 +2267,8 @@ export class SessionEngine {
2216
2267
  ? {}
2217
2268
  : { sdkAgentId: record.sdkAgentId }));
2218
2269
  }
2219
- channelSession(sessionId, record, isNew, coalesced = false) {
2220
- return Object.assign(Object.assign({ id: sessionId, continuationToken: record.continuationKey, isNew }, (coalesced ? { coalesced: true } : {})), { info: this.sessionInfo(record), getEventStream: (options) => { var _a; return this.eventStream(sessionId, (_a = options === null || options === void 0 ? void 0 : options.startIndex) !== null && _a !== void 0 ? _a : 0); }, waitForCompletion: () => this.waitForCompletion(sessionId), setContinuationToken: (token) => this.setContinuationToken(sessionId, token) });
2270
+ channelSession(sessionId, record, isNew, coalesced = false, since = 0) {
2271
+ return Object.assign(Object.assign({ id: sessionId, continuationToken: record.continuationKey, isNew }, (coalesced ? { coalesced: true } : {})), { info: this.sessionInfo(record), getEventStream: (options) => { var _a; return this.eventStream(sessionId, (_a = options === null || options === void 0 ? void 0 : options.startIndex) !== null && _a !== void 0 ? _a : since); }, waitForCompletion: () => this.waitForCompletion(sessionId), setContinuationToken: (token) => this.setContinuationToken(sessionId, token) });
2221
2272
  }
2222
2273
  resolveChannelId(channel) {
2223
2274
  if (typeof channel === "string") {
@@ -18,6 +18,14 @@ export declare function createMemoryKv(): HostKvCasApi;
18
18
  * fixtures, an off-hosting no-op sink). Non-durable and process-local by
19
19
  * construction; a real deployment backend should implement CAS against
20
20
  * its own store instead.
21
+ *
22
+ * **Never pair this with `serving: "ledger"` on a real backend.** The
23
+ * ledger derives its coordination store from the whole storage config,
24
+ * so a durable plain backend with a memory `cas` group silently voids
25
+ * ack-after-durable: leases, heads, and pending indexes live only in
26
+ * this process and vanish on restart while payload blobs go through the
27
+ * plain methods. Ledger state must live in the same durable store as
28
+ * the blobs.
21
29
  */
22
30
  export declare function memoryCasTable(): StorageCasTable;
23
31
  //# sourceMappingURL=memory-kv.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-kv.d.ts","sourceRoot":"","sources":["../../src/kv/memory-kv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,aAAa,CAAC;AAErB,gDAAgD;AAChD,wBAAgB,cAAc,IAAI,YAAY,CA4D7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,eAAe,CAShD"}
1
+ {"version":3,"file":"memory-kv.d.ts","sourceRoot":"","sources":["../../src/kv/memory-kv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,aAAa,CAAC;AAErB,gDAAgD;AAChD,wBAAgB,cAAc,IAAI,YAAY,CA4D7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,IAAI,eAAe,CAShD"}
@@ -91,6 +91,14 @@ export function createMemoryKv() {
91
91
  * fixtures, an off-hosting no-op sink). Non-durable and process-local by
92
92
  * construction; a real deployment backend should implement CAS against
93
93
  * its own store instead.
94
+ *
95
+ * **Never pair this with `serving: "ledger"` on a real backend.** The
96
+ * ledger derives its coordination store from the whole storage config,
97
+ * so a durable plain backend with a memory `cas` group silently voids
98
+ * ack-after-durable: leases, heads, and pending indexes live only in
99
+ * this process and vanish on restart while payload blobs go through the
100
+ * plain methods. Ledger state must live in the same durable store as
101
+ * the blobs.
94
102
  */
95
103
  export function memoryCasTable() {
96
104
  const kv = createMemoryKv();
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The ledger janitor: lazy crash recovery has a hole — a session with
3
+ * pending events and no future events would wait forever — so serving runs
4
+ * this loop. It scans `ledger/` by prefix, finds pending-with-expired-lease
5
+ * sessions, claims them, and hands each claim to the execution callback.
6
+ * Self-host runs the same loop; it is part of the library, not the control
7
+ * plane.
8
+ *
9
+ * Nothing starts on construction: call `start()` for the interval loop or
10
+ * `runOnce()` for an explicit pass (what unit tests use).
11
+ */
12
+ import type { LedgerLease, SessionLedger } from "./session-ledger.js";
13
+ export declare const DEFAULT_JANITOR_INTERVAL_MS = 15000;
14
+ export interface JanitorClaimedSession {
15
+ sessionId: string;
16
+ lease: LedgerLease;
17
+ head: number;
18
+ pending: string[];
19
+ }
20
+ export interface JanitorRunResult {
21
+ /** Ledgers scanned this pass. */
22
+ scanned: number;
23
+ /** Sessions this pass claimed and handed to the callback. */
24
+ claimed: JanitorClaimedSession[];
25
+ /** Orphan blob ids deleted (only when the sweep is enabled). */
26
+ orphansDeleted: string[];
27
+ }
28
+ export interface LedgerJanitorOptions {
29
+ ledger: SessionLedger;
30
+ /**
31
+ * Executes a recovered session: drain, run the turn, commit. Awaited so
32
+ * one pass never claims more sessions than it can serve.
33
+ */
34
+ onSession: (session: JanitorClaimedSession) => Promise<void> | void;
35
+ /** Scan cadence for `start()` (default {@link DEFAULT_JANITOR_INTERVAL_MS}). */
36
+ intervalMs?: number;
37
+ /**
38
+ * Also sweep unreferenced mailbox blobs each pass. Off by default —
39
+ * enable with `{ olderThanMs }` (see `SessionLedger.sweepOrphans`).
40
+ */
41
+ orphanSweep?: {
42
+ olderThanMs?: number;
43
+ };
44
+ /** Called with per-session or sweep failures; the pass continues. */
45
+ onError?: (error: unknown, sessionId?: string) => void;
46
+ /** Time source for lease-expiry checks (default `Date.now`). */
47
+ clock?: () => number;
48
+ }
49
+ export interface LedgerJanitor {
50
+ start(): void;
51
+ /**
52
+ * Clear the interval AND await any in-flight pass: a running pass can
53
+ * claim a session and spawn a turn, so shutdown must not proceed while
54
+ * one is mid-scan.
55
+ */
56
+ stop(): Promise<void>;
57
+ runOnce(): Promise<JanitorRunResult>;
58
+ }
59
+ export declare function createLedgerJanitor(options: LedgerJanitorOptions): LedgerJanitor;
60
+ //# sourceMappingURL=janitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"janitor.d.ts","sourceRoot":"","sources":["../../src/ledger/janitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAEV,WAAW,EACX,aAAa,EACd,MAAM,qBAAqB,CAAC;AAE7B,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,gEAAgE;IAChE,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,aAAa,CAAC;IACtB;;;OAGG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACtC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,aAAa,CA2Hf"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The ledger janitor: lazy crash recovery has a hole — a session with
3
+ * pending events and no future events would wait forever — so serving runs
4
+ * this loop. It scans `ledger/` by prefix, finds pending-with-expired-lease
5
+ * sessions, claims them, and hands each claim to the execution callback.
6
+ * Self-host runs the same loop; it is part of the library, not the control
7
+ * plane.
8
+ *
9
+ * Nothing starts on construction: call `start()` for the interval loop or
10
+ * `runOnce()` for an explicit pass (what unit tests use).
11
+ */
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ export const DEFAULT_JANITOR_INTERVAL_MS = 15000;
22
+ export function createLedgerJanitor(options) {
23
+ var _a, _b, _c;
24
+ const intervalMs = (_a = options.intervalMs) !== null && _a !== void 0 ? _a : DEFAULT_JANITOR_INTERVAL_MS;
25
+ const clock = (_b = options.clock) !== null && _b !== void 0 ? _b : Date.now;
26
+ const reportError = (_c = options.onError) !== null && _c !== void 0 ? _c : (() => {
27
+ // Recovery is best-effort per pass; an unobserved failure is retried
28
+ // on the next scan.
29
+ });
30
+ let timer;
31
+ let running = false;
32
+ /** The settled-shape promise of the pass currently scanning, if any. */
33
+ let inFlight;
34
+ const runPass = () => __awaiter(this, void 0, void 0, function* () {
35
+ // An unparseable ledger record is reported and skipped: it degrades
36
+ // that one session's recovery, never the whole agent's pass.
37
+ const ledgers = yield options.ledger.listLedgers((key, error) => {
38
+ reportError(new Error(`janitor scan: skipping unparseable ledger record at ${key}: ${String(error instanceof Error ? error.message : error)}`));
39
+ });
40
+ const claimed = [];
41
+ for (const snapshot of ledgers) {
42
+ if (snapshot.pending.length === 0) {
43
+ continue;
44
+ }
45
+ if (snapshot.lease !== null && snapshot.lease.expiresAt > clock()) {
46
+ continue;
47
+ }
48
+ let claim;
49
+ try {
50
+ claim = yield options.ledger.claim(snapshot.sessionId);
51
+ }
52
+ catch (error) {
53
+ reportError(error, snapshot.sessionId);
54
+ continue;
55
+ }
56
+ if (!claim.claimed) {
57
+ continue;
58
+ }
59
+ const session = {
60
+ sessionId: snapshot.sessionId,
61
+ lease: claim.lease,
62
+ head: claim.head,
63
+ pending: claim.pending,
64
+ };
65
+ claimed.push(session);
66
+ try {
67
+ yield options.onSession(session);
68
+ }
69
+ catch (error) {
70
+ reportError(error, snapshot.sessionId);
71
+ }
72
+ }
73
+ let orphansDeleted = [];
74
+ if (options.orphanSweep !== undefined) {
75
+ try {
76
+ const swept = yield options.ledger.sweepOrphans({
77
+ olderThanMs: options.orphanSweep.olderThanMs,
78
+ });
79
+ orphansDeleted = swept.deleted;
80
+ }
81
+ catch (error) {
82
+ reportError(error);
83
+ }
84
+ }
85
+ return { scanned: ledgers.length, claimed, orphansDeleted };
86
+ });
87
+ const runOnce = () => {
88
+ const pass = runPass();
89
+ const settled = pass.then(() => undefined, () => undefined);
90
+ inFlight = settled;
91
+ void settled.finally(() => {
92
+ if (inFlight === settled) {
93
+ inFlight = undefined;
94
+ }
95
+ });
96
+ return pass;
97
+ };
98
+ const tick = () => __awaiter(this, void 0, void 0, function* () {
99
+ // Skip a tick rather than overlap passes when a scan outlasts the
100
+ // cadence — overlapping claims would just fence each other.
101
+ if (running) {
102
+ return;
103
+ }
104
+ running = true;
105
+ try {
106
+ yield runOnce();
107
+ }
108
+ catch (error) {
109
+ reportError(error);
110
+ }
111
+ finally {
112
+ running = false;
113
+ }
114
+ });
115
+ return {
116
+ start() {
117
+ var _a;
118
+ if (timer !== undefined) {
119
+ return;
120
+ }
121
+ timer = setInterval(() => {
122
+ void tick();
123
+ }, intervalMs);
124
+ (_a = timer.unref) === null || _a === void 0 ? void 0 : _a.call(timer);
125
+ },
126
+ stop() {
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ if (timer !== undefined) {
129
+ clearInterval(timer);
130
+ timer = undefined;
131
+ }
132
+ yield inFlight;
133
+ });
134
+ },
135
+ runOnce,
136
+ };
137
+ }