@cursor/july 0.1.4 → 0.1.6

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 (182) hide show
  1. package/dist/ab.d.ts +8 -95
  2. package/dist/ab.d.ts.map +1 -1
  3. package/dist/ab.js +9 -150
  4. package/dist/bin/agent-serve.js +14 -6
  5. package/dist/docs/404.html +2 -2
  6. package/dist/docs/ab.html +4 -4
  7. package/dist/docs/assets/{app.DF-Yx4OP.js → app.DMboGKUD.js} +1 -1
  8. package/dist/docs/assets/chunks/@localSearchIndexroot.O6eP7QZr.js +1 -0
  9. package/dist/docs/assets/chunks/{VPLocalSearchBox.DSeJSudL.js → VPLocalSearchBox.DkEqEnJU.js} +1 -1
  10. package/dist/docs/assets/chunks/{theme.DZVLTXOC.js → theme.ASugdMNm.js} +2 -2
  11. package/dist/docs/assets/deployment.md.BtfEsc9S.js +55 -0
  12. package/dist/docs/assets/deployment.md.BtfEsc9S.lean.js +1 -0
  13. package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.js +10 -0
  14. package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.lean.js +1 -0
  15. package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.js +7 -0
  16. package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.lean.js +1 -0
  17. package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.js +11 -0
  18. package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.lean.js +1 -0
  19. package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.js +8 -0
  20. package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.lean.js +1 -0
  21. package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.js +8 -0
  22. package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.lean.js +1 -0
  23. package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.js +23 -0
  24. package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.lean.js +1 -0
  25. package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.js +15 -0
  26. package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.lean.js +1 -0
  27. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +2 -0
  28. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +1 -0
  29. package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.js +11 -0
  30. package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.lean.js +1 -0
  31. package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.js +19 -0
  32. package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.lean.js +1 -0
  33. package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.js +5 -0
  34. package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.lean.js +1 -0
  35. package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.js +24 -0
  36. package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.lean.js +1 -0
  37. package/dist/docs/assets/index.md.COiu-1jL.js +20 -0
  38. package/dist/docs/assets/{index.md.Cylk70gg.lean.js → index.md.COiu-1jL.lean.js} +1 -1
  39. package/dist/docs/assets/reference_cli.md.D189RBCH.js +60 -0
  40. package/dist/docs/assets/reference_cli.md.D189RBCH.lean.js +1 -0
  41. package/dist/docs/building-with-agents.html +4 -4
  42. package/dist/docs/concepts.html +4 -4
  43. package/dist/docs/deployment.html +58 -17
  44. package/dist/docs/evals.html +4 -4
  45. package/dist/docs/example-agents/approval-buddy.html +34 -0
  46. package/dist/docs/example-agents/benny.html +31 -0
  47. package/dist/docs/example-agents/bugbot.html +35 -0
  48. package/dist/docs/example-agents/codebase-wiki.html +32 -0
  49. package/dist/docs/example-agents/codeowners-review.html +32 -0
  50. package/dist/docs/example-agents/concierge.html +47 -0
  51. package/dist/docs/example-agents/fsd.html +39 -0
  52. package/dist/docs/example-agents/index.html +26 -0
  53. package/dist/docs/example-agents/knowledge-base.html +35 -0
  54. package/dist/docs/example-agents/security-reviewer.html +43 -0
  55. package/dist/docs/example-agents/slack-agent.html +29 -0
  56. package/dist/docs/example-agents/weather-agent.html +48 -0
  57. package/dist/docs/guides/agent-to-agent.html +4 -4
  58. package/dist/docs/guides/cloud-runtime.html +5 -5
  59. package/dist/docs/guides/github.html +4 -4
  60. package/dist/docs/guides/human-in-the-loop.html +4 -4
  61. package/dist/docs/guides/slack.html +4 -4
  62. package/dist/docs/guides/webhooks.html +4 -4
  63. package/dist/docs/hashmap.json +1 -1
  64. package/dist/docs/hillclimbing.html +4 -4
  65. package/dist/docs/index.html +7 -7
  66. package/dist/docs/quickstart.html +4 -4
  67. package/dist/docs/reference/agent-config.html +4 -4
  68. package/dist/docs/reference/channels.html +4 -4
  69. package/dist/docs/reference/cli.html +52 -30
  70. package/dist/docs/reference/connections.html +4 -4
  71. package/dist/docs/reference/hooks.html +4 -4
  72. package/dist/docs/reference/http-api.html +4 -4
  73. package/dist/docs/reference/instructions.html +4 -4
  74. package/dist/docs/reference/playground.html +4 -4
  75. package/dist/docs/reference/project-layout.html +4 -4
  76. package/dist/docs/reference/schedules.html +4 -4
  77. package/dist/docs/reference/sessions.html +4 -4
  78. package/dist/docs/reference/skills.html +4 -4
  79. package/dist/docs/reference/subagents.html +4 -4
  80. package/dist/docs/reference/tools.html +4 -4
  81. package/dist/docs/scaffolding-agents.html +4 -4
  82. package/dist/docs/storage.html +4 -4
  83. package/dist/docs/troubleshooting.html +4 -4
  84. package/dist/evals.d.ts +4 -61
  85. package/dist/evals.d.ts.map +1 -1
  86. package/dist/evals.js +3 -66
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/internal/ab-collector.d.ts +7 -5
  90. package/dist/internal/ab-collector.d.ts.map +1 -1
  91. package/dist/internal/ab-collector.js +3 -14
  92. package/dist/internal/ab-snapshot.d.ts +2 -4
  93. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  94. package/dist/internal/cli-ax.d.ts +18 -3
  95. package/dist/internal/cli-ax.d.ts.map +1 -1
  96. package/dist/internal/cli-ax.js +80 -42
  97. package/dist/internal/cli-deploy.d.ts.map +1 -1
  98. package/dist/internal/cli-deploy.js +5 -2
  99. package/dist/internal/cursor-event-relay.d.ts +4 -1
  100. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  101. package/dist/internal/cursor-event-relay.js +6 -0
  102. package/dist/internal/cursor-relay-core.d.ts +36 -1
  103. package/dist/internal/cursor-relay-core.d.ts.map +1 -1
  104. package/dist/internal/cursor-relay-core.js +89 -8
  105. package/dist/internal/cursor-slack-relay.d.ts +2 -2
  106. package/dist/internal/cursor-slack-relay.d.ts.map +1 -1
  107. package/dist/internal/cursor-slack-relay.js +4 -0
  108. package/dist/internal/discovery.js +3 -3
  109. package/dist/internal/eval-run-store.d.ts +33 -30
  110. package/dist/internal/eval-run-store.d.ts.map +1 -1
  111. package/dist/internal/eval-run-store.js +84 -96
  112. package/dist/internal/init-project.d.ts.map +1 -1
  113. package/dist/internal/init-project.js +3 -2
  114. package/dist/internal/playground-proxy.d.ts +5 -5
  115. package/dist/internal/playground-proxy.js +3 -3
  116. package/dist/internal/resolve-prod-target.d.ts +30 -0
  117. package/dist/internal/resolve-prod-target.d.ts.map +1 -1
  118. package/dist/internal/resolve-prod-target.js +74 -2
  119. package/dist/internal/server.d.ts.map +1 -1
  120. package/dist/internal/server.js +5 -3
  121. package/dist/internal/session-engine.d.ts +1 -2
  122. package/dist/internal/session-engine.d.ts.map +1 -1
  123. package/dist/internal/session-engine.js +14 -31
  124. package/dist/internal/storage-coordinator.d.ts +16 -15
  125. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  126. package/dist/internal/storage-coordinator.js +73 -80
  127. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  128. package/dist/playground/assets/index-7GJIUFQj.css +1 -0
  129. package/dist/playground/assets/index-mux9aj5P.js +319 -0
  130. package/dist/playground/index.html +2 -2
  131. package/dist/storage.d.ts +51 -10
  132. package/dist/storage.d.ts.map +1 -1
  133. package/dist/storage.js +27 -10
  134. package/docs/README.md +34 -5
  135. package/docs/deployment.md +352 -149
  136. package/docs/example-agents/approval-buddy.md +270 -0
  137. package/docs/example-agents/benny.md +186 -0
  138. package/docs/example-agents/bugbot.md +231 -0
  139. package/docs/example-agents/codebase-wiki.md +174 -0
  140. package/docs/example-agents/codeowners-review.md +195 -0
  141. package/docs/example-agents/concierge.md +205 -0
  142. package/docs/example-agents/fsd.md +330 -0
  143. package/docs/example-agents/index.md +102 -0
  144. package/docs/example-agents/knowledge-base.md +171 -0
  145. package/docs/example-agents/security-reviewer.md +296 -0
  146. package/docs/example-agents/slack-agent.md +146 -0
  147. package/docs/example-agents/weather-agent.md +302 -0
  148. package/docs/reference/cli.md +546 -147
  149. package/package.json +1 -1
  150. package/src/ab.ts +9 -261
  151. package/src/bin/agent-serve.ts +14 -6
  152. package/src/evals.ts +4 -118
  153. package/src/index.ts +2 -0
  154. package/src/internal/ab-collector.ts +12 -22
  155. package/src/internal/ab-snapshot.ts +2 -4
  156. package/src/internal/cli-ax.ts +101 -45
  157. package/src/internal/cli-deploy.ts +5 -2
  158. package/src/internal/cursor-event-relay.ts +10 -1
  159. package/src/internal/cursor-relay-core.ts +115 -9
  160. package/src/internal/cursor-slack-relay.ts +6 -2
  161. package/src/internal/discovery.ts +2 -2
  162. package/src/internal/eval-run-store.ts +86 -100
  163. package/src/internal/init-project.ts +3 -2
  164. package/src/internal/playground-proxy.ts +5 -5
  165. package/src/internal/resolve-prod-target.ts +101 -3
  166. package/src/internal/server.ts +5 -3
  167. package/src/internal/session-engine.ts +9 -29
  168. package/src/internal/storage-coordinator.ts +109 -101
  169. package/src/storage.ts +79 -14
  170. package/dist/docs/assets/chunks/@localSearchIndexroot.0XrheBv2.js +0 -1
  171. package/dist/docs/assets/deployment.md.DTKwE15Z.js +0 -14
  172. package/dist/docs/assets/deployment.md.DTKwE15Z.lean.js +0 -1
  173. package/dist/docs/assets/index.md.Cylk70gg.js +0 -20
  174. package/dist/docs/assets/reference_cli.md.Bv6pOxcF.js +0 -38
  175. package/dist/docs/assets/reference_cli.md.Bv6pOxcF.lean.js +0 -1
  176. package/dist/internal/json-dir-store.d.ts +0 -32
  177. package/dist/internal/json-dir-store.d.ts.map +0 -1
  178. package/dist/internal/json-dir-store.js +0 -100
  179. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  180. package/dist/playground/assets/index-Ct_Usdm8.css +0 -1
  181. package/dist/playground/assets/index-Dsyb-Vaj.js +0 -319
  182. package/src/internal/json-dir-store.ts +0 -109
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Store for playground-triggered eval batches.
3
3
  *
