@cursor/july 0.1.5 → 0.1.7

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 (177) 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 +41 -8
  5. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  6. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  7. package/dist/docs/404.html +2 -2
  8. package/dist/docs/ab.html +4 -4
  9. package/dist/docs/assets/{app.DabPG-io.js → app.COTN7wgo.js} +1 -1
  10. package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +1 -0
  11. package/dist/docs/assets/chunks/{VPLocalSearchBox.jmyr0bU0.js → VPLocalSearchBox.BW3TBdT0.js} +1 -1
  12. package/dist/docs/assets/chunks/{theme.DysN9-VN.js → theme.BEJW0vE7.js} +2 -2
  13. package/dist/docs/assets/deployment.md.BtfEsc9S.js +55 -0
  14. package/dist/docs/assets/deployment.md.BtfEsc9S.lean.js +1 -0
  15. package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.js +10 -0
  16. package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.lean.js +1 -0
  17. package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.js +7 -0
  18. package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.lean.js +1 -0
  19. package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.js +11 -0
  20. package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.lean.js +1 -0
  21. package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.js +8 -0
  22. package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.lean.js +1 -0
  23. package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.js +8 -0
  24. package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.lean.js +1 -0
  25. package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.js +23 -0
  26. package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.lean.js +1 -0
  27. package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.js +15 -0
  28. package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.lean.js +1 -0
  29. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +2 -0
  30. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +1 -0
  31. package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.js +11 -0
  32. package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.lean.js +1 -0
  33. package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.js +19 -0
  34. package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.lean.js +1 -0
  35. package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.js +5 -0
  36. package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.lean.js +1 -0
  37. package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.js +24 -0
  38. package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.lean.js +1 -0
  39. package/dist/docs/assets/index.md.COiu-1jL.js +20 -0
  40. package/dist/docs/assets/{index.md.Cylk70gg.lean.js → index.md.COiu-1jL.lean.js} +1 -1
  41. package/dist/docs/assets/reference_cli.md.D189RBCH.js +60 -0
  42. package/dist/docs/assets/reference_cli.md.D189RBCH.lean.js +1 -0
  43. package/dist/docs/building-with-agents.html +4 -4
  44. package/dist/docs/concepts.html +4 -4
  45. package/dist/docs/deployment.html +58 -17
  46. package/dist/docs/evals.html +4 -4
  47. package/dist/docs/example-agents/approval-buddy.html +34 -0
  48. package/dist/docs/example-agents/benny.html +31 -0
  49. package/dist/docs/example-agents/bugbot.html +35 -0
  50. package/dist/docs/example-agents/codebase-wiki.html +32 -0
  51. package/dist/docs/example-agents/codeowners-review.html +32 -0
  52. package/dist/docs/example-agents/concierge.html +47 -0
  53. package/dist/docs/example-agents/fsd.html +39 -0
  54. package/dist/docs/example-agents/index.html +26 -0
  55. package/dist/docs/example-agents/knowledge-base.html +35 -0
  56. package/dist/docs/example-agents/security-reviewer.html +43 -0
  57. package/dist/docs/example-agents/slack-agent.html +29 -0
  58. package/dist/docs/example-agents/weather-agent.html +48 -0
  59. package/dist/docs/guides/agent-to-agent.html +4 -4
  60. package/dist/docs/guides/cloud-runtime.html +5 -5
  61. package/dist/docs/guides/github.html +4 -4
  62. package/dist/docs/guides/human-in-the-loop.html +4 -4
  63. package/dist/docs/guides/slack.html +4 -4
  64. package/dist/docs/guides/webhooks.html +4 -4
  65. package/dist/docs/hashmap.json +1 -1
  66. package/dist/docs/hillclimbing.html +4 -4
  67. package/dist/docs/index.html +7 -7
  68. package/dist/docs/quickstart.html +4 -4
  69. package/dist/docs/reference/agent-config.html +4 -4
  70. package/dist/docs/reference/channels.html +4 -4
  71. package/dist/docs/reference/cli.html +52 -30
  72. package/dist/docs/reference/connections.html +4 -4
  73. package/dist/docs/reference/hooks.html +4 -4
  74. package/dist/docs/reference/http-api.html +4 -4
  75. package/dist/docs/reference/instructions.html +4 -4
  76. package/dist/docs/reference/playground.html +4 -4
  77. package/dist/docs/reference/project-layout.html +4 -4
  78. package/dist/docs/reference/schedules.html +4 -4
  79. package/dist/docs/reference/sessions.html +4 -4
  80. package/dist/docs/reference/skills.html +4 -4
  81. package/dist/docs/reference/subagents.html +4 -4
  82. package/dist/docs/reference/tools.html +4 -4
  83. package/dist/docs/scaffolding-agents.html +4 -4
  84. package/dist/docs/storage.html +4 -4
  85. package/dist/docs/troubleshooting.html +4 -4
  86. package/dist/evals.d.ts +5 -62
  87. package/dist/evals.d.ts.map +1 -1
  88. package/dist/evals.js +3 -66
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/internal/ab-collector.d.ts +7 -5
  92. package/dist/internal/ab-collector.d.ts.map +1 -1
  93. package/dist/internal/ab-collector.js +3 -14
  94. package/dist/internal/ab-snapshot.d.ts +2 -4
  95. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  96. package/dist/internal/cli-ax.d.ts +33 -5
  97. package/dist/internal/cli-ax.d.ts.map +1 -1
  98. package/dist/internal/cli-ax.js +428 -87
  99. package/dist/internal/cli-deploy.js +1 -1
  100. package/dist/internal/discovery.js +3 -3
  101. package/dist/internal/eval-run-store.d.ts +35 -30
  102. package/dist/internal/eval-run-store.d.ts.map +1 -1
  103. package/dist/internal/eval-run-store.js +88 -100
  104. package/dist/internal/evals-client.d.ts +96 -0
  105. package/dist/internal/evals-client.d.ts.map +1 -0
  106. package/dist/internal/evals-client.js +262 -0
  107. package/dist/internal/init-project.d.ts.map +1 -1
  108. package/dist/internal/init-project.js +1 -0
  109. package/dist/internal/persistence-coordinator.d.ts +127 -0
  110. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  111. package/dist/internal/playground-proxy.d.ts +5 -5
  112. package/dist/internal/playground-proxy.js +3 -3
  113. package/dist/internal/resolve-prod-target.d.ts +30 -0
  114. package/dist/internal/resolve-prod-target.d.ts.map +1 -1
  115. package/dist/internal/resolve-prod-target.js +74 -2
  116. package/dist/internal/server.d.ts.map +1 -1
  117. package/dist/internal/server.js +16 -5
  118. package/dist/internal/session-engine.d.ts +1 -2
  119. package/dist/internal/session-engine.d.ts.map +1 -1
  120. package/dist/internal/session-engine.js +14 -31
  121. package/dist/internal/storage-coordinator.d.ts +16 -15
  122. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  123. package/dist/internal/storage-coordinator.js +73 -80
  124. package/dist/persistence.d.ts +184 -0
  125. package/dist/persistence.d.ts.map +1 -0
  126. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  127. package/dist/playground/assets/index-72vCOBWO.js +86 -0
  128. package/dist/playground/assets/index-BjnMwYoR.css +1 -0
  129. package/dist/playground/index.html +2 -2
  130. package/dist/storage.d.ts +51 -10
  131. package/dist/storage.d.ts.map +1 -1
  132. package/dist/storage.js +27 -10
  133. package/docs/README.md +34 -5
  134. package/docs/deployment.md +352 -149
  135. package/docs/example-agents/approval-buddy.md +270 -0
  136. package/docs/example-agents/benny.md +186 -0
  137. package/docs/example-agents/bugbot.md +231 -0
  138. package/docs/example-agents/codebase-wiki.md +174 -0
  139. package/docs/example-agents/codeowners-review.md +195 -0
  140. package/docs/example-agents/concierge.md +205 -0
  141. package/docs/example-agents/fsd.md +330 -0
  142. package/docs/example-agents/index.md +102 -0
  143. package/docs/example-agents/knowledge-base.md +171 -0
  144. package/docs/example-agents/security-reviewer.md +296 -0
  145. package/docs/example-agents/slack-agent.md +146 -0
  146. package/docs/example-agents/weather-agent.md +302 -0
  147. package/docs/reference/cli.md +546 -147
  148. package/package.json +1 -1
  149. package/src/ab.ts +9 -261
  150. package/src/bin/agent-serve.ts +46 -7
  151. package/src/evals.ts +5 -119
  152. package/src/index.ts +2 -0
  153. package/src/internal/ab-collector.ts +12 -22
  154. package/src/internal/ab-snapshot.ts +2 -4
  155. package/src/internal/cli-ax.ts +551 -104
  156. package/src/internal/cli-deploy.ts +1 -1
  157. package/src/internal/discovery.ts +2 -2
  158. package/src/internal/eval-run-store.ts +91 -100
  159. package/src/internal/evals-client.ts +431 -0
  160. package/src/internal/init-project.ts +1 -0
  161. package/src/internal/playground-proxy.ts +5 -5
  162. package/src/internal/resolve-prod-target.ts +101 -3
  163. package/src/internal/server.ts +17 -3
  164. package/src/internal/session-engine.ts +9 -29
  165. package/src/internal/storage-coordinator.ts +109 -101
  166. package/src/storage.ts +79 -14
  167. package/dist/docs/assets/chunks/@localSearchIndexroot.QwK5BtEH.js +0 -1
  168. package/dist/docs/assets/deployment.md.DTKwE15Z.js +0 -14
  169. package/dist/docs/assets/deployment.md.DTKwE15Z.lean.js +0 -1
  170. package/dist/docs/assets/index.md.Cylk70gg.js +0 -20
  171. package/dist/docs/assets/reference_cli.md.Bv6pOxcF.js +0 -38
  172. package/dist/docs/assets/reference_cli.md.Bv6pOxcF.lean.js +0 -1
  173. package/dist/internal/json-dir-store.js +0 -100
  174. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  175. package/dist/playground/assets/index-BEauYlII.css +0 -1
  176. package/dist/playground/assets/index-BtM0wEGg.js +0 -319
  177. package/src/internal/json-dir-store.ts +0 -109
