@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
@@ -0,0 +1,431 @@
1
+ /**
2
+ * HTTP client for playground eval batches (`/v1/dev/evals/runs`).
3
+ *
4
+ * Used by `agentkit eval --prod` (or `--url`) so the batch runs on the serve
5
+ * process and is mirrored to `defineStorage` when an `evals` table is
6
+ * configured. Without those flags, eval keeps the in-process ephemeral path
7
+ * in `cli-ax.ts`.
8
+ */
9
+
10
+ import type { EvalRunSnapshot } from "../evals.js";
11
+ import { matchesEvalIdFilter } from "../evals.js";
12
+ import type { EvalRunResult } from "./eval-runner.js";
13
+ import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
14
+
15
+ const DEFAULT_POLL_MS = 1_000;
16
+
17
+ export type RemoteEvalErrorCode =
18
+ | "no_matching_evals"
19
+ | "eval_run_in_progress"
20
+ | "unknown_eval_run"
21
+ | "start_failed"
22
+ | "poll_failed"
23
+ | "aborted";
24
+
25
+ export class RemoteEvalError extends Error {
26
+ constructor(
27
+ message: string,
28
+ readonly status?: number,
29
+ readonly code?: RemoteEvalErrorCode,
30
+ readonly activeRunId?: string
31
+ ) {
32
+ super(message);
33
+ this.name = "RemoteEvalError";
34
+ }
35
+ }
36
+
37
+ export interface RemoteEvalListItem {
38
+ id: string;
39
+ fileId: string;
40
+ description?: string;
41
+ tags?: string[];
42
+ }
43
+
44
+ export interface RunRemoteEvalsOptions extends RequestAuth {
45
+ baseUrl: string;
46
+ filterIds?: string[];
47
+ tags?: string[];
48
+ /** Per-case timeout override forwarded to the serve process. */
49
+ timeoutMs?: number;
50
+ /** Ask the serve harness to emit verbose case logs (server-side). */
51
+ verbose?: boolean;
52
+ fetchImpl?: typeof fetch;
53
+ /** Poll interval while the batch is `running`. */
54
+ pollMs?: number;
55
+ /** Abort the wait (not the remote batch). */
56
+ signal?: AbortSignal;
57
+ /** Called each time the polled snapshot changes. */
58
+ onSnapshot?: (snap: EvalRunSnapshot) => void;
59
+ }
60
+
61
+ /**
62
+ * Start a batch on the target serve via `POST /v1/dev/evals/runs`, poll until
63
+ * terminal, and return CLI-shaped case results.
64
+ */
65
+ export async function runRemoteEvals(
66
+ options: RunRemoteEvalsOptions
67
+ ): Promise<{ snapshot: EvalRunSnapshot; results: EvalRunResult[] }> {
68
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
69
+ const fetchImpl = options.fetchImpl ?? fetch;
70
+ const auth: RequestAuth = {
71
+ bearerToken: options.bearerToken,
72
+ headers: options.headers,
73
+ };
74
+
75
+ const started = await startRemoteEvalRun({
76
+ baseUrl,
77
+ auth,
78
+ fetchImpl,
79
+ filterIds: options.filterIds,
80
+ tags: options.tags,
81
+ timeoutMs: options.timeoutMs,
82
+ verbose: options.verbose,
83
+ signal: options.signal,
84
+ });
85
+ options.onSnapshot?.(started);
86
+
87
+ const snapshot = await pollRemoteEvalRun({
88
+ baseUrl,
89
+ runId: started.runId,
90
+ auth,
91
+ fetchImpl,
92
+ pollMs: options.pollMs ?? DEFAULT_POLL_MS,
93
+ signal: options.signal,
94
+ onSnapshot: options.onSnapshot,
95
+ });
96
+
97
+ return { snapshot, results: snapshotToEvalRunResults(snapshot) };
98
+ }
99
+
100
+ /** List datapoints from `GET /v1/dev/evals` (same surface a remote run uses). */
101
+ export async function listRemoteEvals(args: {
102
+ baseUrl: string;
103
+ auth?: RequestAuth;
104
+ fetchImpl?: typeof fetch;
105
+ filterIds?: string[];
106
+ tags?: string[];
107
+ signal?: AbortSignal;
108
+ }): Promise<RemoteEvalListItem[]> {
109
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
110
+ const fetchImpl = args.fetchImpl ?? fetch;
111
+ let resp: Response;
112
+ try {
113
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals`, {
114
+ headers: mergeRequestHeaders(args.auth),
115
+ signal: args.signal,
116
+ });
117
+ } catch (error) {
118
+ throw new RemoteEvalError(
119
+ error instanceof Error ? error.message : "Could not list evals.",
120
+ undefined,
121
+ "start_failed"
122
+ );
123
+ }
124
+ if (!resp.ok) {
125
+ throw new RemoteEvalError(
126
+ `Could not list evals (HTTP ${resp.status}).`,
127
+ resp.status,
128
+ "start_failed"
129
+ );
130
+ }
131
+ const body = (await resp.json()) as {
132
+ evals?: Array<{
133
+ id?: unknown;
134
+ fileId?: unknown;
135
+ description?: unknown;
136
+ tags?: unknown;
137
+ }>;
138
+ };
139
+ const listed: RemoteEvalListItem[] = [];
140
+ for (const row of body.evals ?? []) {
141
+ if (typeof row.id !== "string" || row.id === "") {
142
+ continue;
143
+ }
144
+ if (typeof row.fileId !== "string" || row.fileId === "") {
145
+ continue;
146
+ }
147
+ const tags = Array.isArray(row.tags)
148
+ ? row.tags.filter((t): t is string => typeof t === "string")
149
+ : undefined;
150
+ listed.push({
151
+ id: row.id,
152
+ fileId: row.fileId,
153
+ ...(typeof row.description === "string"
154
+ ? { description: row.description }
155
+ : {}),
156
+ ...(tags === undefined || tags.length === 0 ? {} : { tags }),
157
+ });
158
+ }
159
+
160
+ let selected = listed;
161
+ if (args.filterIds !== undefined && args.filterIds.length > 0) {
162
+ selected = selected.filter((e) =>
163
+ args.filterIds!.some((f) => matchesEvalIdFilter(e.id, f))
164
+ );
165
+ }
166
+ if (args.tags !== undefined && args.tags.length > 0) {
167
+ selected = selected.filter((e) =>
168
+ (e.tags ?? []).some((t) => args.tags!.includes(t))
169
+ );
170
+ }
171
+ return selected;
172
+ }
173
+
174
+ export async function startRemoteEvalRun(args: {
175
+ baseUrl: string;
176
+ auth?: RequestAuth;
177
+ fetchImpl?: typeof fetch;
178
+ filterIds?: string[];
179
+ tags?: string[];
180
+ timeoutMs?: number;
181
+ verbose?: boolean;
182
+ signal?: AbortSignal;
183
+ }): Promise<EvalRunSnapshot> {
184
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
185
+ const fetchImpl = args.fetchImpl ?? fetch;
186
+ const body: {
187
+ filterIds?: string[];
188
+ tags?: string[];
189
+ timeoutMs?: number;
190
+ verbose?: boolean;
191
+ } = {};
192
+ if (args.filterIds !== undefined && args.filterIds.length > 0) {
193
+ body.filterIds = args.filterIds;
194
+ }
195
+ if (args.tags !== undefined && args.tags.length > 0) {
196
+ body.tags = args.tags;
197
+ }
198
+ if (args.timeoutMs !== undefined) {
199
+ body.timeoutMs = args.timeoutMs;
200
+ }
201
+ if (args.verbose === true) {
202
+ body.verbose = true;
203
+ }
204
+
205
+ let resp: Response;
206
+ try {
207
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
208
+ method: "POST",
209
+ headers: {
210
+ "content-type": "application/json",
211
+ ...mergeRequestHeaders(args.auth),
212
+ },
213
+ body: JSON.stringify(body),
214
+ signal: args.signal,
215
+ });
216
+ } catch (error) {
217
+ throw new RemoteEvalError(
218
+ error instanceof Error ? error.message : "Could not start eval run.",
219
+ undefined,
220
+ "start_failed"
221
+ );
222
+ }
223
+
224
+ const json = (await resp.json().catch(() => ({}))) as {
225
+ ok?: boolean;
226
+ error?: string;
227
+ message?: string;
228
+ activeRunId?: string;
229
+ runId?: string;
230
+ };
231
+
232
+ if (json.error === "no_matching_evals") {
233
+ throw new RemoteEvalError(
234
+ "No matching evals found.",
235
+ 404,
236
+ "no_matching_evals"
237
+ );
238
+ }
239
+ if (resp.status === 409 || json.error === "eval_run_in_progress") {
240
+ throw new RemoteEvalError(
241
+ json.activeRunId !== undefined && json.activeRunId !== ""
242
+ ? `An eval run is already in progress (${json.activeRunId}).`
243
+ : "An eval run is already in progress.",
244
+ 409,
245
+ "eval_run_in_progress",
246
+ typeof json.activeRunId === "string" ? json.activeRunId : undefined
247
+ );
248
+ }
249
+ if (!resp.ok) {
250
+ const detail =
251
+ typeof json.message === "string" && json.message !== ""
252
+ ? json.message
253
+ : typeof json.error === "string" && json.error !== ""
254
+ ? json.error
255
+ : `HTTP ${resp.status}`;
256
+ throw new RemoteEvalError(
257
+ `Could not start eval run: ${detail}`,
258
+ resp.status,
259
+ "start_failed"
260
+ );
261
+ }
262
+ if (typeof json.runId !== "string" || json.runId === "") {
263
+ throw new RemoteEvalError(
264
+ "Eval start response missing runId.",
265
+ undefined,
266
+ "start_failed"
267
+ );
268
+ }
269
+ return json as EvalRunSnapshot;
270
+ }
271
+
272
+ /** One-shot `GET /v1/dev/evals/runs/:runId` (no polling). */
273
+ export async function getRemoteEvalRun(args: {
274
+ baseUrl: string;
275
+ runId: string;
276
+ auth?: RequestAuth;
277
+ fetchImpl?: typeof fetch;
278
+ signal?: AbortSignal;
279
+ }): Promise<EvalRunSnapshot> {
280
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
281
+ const fetchImpl = args.fetchImpl ?? fetch;
282
+ let resp: Response;
283
+ try {
284
+ resp = await fetchImpl(
285
+ `${baseUrl}/v1/dev/evals/runs/${encodeURIComponent(args.runId)}`,
286
+ {
287
+ headers: mergeRequestHeaders(args.auth),
288
+ signal: args.signal,
289
+ }
290
+ );
291
+ } catch (error) {
292
+ throw new RemoteEvalError(
293
+ error instanceof Error ? error.message : "Could not fetch eval run.",
294
+ undefined,
295
+ "poll_failed"
296
+ );
297
+ }
298
+
299
+ if (resp.status === 404) {
300
+ throw new RemoteEvalError(
301
+ `Unknown eval run ${args.runId}.`,
302
+ 404,
303
+ "unknown_eval_run"
304
+ );
305
+ }
306
+ if (!resp.ok) {
307
+ throw new RemoteEvalError(
308
+ `Could not fetch eval run (HTTP ${resp.status}).`,
309
+ resp.status,
310
+ "poll_failed"
311
+ );
312
+ }
313
+ return (await resp.json()) as EvalRunSnapshot;
314
+ }
315
+
316
+ /** Recent batches from `GET /v1/dev/evals/runs`. */
317
+ export async function listRemoteEvalRuns(args: {
318
+ baseUrl: string;
319
+ auth?: RequestAuth;
320
+ fetchImpl?: typeof fetch;
321
+ signal?: AbortSignal;
322
+ }): Promise<{ runs: EvalRunSnapshot[]; activeRunId?: string }> {
323
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
324
+ const fetchImpl = args.fetchImpl ?? fetch;
325
+ let resp: Response;
326
+ try {
327
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
328
+ headers: mergeRequestHeaders(args.auth),
329
+ signal: args.signal,
330
+ });
331
+ } catch (error) {
332
+ throw new RemoteEvalError(
333
+ error instanceof Error ? error.message : "Could not list eval runs.",
334
+ undefined,
335
+ "poll_failed"
336
+ );
337
+ }
338
+ if (!resp.ok) {
339
+ throw new RemoteEvalError(
340
+ `Could not list eval runs (HTTP ${resp.status}).`,
341
+ resp.status,
342
+ "poll_failed"
343
+ );
344
+ }
345
+ const body = (await resp.json()) as {
346
+ runs?: unknown;
347
+ activeRunId?: unknown;
348
+ };
349
+ const runs = Array.isArray(body.runs)
350
+ ? (body.runs.filter(
351
+ (r): r is EvalRunSnapshot =>
352
+ r !== null &&
353
+ typeof r === "object" &&
354
+ typeof (r as EvalRunSnapshot).runId === "string"
355
+ ) as EvalRunSnapshot[])
356
+ : [];
357
+ return {
358
+ runs,
359
+ ...(typeof body.activeRunId === "string" && body.activeRunId !== ""
360
+ ? { activeRunId: body.activeRunId }
361
+ : {}),
362
+ };
363
+ }
364
+
365
+ export async function pollRemoteEvalRun(args: {
366
+ baseUrl: string;
367
+ runId: string;
368
+ auth?: RequestAuth;
369
+ fetchImpl?: typeof fetch;
370
+ pollMs?: number;
371
+ signal?: AbortSignal;
372
+ onSnapshot?: (snap: EvalRunSnapshot) => void;
373
+ }): Promise<EvalRunSnapshot> {
374
+ const pollMs = args.pollMs ?? DEFAULT_POLL_MS;
375
+ let lastJson = "";
376
+
377
+ for (;;) {
378
+ if (args.signal?.aborted === true) {
379
+ throw new RemoteEvalError("Eval wait aborted.", undefined, "aborted");
380
+ }
381
+
382
+ const snap = await getRemoteEvalRun(args);
383
+ const serialized = JSON.stringify(snap);
384
+ if (serialized !== lastJson) {
385
+ lastJson = serialized;
386
+ args.onSnapshot?.(snap);
387
+ }
388
+ if (snap.status !== "running") {
389
+ return snap;
390
+ }
391
+ await sleep(pollMs, args.signal);
392
+ }
393
+ }
394
+
395
+ export function snapshotToEvalRunResults(
396
+ snapshot: EvalRunSnapshot
397
+ ): EvalRunResult[] {
398
+ return snapshot.cases.map((c) => ({
399
+ id: c.id,
400
+ path: c.fileId,
401
+ ...(c.description === undefined ? {} : { description: c.description }),
402
+ ok: c.ok === true,
403
+ assertions: c.assertions ?? [],
404
+ ...(c.sessionId === undefined ? {} : { sessionId: c.sessionId }),
405
+ inputs: c.inputs ?? [],
406
+ ...(c.finalText === undefined ? {} : { finalText: c.finalText }),
407
+ ...(c.tools === undefined ? {} : { tools: c.tools }),
408
+ ...(c.toolCalls === undefined ? {} : { toolCalls: c.toolCalls }),
409
+ ...(c.error === undefined ? {} : { error: c.error }),
410
+ logs: c.logs ?? [],
411
+ durationMs: c.durationMs ?? 0,
412
+ }));
413
+ }
414
+
415
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
416
+ return new Promise((resolve, reject) => {
417
+ if (signal?.aborted === true) {
418
+ reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
419
+ return;
420
+ }
421
+ const timer = setTimeout(() => {
422
+ signal?.removeEventListener("abort", onAbort);
423
+ resolve();
424
+ }, ms);
425
+ const onAbort = (): void => {
426
+ clearTimeout(timer);
427
+ reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
428
+ };
429
+ signal?.addEventListener("abort", onAbort, { once: true });
430
+ });
431
+ }
@@ -441,6 +441,7 @@ Evals:
441
441
  Required: evals/evals.config.ts with defineEvalConfig({ maxConcurrency })
442
442
  (hard-capped at 200 due to model provider request limits)
443
443
  Run: ${CLI_COMMAND_NAME} eval [--json] [evalId...]
444
+ (pass --prod 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 {
@@ -1080,6 +1082,8 @@ function mountAgentRoutes(
1080
1082
  const body = await readJsonObject(request);
1081
1083
  const filterIds = asStringArray(body?.filterIds);
1082
1084
  const tags = asStringArray(body?.tags);
1085
+ const timeoutMs = asPositiveInt(body?.timeoutMs);
1086
+ const verbose = body?.verbose === true;
1083
1087
  const authHeader = request.headers.get("authorization");
1084
1088
  const bearerToken = authHeader?.toLowerCase().startsWith("bearer ")
1085
1089
  ? authHeader.slice("bearer ".length).trim()
@@ -1091,6 +1095,8 @@ function mountAgentRoutes(
1091
1095
  ...(bearerToken === undefined || bearerToken === ""
1092
1096
  ? {}
1093
1097
  : { bearerToken }),
1098
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
1099
+ ...(verbose ? { verbose: true } : {}),
1094
1100
  });
1095
1101
  return jsonResponse(snap, { status: 202 });
1096
1102
  } catch (error) {
@@ -1894,6 +1900,14 @@ function asStringArray(value: unknown): string[] | undefined {
1894
1900
  return out.length === 0 ? undefined : out;
1895
1901
  }
1896
1902
 
1903
+ function asPositiveInt(value: unknown): number | undefined {
1904
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1905
+ return undefined;
1906
+ }
1907
+ const n = Math.floor(value);
1908
+ return n > 0 ? n : undefined;
1909
+ }
1910
+
1897
1911
  async function assertReminderReadable(input: {
1898
1912
  engine: SessionEngine;
1899
1913
  reminder: ReminderInfo;