4
- * Default: process memory only, capped by `maxPlaygroundRuns` (see `defineEvalConfig`).
5
- * Optional durable `persistRuns` from `evals/evals.config.ts` survives restart.
4
+ * Default: process memory only, capped by `maxPlaygroundRuns` (see
5
+ * `defineEvalConfig`). When `agent/storage.ts` (`defineStorage`) declares an
6
+ * `evals` table, runs are mirrored there and survive restart.
6
7
  * Runs against the live mount's loopback URL so case sessions appear in
7
8
  * the playground session list.
8
9
  */
@@ -11,7 +12,6 @@ import { randomUUID } from "node:crypto";
11
12
  import {
12
13
  type EvalCaseSnapshot,
13
14
  type EvalConfigFile,
14
- type EvalRunPersistence,
15
15
  type EvalRunSnapshot,
16
16
  resolveEvalMaxPlaygroundRuns,
17
17
  } from "../evals.js";
@@ -30,6 +30,18 @@ export type {
30
30
  EvalRunSnapshot,
31
31
  } from "../evals.js";
32
32
 
33
+ /**
34
+ * Durable mirror for playground eval batches — the `StorageCoordinator`
35
+ * adapter over the `defineStorage` `evals` table. Writes are
36
+ * fire-and-forget (the coordinator serializes and isolates failures);
37
+ * `list` is the serve-start hydrate read.
38
+ */
39
+ export interface EvalRunStorage {
40
+ save(run: EvalRunSnapshot): void;
41
+ delete(runId: string): void;
42
+ list(): Promise<EvalRunSnapshot[]>;
43
+ }
44
+
33
45
  export interface StartEvalRunInput {
34
46
  filterIds?: string[];
35
47
  tags?: string[];
@@ -41,7 +53,7 @@ export type EvalListConfig = {
41
53
  maxConcurrency?: number;
42
54
  timeoutMs?: number;
43
55
  maxPlaygroundRuns: number;
44
- /** True when `evals.config.ts` set `persistRuns`. */
56
+ /** True when `agent/storage.ts` declares an `evals` table. */
45
57
  durableRuns: boolean;
46
58
  };
47
59
 
@@ -55,28 +67,16 @@ export class EvalRunStore {
55
67
  private activeRunId: string | undefined;
56
68
  private hydratePromise: Promise<void> | undefined;
57
69
  private hydrated = false;
58
- /** Per-run persist chain so intermediate saves cannot overwrite a later final write. */
59
- private readonly persistChains = new Map<string, Promise<void>>();
60
70
  /** Filled after listen — e.g. `http://127.0.0.1:3000/weather-agent`. */
61
71
  loopbackUrl: string | undefined;
62
72
 
63
73
  constructor(
64
74
  private readonly projectRoot: string,
65
75
  private readonly logger: (line: string) => void,
66
- /**
67
- * Project-wide sink from `agent/storage.ts` (`defineStorage`),
68
- * used when `evals.config.ts` sets no `persistRuns` of its own.
69
- */
70
- private readonly fallbackPersistence?: EvalRunPersistence
76
+ /** `defineStorage` evals-table adapter; undefined ⇒ memory only. */
77
+ private readonly storage?: EvalRunStorage | undefined
71
78
  ) {}
72
79
 
73
- /** `evals.config.ts` `persistRuns` wins; `defineStorage` is fallback. */
74
- private resolvePersist(
75
- config: Partial<EvalConfigFile>
76
- ): EvalRunPersistence | undefined {
77
- return config.persistRuns ?? this.fallbackPersistence;
78
- }
79
-
80
80
  async list(): Promise<{
81
81
  evals: Array<{
82
82
  id: string;
@@ -97,10 +97,7 @@ export class EvalRunStore {
97
97
  : { description: e.definition.description }),
98
98
  ...(e.definition.tags === undefined ? {} : { tags: e.definition.tags }),
99
99
  })),
100
- config: snapshotListConfig(
101
- config,
102
- this.resolvePersist(config) !== undefined
103
- ),
100
+ config: snapshotListConfig(config, this.storage !== undefined),
104
101
  };