@@ -675,7 +675,7 @@ async function deployOne(
675
675
  ` kind: ${detail.deploymentKind}\n` +
676
676
  ` alias: ${aliasUrl}\n` +
677
677
  ` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
678
- ` or: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
678
+ ` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
679
679
  );
680
680
  return 0;
681
681
  }
@@ -1611,8 +1611,8 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1611
1611
  maxPlaygroundSessions: resolveABMaxPlaygroundSessions(
1612
1612
  project.abConfig?.maxPlaygroundSessions
1613
1613
  ),
1614
- durableSamples: project.abConfig?.persistSamples !== undefined,
1615
- durableSnapshots: project.abConfig?.persistSnapshots !== undefined,
1614
+ durableSamples: project.storage?.abs !== undefined,
1615
+ durableSnapshots: project.storage?.abs?.putSnapshot !== undefined,
1616
1616
  },
1617
1617
  }),
1618
1618
  storage:
@@ -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,18 +30,32 @@ 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[];
36
48
  bearerToken?: string;
37
49
  verbose?: boolean;
50
+ /** Per-case timeout override (same semantics as CLI `--timeout-ms`). */
51
+ timeoutMs?: number;
38
52
  }
39
53
 
40
54
  export type EvalListConfig = {
41
55
  maxConcurrency?: number;
42
56
  timeoutMs?: number;
43
57
  maxPlaygroundRuns: number;
44
- /** True when `evals.config.ts` set `persistRuns`. */
58
+ /** True when `agent/storage.ts` declares an `evals` table. */
45
59
  durableRuns: boolean;
46
60
  };
47
61
 
@@ -55,28 +69,16 @@ export class EvalRunStore {
55
69
  private activeRunId: string | undefined;
56
70
  private hydratePromise: Promise<void> | undefined;
57
71
  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
72
  /** Filled after listen — e.g. `http://127.0.0.1:3000/weather-agent`. */
61
73
  loopbackUrl: string | undefined;
62
74
 
63
75
  constructor(
64
76
  private readonly projectRoot: string,
65
77
  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
78
+ /** `defineStorage` evals-table adapter; undefined ⇒ memory only. */
79
+ private readonly storage?: EvalRunStorage | undefined
71
80
  ) {}
72
81
 
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
82
  async list(): Promise<{
81
83
  evals: Array<{
82
84
  id: string;
@@ -97,10 +99,7 @@ export class EvalRunStore {
97
99
  : { description: e.definition.description }),
98
100
  ...(e.definition.tags === undefined ? {} : { tags: e.definition.tags }),
99
101
  })),