105
102
  }
106
103
 
@@ -175,15 +172,12 @@ export class EvalRunStore {
175
172
  done: 0,
176
173
  },
177
174
  cases,
178
- config: snapshotRunConfig(
179
- config,
180
- this.resolvePersist(config) !== undefined
181
- ),
175
+ config: snapshotRunConfig(config, this.storage !== undefined),
182
176
  };
183
177
  this.runs.set(runId, snapshot);
184
178
  this.activeRunId = runId;
185
- await this.pruneOldRuns(config);
186
- await this.enqueuePersist(snapshot, this.resolvePersist(config));
179
+ this.pruneOldRuns(config);
180
+ this.persist(snapshot);
187
181
  this.logger(
188
182
  `[agentkit] eval run ${runId}: starting ${cases.length} case(s)`
189
183
  );
@@ -208,39 +202,28 @@ export class EvalRunStore {
208
202
  }
209
203
 
210
204
  /**
211
- * Hydrate playground eval history from durable persistence
212
- * (`evals.config.ts` `persistRuns`, else the `defineStorage`
213
- * fallback). Called eagerly from the serve startup restore step
214
- * alongside session restore and lazily as a safety net by every read.
215
- * Idempotent; concurrent callers share one pass.
205
+ * Hydrate playground eval history from the `defineStorage` evals table.
206
+ * Called eagerly from the serve startup restore step — alongside session
207
+ * restore and lazily as a safety net by every read. Idempotent;
208
+ * concurrent callers share one pass.
216
209
  */