100
- config: snapshotListConfig(
101
- config,
102
- this.resolvePersist(config) !== undefined
103
- ),
102
+ config: snapshotListConfig(config, this.storage !== undefined),
104
103
  };
105
104
  }
106
105
 
@@ -175,15 +174,12 @@ export class EvalRunStore {
175
174
  done: 0,
176
175
  },
177
176
  cases,
178
- config: snapshotRunConfig(
179
- config,
180
- this.resolvePersist(config) !== undefined
181
- ),
177
+ config: snapshotRunConfig(config, this.storage !== undefined),
182
178
  };
183
179
  this.runs.set(runId, snapshot);
184
180
  this.activeRunId = runId;
185
- await this.pruneOldRuns(config);
186
- await this.enqueuePersist(snapshot, this.resolvePersist(config));
181
+ this.pruneOldRuns(config);
182
+ this.persist(snapshot);
187
183
  this.logger(
188
184
  `[agentkit] eval run ${runId}: starting ${cases.length} case(s)`
189
185
  );
@@ -196,6 +192,7 @@ export class EvalRunStore {
196
192
  ? {}
197
193
  : { bearerToken: input.bearerToken }),
198
194
  ...(input.verbose === undefined ? {} : { verbose: input.verbose }),
195
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
199
196
  });