217
210
  async hydrate(): Promise<void> {
218
211
  if (this.hydrated) {
219
212
  return;
220
213
  }
221
214
  if (this.hydratePromise === undefined) {
222
- this.hydratePromise = this.hydrateFromPersistence();
215
+ this.hydratePromise = this.hydrateFromStorage();
223
216
  }
224
217
  await this.hydratePromise;
225
218
  }
226
219
 
227
- private async hydrateFromPersistence(): Promise<void> {
220
+ private async hydrateFromStorage(): Promise<void> {
228
221
  try {
229
- const { config } = await discoverEvals(this.projectRoot);
230
- const persist = this.resolvePersist(config);
231
- if (persist === undefined) {
232
- this.hydrated = true;
233
- return;
234
- }
235
- const loaded = await persist.load({ projectRoot: this.projectRoot });
236
- if (!Array.isArray(loaded)) {
237
- this.logger(
238
- "[agentkit] eval persistRuns.load() did not return an array; ignoring"
239
- );
222
+ if (this.storage === undefined) {
240
223
  this.hydrated = true;
241
224
  return;
242
225
  }
243
- const repaired: EvalRunSnapshot[] = [];
226
+ const loaded = await this.storage.list();
244
227
  for (const run of loaded) {
245
228
  if (
246
229
  run === null ||
@@ -253,16 +236,26 @@ export class EvalRunStore {
253
236
  // Incomplete batches from a killed process should not block a new run.
254
237
  if (run.status === "running") {
255
238
  markInterruptedRun(run);
256
- repaired.push(run);
239
+ this.persist(run);
257
240
  }
258
241
  this.runs.set(run.runId, run);
259
242
  }
260
- for (const run of repaired) {
261
- await this.enqueuePersist(run, persist);
243
+ // Prune with the defaults even when eval discovery fails — loaded
244
+ // history must not stay unpruned for the process lifetime just
245
+ // because an eval file has a syntax error at boot.
246
+ let config: Partial<EvalConfigFile> = {};
247
+ try {
248
+ config = (await discoverEvals(this.projectRoot)).config;
249
+ } catch (error) {
250
+ this.logger(
251
+ `[agentkit] eval discovery failed during hydrate (pruning with defaults): ${
252
+ error instanceof Error ? error.message : String(error)
253
+ }`
254
+ );
262
255
  }
263
- await this.pruneOldRuns(config);
256
+ this.pruneOldRuns(config);
264
257
  this.logger(
265
- `[agentkit] hydrated ${this.runs.size} playground eval run(s) from persistRuns`
258
+ `[agentkit] hydrated ${this.runs.size} playground eval run(s) from storage`
266
259
  );
267
260
  } catch (error) {
268
261
  const message = error instanceof Error ? error.message : String(error);
@@ -274,7 +267,7 @@ export class EvalRunStore {
274
267
  }
275
268
  }
276
269
 
277
- private async pruneOldRuns(config: Partial<EvalConfigFile>): Promise<void> {
270
+ private pruneOldRuns(config: Partial<EvalConfigFile>): void {
278
271
  const limit = resolveEvalMaxPlaygroundRuns(config.maxPlaygroundRuns);
279
272
  const ordered = this.sortedRuns();
280
273
  if (ordered.length <= limit) {
@@ -285,58 +278,34 @@ export class EvalRunStore {
285
278
  continue;
286
279
  }
287
280
  this.runs.delete(stale.runId);
288
- await this.persistDelete(stale.runId, this.resolvePersist(config));
289
- }
290
- }
291
-
292
- /**
293
- * Queue a durable save for `run`. Snapshots are cloned at enqueue time and
294
- * writes for the same runId run strictly in order so an intermediate
295
- * fire-and-forget save cannot finish after (and overwrite) a later final save.
296
- */
297
- private enqueuePersist(
298
- run: EvalRunSnapshot,
299
- persist: EvalRunPersistence | undefined
300
- ): Promise<void> {
301
- if (persist === undefined) {
302
- return Promise.resolve();
281
+ this.storage?.delete(stale.runId);
303
282
  }
304
- const snapshot = structuredClone(run);
305
- const prev = this.persistChains.get(run.runId) ?? Promise.resolve();
306
- const next = prev
307
- .catch(() => undefined)
308
- .then(() => this.persistSave(snapshot, persist));
309
- this.persistChains.set(run.runId, next);
310
- return next;
311
283
  }
312
284
 
313
- private async persistSave(
314
- run: EvalRunSnapshot,
315
- persist: EvalRunPersistence
316
- ): Promise<void> {
317
- try {
318
- await persist.save(run, { projectRoot: this.projectRoot });
319
- } catch (error) {
320
- const message = error instanceof Error ? error.message : String(error);
321
- this.logger(
322
- `[agentkit] persistRuns.save(${run.runId}) failed: ${message}`
323
- );
324
- }
285
+ /** Mirror the run onto durable storage (coordinator clones + serializes). */
286
+ private persist(run: EvalRunSnapshot): void {
287
+ this.storage?.save(run);
325
288
  }
326
289
 
327
- private async persistDelete(
328
- runId: string,
329
- persist: EvalRunPersistence | undefined
330
- ): Promise<void> {
331
- if (persist === undefined) {
290
+ /**
291
+ * Serve is draining: durably mark any in-flight batch as interrupted
292
+ * while the storage queue can still deliver. Late `execute` writes are
293
+ * dropped by the closed coordinator, so without this a batch that was
294
+ * running at shutdown would look "running" (or silently lose its final
295
+ * completion write) until the next hydrate repairs it.
296
+ */
297
+ close(): void {
298
+ const activeRunId = this.activeRunId;
299
+ if (activeRunId === undefined) {
332
300
  return;
333
301
  }
334
- try {
335
- await persist.delete(runId, { projectRoot: this.projectRoot });
336
- } catch (error) {
337
- const message = error instanceof Error ? error.message : String(error);
338
- this.logger(`[agentkit] persistRuns.delete(${runId}) failed: ${message}`);
302
+ const run = this.runs.get(activeRunId);
303
+ if (run === undefined || run.status !== "running") {
304
+ return;
339
305
  }
306
+ markInterruptedRun(run);
307
+ this.persist(run);
308
+ this.activeRunId = undefined;
340
309
  }
341
310
 
342
311
  private async execute(
@@ -408,7 +377,7 @@ export class EvalRunStore {
408
377
  this.applyCaseResult(runId, result);
409
378
  const run = this.runs.get(runId);
410
379
  if (run !== undefined) {
411
- void this.enqueuePersist(run, this.resolvePersist(opts.config));
380
+ this.persist(run);
412
381
  }
413
382
  },
414
383
  });
@@ -418,7 +387,7 @@ export class EvalRunStore {
418
387
  }
419
388
  run.status = "completed";
420
389
  run.finishedAt = new Date().toISOString();
421
- await this.enqueuePersist(run, this.resolvePersist(opts.config));
390
+ this.persist(run);
422
391
  this.logger(
423
392
  `[agentkit] eval run ${runId}: done — ${run.summary.passed} passed, ${run.summary.failed} failed`
424
393
  );
@@ -430,7 +399,7 @@ export class EvalRunStore {
430
399
  run.status = "failed";
431
400
  run.finishedAt = new Date().toISOString();
432
401
  run.error = error instanceof Error ? error.message : String(error);
433
- await this.enqueuePersist(run, this.resolvePersist(opts.config));
402
+ this.persist(run);
434
403
  this.logger(`[agentkit] eval run ${runId}: failed — ${run.error}`);
435
404
  } finally {
436
405
  if (this.activeRunId === runId) {
@@ -489,6 +458,23 @@ function markInterruptedRun(run: EvalRunSnapshot): void {
489
458
  c.error = c.error ?? INTERRUPTED_CASE_ERROR;
490
459
  }
491
460
  }
461
+ // Reconcile the summary with the repaired case rows so a persisted
462
+ // interrupted batch never claims pre-interruption pass counts.
463
+ let passed = 0;
464
+ let failed = 0;
465
+ for (const c of run.cases) {
466
+ if (c.ok === true) {
467
+ passed += 1;
468
+ } else {
469
+ failed += 1;
470
+ }
471
+ }
472
+ run.summary = {
473
+ passed,
474
+ failed,
475
+ total: run.cases.length,
476
+ done: run.cases.length,
477
+ };
492
478
  }
493
479
 
494
480
  function snapshotListConfig(
@@ -433,14 +433,15 @@ Recommended loop once the project exists:
433
433
  1. Edit agent/ + tools under agent/tools/
434
434
  2. ${CLI_COMMAND_NAME} info --dir . --json
435
435
  3. ${CLI_COMMAND_NAME} run --dir . --message "..."
436
- 4. ${CLI_COMMAND_NAME} eval --dir .
436
+ 4. ${CLI_COMMAND_NAME} eval --local --dir .
437
437
 
438
438
  Evals:
439
439
  Create evals/**/*.eval.ts using defineEval from ${PACKAGE_NAME}/evals
440
440
  Case id is the path: evals/weather/nyc.eval.ts → weather/nyc
441
441
  Required: evals/evals.config.ts with defineEvalConfig({ maxConcurrency })
442
442
  (hard-capped at 200 due to model provider request limits)
443
- Run: ${CLI_COMMAND_NAME} eval [--json] [evalId...]
443
+ Run: ${CLI_COMMAND_NAME} eval --local [--json] [evalId...]
444
+ (omit --local to run against the hosted deployment)
444
445
 
445
446
  See AGENTS.md in the ${PACKAGE_NAME} package for the full loop.
446
447
  `);
@@ -14,9 +14,9 @@
14
14
  * request-targets are rejected so injected headers never leave the configured
15
15
  * upstream.
16
16
  *
17
- * When `refreshUpstream` is provided (hosted `--prod`), credentials are
18
- * re-minted shortly before `expiresAt` and once on upstream 401 so a long
19
- * playground session does not require restarting the CLI.
17
+ * When `refreshUpstream` is provided (e.g. short-lived upstream tokens),
18
+ * credentials are re-minted shortly before `expiresAt` and once on upstream
19
+ * 401 so a long playground session does not require restarting the CLI.
20
20
  */
21
21
 
22
22
  import { randomBytes } from "node:crypto";
@@ -84,8 +84,8 @@ export interface StartPlaygroundProxyArgs {
84
84
  /** Initial engineAccess expiry (ISO); enables proactive refresh. */
85
85
  expiresAt?: string;
86
86
  /**
87
- * Re-mint upstream URL + headers (hosted `--prod`). Single-flighted across
88
- * concurrent browser requests; called near expiry and once on 401.
87
+ * Re-mint upstream URL + headers. Single-flighted across concurrent browser
88
+ * requests; called near expiry and once on 401.
89
89
  */
90
90
  refreshUpstream?: () => Promise<PlaygroundUpstreamAccess>;
91
91
  /** Bind port; `0` = ephemeral. */
@@ -48,6 +48,29 @@ export interface ResolvedProdTarget {
48
48
  expiresAt: string;
49
49
  }
50
50
 
51
+ /**
52
+ * Thrown when a hosted deployment cannot be resolved. `notFound` is true
53
+ * when the deployment does not exist, so callers can suggest deploying
54
+ * (or running locally) instead of surfacing a bare API error.
55
+ */
56
+ export class ProdTargetError extends Error {
57
+ constructor(
58
+ message: string,
59
+ readonly options: { slug: string; notFound?: boolean } = { slug: "" }
60
+ ) {
61
+ super(message);
62
+ this.name = "ProdTargetError";
63
+ }
64
+
65
+ get slug(): string {
66
+ return this.options.slug;
67
+ }
68
+
69
+ get notFound(): boolean {
70
+ return this.options.notFound === true;
71
+ }
72
+ }
73
+
51
74
  /**
52
75
  * Look up a running deployment and return its engineAccess URL/headers.
53
76
  * Throws {@link Error} with a CLI-ready message on failure.
@@ -65,8 +88,10 @@ export async function resolveProdTarget(
65
88
  err,
66
89
  });
67
90
  if (typeof auth === "number") {
91
+ // Not "--prod": `eval` resolves the hosted deployment by default,
92
+ // without any flag.
68
93
  throw new Error(
69
- `Could not resolve Cursor credentials/team for --prod (see above).`
94
+ `Could not resolve Cursor credentials/team for the hosted deployment (see above).`
70
95
  );
71
96
  }
72
97
 
@@ -83,8 +108,9 @@ export async function resolveProdTarget(
83
108
  });
84
109
  } catch (error) {
85
110
  if (error instanceof DeployApiError) {
86
- throw new Error(
87
- `Deployment "${slug}" (team ${auth.teamId}): ${error.message}`
111
+ throw new ProdTargetError(
112
+ `Deployment "${slug}" (team ${auth.teamId}): ${error.message}`,
113
+ { slug, notFound: error.status === 404 }
88
114
  );
89
115
  }
90
116
  throw error;
@@ -143,6 +169,78 @@ export async function resolveProdTarget(
143
169
  };
144
170
  }
145
171
 
172
+ export interface ResolvedProdAliasPlayground {
173
+ /** Hosted playground URL on the API alias (SPA owns alias-token sign-in). */
174
+ playgroundUrl: string;
175
+ slug: string;
176
+ }
177
+
178
+ /**
179
+ * Look up a running deployment and return its stable alias playground URL.
180
+ * Does not mint `engineAccess` — the browser signs in with the alias token.
181
+ * Throws {@link Error} with a CLI-ready message on failure.
182
+ */
183
+ export async function resolveProdAliasPlaygroundUrl(
184
+ options: ResolveProdTargetOptions & { sessionId?: string }
185
+ ): Promise<ResolvedProdAliasPlayground> {
186
+ const err =
187
+ options.err ?? ((text: string) => void process.stderr.write(text));
188
+ const slug = resolveProdSlug(options);
189
+ const auth = await resolveDeployAuth({
190
+ team: options.team,
191
+ apiKey: options.apiKey,
192
+ fetchImpl: options.fetchImpl,
193
+ err,
194
+ });
195
+ if (typeof auth === "number") {
196
+ throw new Error(
197
+ `Could not resolve Cursor credentials/team for the hosted deployment (see above).`
198
+ );
199
+ }
200
+
201
+ let detail: DeploymentDetail;
202
+ try {
203
+ detail = await auth.client.getDeployment({
204
+ teamId: auth.teamId,
205
+ slug,
206
+ });
207
+ } catch (error) {
208
+ if (error instanceof DeployApiError) {
209
+ throw new Error(
210
+ `Deployment "${slug}" (team ${auth.teamId}): ${error.message}`
211
+ );
212
+ }
213
+ throw error;
214
+ }
215
+
216
+ const statusOk = detail.status === "running" || detail.status === "degraded";
217
+ if (!statusOk) {
218
+ const lastError =
219
+ detail.lastError === null || detail.lastError === undefined
220
+ ? ""
221
+ : ` ${sanitizeCustomerFacingDetail(detail.lastError)}`;
222
+ throw new Error(
223
+ `Deployment "${slug}" is ${detail.status}, not running.${lastError}\n` +
224
+ `Deploy or wait: \`${CLI} deploy --slug ${slug}\` / \`${CLI} deployment ${slug}\``
225
+ );
226
+ }
227
+
228
+ const aliasBase = auth.client.aliasUrl(detail.engineAlias).replace(/\/$/, "");
229
+ const base = `${aliasBase}/playground`;
230
+ const sessionId =
231
+ options.sessionId !== undefined && options.sessionId.trim() !== ""
232
+ ? options.sessionId.trim()
233
+ : undefined;
234
+ const playgroundUrl =
235
+ sessionId === undefined
236
+ ? base
237
+ : `${base}?sessionId=${encodeURIComponent(sessionId)}`;
238
+
239
+ err(`${stderrPalette().dim(`Using hosted deployment "${slug}".`)}\n`);
240
+
241
+ return { playgroundUrl, slug };
242
+ }
243
+
146
244
  /** Strip obvious hosting internals from customer-facing error fragments. */
147
245
  function sanitizeCustomerFacingDetail(message: string): string {
148
246
  return message
@@ -362,9 +362,8 @@ export async function startServer(
362
362
  const evalRuns = new EvalRunStore(
363
363
  project.rootDir,
364
364
  logger,
365
- // defineStorage fallback for playground eval history; explicit
366
- // `evals.config.ts` persistRuns still wins inside the store.
367
- engine.storage?.asEvalRunPersistence()
365
+ // Playground eval history mirrors onto the defineStorage evals table.
366
+ engine.storage?.evalRuns()
368
367
  );
369
368
  // Eval history restores with the same startup step as sessions (reads
370
369
  // also hydrate lazily, but doing it here surfaces store problems at
@@ -738,6 +737,9 @@ export async function startServer(
738
737
  for (const runtime of runtimes) {
739
738
  runtime.scheduleRunner.stop();
740
739
  runtime.reminderRunner.stop();
740
+ // Before the engines close their storage coordinators, so the
741
+ // interrupted-batch write can still reach the sink.
742
+ runtime.evalRuns.close();
741
743
  }
742
744
  for (const stop of channelStopHooks) {
743
745
  try {
@@ -204,8 +204,7 @@ export class SessionEngine {
204
204
  * Runtime for `agent/storage.ts` (`defineStorage`), when
205
205
  * authored. All durable state changes funnel through it (session
206
206
  * records, event chunks, A/B samples/snapshots); the eval run store
207
- * attaches via {@link StorageCoordinator.asEvalRunPersistence} at
208
- * serve start.
207
+ * attaches via {@link StorageCoordinator.evalRuns} at serve start.
209
208
  */
210
209
  readonly storage: StorageCoordinator | undefined;
211
210
  /** In-flight lazy restores, deduped per channel + continuation key. */
@@ -261,18 +260,12 @@ export class SessionEngine {
261
260
  projectRoot: options.project.rootDir,
262
261
  logger: this.logger,
263
262
  });
264
- // Domain-specific hooks win; the defineStorage sink is the fallback.
265
- const persistSamples =
266
- options.project.abConfig?.persistSamples ??
267
- this.storage?.asABSamplePersistence();
268
263
  this.abCollector = new ABCollector(
269
264
  options.project.abs,
270
265
  this.logger,
271
266
  async (sessionId) => (await this.logs.get(sessionId)).snapshot(0),
272
- {
273
- projectRoot: options.project.rootDir,
274
- persistSamples,
275
- }
267
+ // Samples flow to the defineStorage `abs` table (no-op without one).
268
+ { persistSample: (sample) => this.storage?.abSample(sample) }
276
269
  );
277
270
  // Peer and Cursor-account transports are symbolic until the serve host
278
271
  // resolves them; the host-side MCP registry starts with the concrete
@@ -1592,6 +1585,7 @@ export class SessionEngine {
1592
1585
  return restoredSnapshot;
1593
1586
  }
1594
1587
  }
1588
+ const abTable = this.project.storage?.abs;
1595
1589
  const snapshot = await buildABSnapshot({
1596
1590
  experiments: this.project.abs,
1597
1591
  agentName: this.project.name,
@@ -1602,27 +1596,13 @@ export class SessionEngine {
1602
1596
  logger: this.logger,
1603
1597
  config: {
1604
1598
  maxPlaygroundSessions,
1605
- durableSamples: abConfig?.persistSamples !== undefined,
1606
- durableSnapshots: abConfig?.persistSnapshots !== undefined,
1599
+ durableSamples: abTable !== undefined,
1600
+ durableSnapshots: abTable?.putSnapshot !== undefined,
1607
1601
  },
1608
1602
  });
1609
- // Side effect on the read path: playground polls GET /v1/abs ~every 4s.
1610
- // Authors who set persistSnapshots accept that write cadence.
1611
- const persistSnapshots = abConfig?.persistSnapshots;
1612
- if (persistSnapshots !== undefined) {
1613
- try {
1614
- await persistSnapshots.save(snapshot, {
1615
- projectRoot: this.project.rootDir,
1616
- });
1617
- } catch (error) {
1618
- this.logger(
1619
- `[agentkit] ab persistSnapshots.save threw: ${describeError(error)}`
1620
- );
1621
- }
1622
- } else {
1623
- // defineStorage fallback (throttled inside the coordinator).
1624
- this.storage?.abSnapshot(snapshot);
1625
- }
1603
+ // Side effect on the read path: playground polls GET /v1/abs ~every 4s;
1604
+ // the coordinator throttles the actual table write.
1605
+ this.storage?.abSnapshot(snapshot);
1626
1606
  return snapshot;
1627
1607
  }
1628
1608