200
197
 
201
198
  return snapshot;
@@ -208,39 +205,28 @@ export class EvalRunStore {
208
205
  }
209
206
 
210
207
  /**
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.
208
+ * Hydrate playground eval history from the `defineStorage` evals table.
209
+ * Called eagerly from the serve startup restore step — alongside session
210
+ * restore and lazily as a safety net by every read. Idempotent;
211
+ * concurrent callers share one pass.
216
212
  */
217
213
  async hydrate(): Promise<void> {
218
214
  if (this.hydrated) {
219
215
  return;
220
216
  }
221
217
  if (this.hydratePromise === undefined) {
222
- this.hydratePromise = this.hydrateFromPersistence();
218
+ this.hydratePromise = this.hydrateFromStorage();
223
219
  }
224
220
  await this.hydratePromise;
225
221
  }
226
222
 
227
- private async hydrateFromPersistence(): Promise<void> {
223
+ private async hydrateFromStorage(): Promise<void> {
228
224
  try {
229
- const { config } = await discoverEvals(this.projectRoot);
230
- const persist = this.resolvePersist(config);
231
- if (persist === undefined) {
225
+ if (this.storage === undefined) {
232
226
  this.hydrated = true;
233
227
  return;
234
228
  }
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
- );
240
- this.hydrated = true;
241
- return;
242
- }
243
- const repaired: EvalRunSnapshot[] = [];
229
+ const loaded = await this.storage.list();
244
230
  for (const run of loaded) {
245
231
  if (
246
232
  run === null ||
@@ -253,16 +239,26 @@ export class EvalRunStore {
253
239
  // Incomplete batches from a killed process should not block a new run.
254
240
  if (run.status === "running") {
255
241
  markInterruptedRun(run);
256
- repaired.push(run);
242
+ this.persist(run);
257
243
  }
258
244
  this.runs.set(run.runId, run);
259
245
  }
260
- for (const run of repaired) {
261
- await this.enqueuePersist(run, persist);
246
+ // Prune with the defaults even when eval discovery fails — loaded
247
+ // history must not stay unpruned for the process lifetime just
248
+ // because an eval file has a syntax error at boot.
249
+ let config: Partial<EvalConfigFile> = {};
250
+ try {
251
+ config = (await discoverEvals(this.projectRoot)).config;
252
+ } catch (error) {
253
+ this.logger(
254
+ `[agentkit] eval discovery failed during hydrate (pruning with defaults): ${
255
+ error instanceof Error ? error.message : String(error)
256
+ }`
257
+ );
262
258
  }
263
- await this.pruneOldRuns(config);
259
+ this.pruneOldRuns(config);
264
260
  this.logger(
265
- `[agentkit] hydrated ${this.runs.size} playground eval run(s) from persistRuns`
261
+ `[agentkit] hydrated ${this.runs.size} playground eval run(s) from storage`
266
262
  );
267
263
  } catch (error) {
268
264
  const message = error instanceof Error ? error.message : String(error);
@@ -274,7 +270,7 @@ export class EvalRunStore {
274
270
  }
275
271
  }
276
272
 
277
- private async pruneOldRuns(config: Partial<EvalConfigFile>): Promise<void> {
273
+ private pruneOldRuns(config: Partial<EvalConfigFile>): void {
278
274
  const limit = resolveEvalMaxPlaygroundRuns(config.maxPlaygroundRuns);
279
275
  const ordered = this.sortedRuns();
280
276
  if (ordered.length <= limit) {
@@ -285,58 +281,34 @@ export class EvalRunStore {
285
281
  continue;
286
282
  }
287
283
  this.runs.delete(stale.runId);
288
- await this.persistDelete(stale.runId, this.resolvePersist(config));
284
+ this.storage?.delete(stale.runId);
289
285
  }
290
286
  }
291
287
 
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();
303
- }
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;
288
+ /** Mirror the run onto durable storage (coordinator clones + serializes). */
289
+ private persist(run: EvalRunSnapshot): void {
290
+ this.storage?.save(run);
311
291
  }
312
292
 
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
- }
325
- }
326
-
327
- private async persistDelete(
328
- runId: string,
329
- persist: EvalRunPersistence | undefined
330
- ): Promise<void> {
331
- if (persist === undefined) {
293
+ /**
294
+ * Serve is draining: durably mark any in-flight batch as interrupted
295
+ * while the storage queue can still deliver. Late `execute` writes are
296
+ * dropped by the closed coordinator, so without this a batch that was
297
+ * running at shutdown would look "running" (or silently lose its final
298
+ * completion write) until the next hydrate repairs it.
299
+ */
300
+ close(): void {
301
+ const activeRunId = this.activeRunId;
302
+ if (activeRunId === undefined) {
332
303
  return;
333
304
  }
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}`);
305
+ const run = this.runs.get(activeRunId);
306
+ if (run === undefined || run.status !== "running") {
307
+ return;
339
308
  }
309
+ markInterruptedRun(run);
310
+ this.persist(run);
311
+ this.activeRunId = undefined;
340
312
  }
341
313
 
342
314
  private async execute(
@@ -347,6 +319,7 @@ export class EvalRunStore {
347
319
  config: Partial<EvalConfigFile>;
348
320
  bearerToken?: string;
349
321
  verbose?: boolean;
322
+ timeoutMs?: number;
350
323
  }
351
324
  ): Promise<void> {
352
325
  if (this.runs.get(runId) === undefined) {
@@ -361,6 +334,7 @@ export class EvalRunStore {
361
334
  ? {}
362
335
  : { bearerToken: opts.bearerToken }),
363
336
  ...(opts.verbose === undefined ? {} : { verbose: opts.verbose }),
337
+ ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
364
338
  onCaseStart: (id) => {
365
339
  const run = this.runs.get(runId);
366
340
  if (run === undefined) {
@@ -408,7 +382,7 @@ export class EvalRunStore {
408
382
  this.applyCaseResult(runId, result);
409
383
  const run = this.runs.get(runId);
410
384
  if (run !== undefined) {
411
- void this.enqueuePersist(run, this.resolvePersist(opts.config));
385
+ this.persist(run);
412
386
  }
413
387
  },
414
388
  });
@@ -418,7 +392,7 @@ export class EvalRunStore {
418
392
  }
419
393
  run.status = "completed";
420
394
  run.finishedAt = new Date().toISOString();
421
- await this.enqueuePersist(run, this.resolvePersist(opts.config));
395
+ this.persist(run);
422
396
  this.logger(
423
397
  `[agentkit] eval run ${runId}: done — ${run.summary.passed} passed, ${run.summary.failed} failed`
424
398
  );
@@ -430,7 +404,7 @@ export class EvalRunStore {
430
404
  run.status = "failed";
431
405
  run.finishedAt = new Date().toISOString();
432
406
  run.error = error instanceof Error ? error.message : String(error);
433
- await this.enqueuePersist(run, this.resolvePersist(opts.config));
407
+ this.persist(run);
434
408
  this.logger(`[agentkit] eval run ${runId}: failed — ${run.error}`);
435
409
  } finally {
436
410
  if (this.activeRunId === runId) {
@@ -489,6 +463,23 @@ function markInterruptedRun(run: EvalRunSnapshot): void {
489
463
  c.error = c.error ?? INTERRUPTED_CASE_ERROR;
490
464
  }
491
465
  }
466
+ // Reconcile the summary with the repaired case rows so a persisted
467
+ // interrupted batch never claims pre-interruption pass counts.
468
+ let passed = 0;
469
+ let failed = 0;
470
+ for (const c of run.cases) {
471
+ if (c.ok === true) {
472
+ passed += 1;
473
+ } else {
474
+ failed += 1;
475
+ }
476
+ }
477
+ run.summary = {
478
+ passed,
479
+ failed,
480
+ total: run.cases.length,
481
+ done: run.cases.length,
482
+ };
492
483
  }
493
484
 
494
485
  function snapshotListConfig(