@cursor/july 0.1.12 → 0.1.15
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.
- package/dist/bin/agent-serve.js +32 -9
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +11 -4
- package/dist/channels/github/cursor-account.d.ts +21 -1
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +29 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +1 -1
- package/dist/channels/slack/api.d.ts +9 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +40 -7
- package/dist/channels/slack/eval-directive.d.ts +3 -0
- package/dist/channels/slack/eval-directive.d.ts.map +1 -1
- package/dist/channels/slack/eval-directive.js +20 -1
- package/dist/channels/slack/post-update-delivery.d.ts +85 -0
- package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.Dh_JkJjI.js → app.DpdGZTLe.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D2d8CfFS.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CptQNLkR.js → VPLocalSearchBox.IB-0QqIT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DWflH-LQ.js → theme.-fYBoNtR.js} +2 -2
- package/dist/docs/assets/{evals.md.DAgEc_hL.js → evals.md.DYOjkRCX.js} +10 -4
- package/dist/docs/assets/{evals.md.DAgEc_hL.lean.js → evals.md.DYOjkRCX.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.B74NrVzb.js → reference_cli.md.DnYfr5V2.js} +4 -1
- package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.js → reference_http-api.md.BncLd3PZ.js} +2 -2
- package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.lean.js → reference_http-api.md.BncLd3PZ.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +13 -7
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +8 -5
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +5 -5
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/evals.d.ts +49 -2
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +53 -16
- package/dist/internal/cli-ax.d.ts +4 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +166 -33
- package/dist/internal/cli-deploy.d.ts +8 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +92 -12
- package/dist/internal/cli-mcp-oauth.d.ts +14 -2
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +95 -2
- package/dist/internal/cursor/backend-client.d.ts +34 -0
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +73 -9
- package/dist/internal/cursor/credentials.d.ts +7 -1
- package/dist/internal/cursor/credentials.d.ts.map +1 -1
- package/dist/internal/cursor/credentials.js +9 -3
- package/dist/internal/cursor-account-mcp-auth.d.ts +55 -0
- package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -0
- package/dist/internal/cursor-account-mcp-auth.js +175 -0
- package/dist/internal/deploy-client.d.ts +6 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +13 -0
- package/dist/internal/eval-run-store.d.ts +15 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +94 -8
- package/dist/internal/eval-runner.d.ts +14 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +111 -22
- package/dist/internal/evals-client.d.ts +9 -1
- package/dist/internal/evals-client.d.ts.map +1 -1
- package/dist/internal/evals-client.js +44 -1
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +74 -7
- package/dist/internal/map-pool.d.ts +6 -1
- package/dist/internal/map-pool.d.ts.map +1 -1
- package/dist/internal/map-pool.js +8 -1
- package/dist/internal/mcp-host.d.ts +13 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +68 -13
- package/dist/internal/persistence-coordinator.d.ts +127 -0
- package/dist/internal/persistence-coordinator.d.ts.map +1 -0
- package/dist/internal/run-client.d.ts +5 -1
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +26 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +57 -4
- package/dist/internal/session-engine.d.ts +13 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +49 -0
- package/dist/internal/storage-coordinator.d.ts +7 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +29 -4
- package/dist/persistence.d.ts +184 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/playground/assets/{index-1dzluIcW.js → index-BVMOoiek.js} +44 -44
- package/dist/playground/assets/index-DYmP4b--.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/evals.md +46 -11
- package/docs/reference/cli.md +3 -0
- package/docs/reference/http-api.md +2 -1
- package/package.json +1 -1
- package/skills/evals/SKILL.md +10 -0
- package/src/bin/agent-serve.ts +38 -8
- package/src/channels/github/api.ts +12 -1
- package/src/channels/github/cursor-account.ts +51 -1
- package/src/channels/github/github-channel.ts +4 -1
- package/src/channels/slack/api.ts +57 -8
- package/src/channels/slack/eval-directive.ts +24 -1
- package/src/evals.ts +112 -25
- package/src/internal/cli-ax.ts +195 -16
- package/src/internal/cli-deploy.ts +140 -13
- package/src/internal/cli-mcp-oauth.ts +122 -3
- package/src/internal/cursor/backend-client.ts +104 -1
- package/src/internal/cursor/credentials.ts +10 -2
- package/src/internal/cursor-account-mcp-auth.ts +241 -0
- package/src/internal/deploy-client.ts +16 -0
- package/src/internal/eval-run-store.ts +97 -6
- package/src/internal/eval-runner.ts +129 -18
- package/src/internal/evals-client.ts +82 -0
- package/src/internal/http-channel.ts +87 -6
- package/src/internal/map-pool.ts +8 -1
- package/src/internal/mcp-host.ts +74 -16
- package/src/internal/run-client.ts +32 -1
- package/src/internal/server.ts +69 -2
- package/src/internal/session-engine.ts +58 -0
- package/src/internal/storage-coordinator.ts +32 -6
- package/src/types.ts +10 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/index-kZjeJs1v.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.B74NrVzb.lean.js → reference_cli.md.DnYfr5V2.lean.js} +0 -0
|
@@ -63,6 +63,8 @@ export interface EvalRunResult {
|
|
|
63
63
|
}>;
|
|
64
64
|
error?: string;
|
|
65
65
|
logs: string[];
|
|
66
|
+
/** Structured scores from {@link import("../evals.js").EvalTestContext.metric}. */
|
|
67
|
+
metrics: Record<string, string | number>;
|
|
66
68
|
durationMs: number;
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -95,6 +97,8 @@ export interface RunEvalsOptions extends EvalSelectionOptions {
|
|
|
95
97
|
evals: readonly DiscoveredEval[];
|
|
96
98
|
config: Partial<EvalConfigFile>;
|
|
97
99
|
};
|
|
100
|
+
/** When aborted, stop starting new cases and cancel in-flight turns. */
|
|
101
|
+
signal?: AbortSignal;
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
async function defaultImport(
|
|
@@ -233,24 +237,86 @@ export async function runDiscoveredEvals(
|
|
|
233
237
|
const maxConcurrency = resolveEvalMaxConcurrency(config);
|
|
234
238
|
const defaultTimeoutMs = options.timeoutMs ?? config.timeoutMs ?? 180_000;
|
|
235
239
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
// Iterations of the same authored datapoint share one concurrency slot and
|
|
241
|
+
// run sequentially. maxConcurrency is therefore the max number of distinct
|
|
242
|
+
// datapoints in flight, not datapoints × iterations.
|
|
243
|
+
const groups = groupEvalsForIterationSchedule(selected);
|
|
244
|
+
const nested = await mapPool(
|
|
245
|
+
groups,
|
|
246
|
+
maxConcurrency,
|
|
247
|
+
async (group) => {
|
|
248
|
+
const results: EvalRunResult[] = [];
|
|
249
|
+
for (const discovered of group) {
|
|
250
|
+
if (options.signal?.aborted) {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
if (options.verbose) {
|
|
254
|
+
process.stderr.write(` eval ${discovered.id}…\n`);
|
|
255
|
+
}
|
|
256
|
+
options.onCaseStart?.(discovered.id);
|
|
257
|
+
const result = await runOneEval(discovered, {
|
|
258
|
+
baseUrl: options.baseUrl,
|
|
259
|
+
defaultTimeoutMs,
|
|
260
|
+
bearerToken: options.bearerToken,
|
|
261
|
+
headers: options.headers,
|
|
262
|
+
verbose: Boolean(options.verbose),
|
|
263
|
+
signal: options.signal,
|
|
264
|
+
onEvent: (event) => {
|
|
265
|
+
options.onCaseEvent?.(discovered.id, event);
|
|
266
|
+
options.onEvent?.(event);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
options.onCaseDone?.(result);
|
|
270
|
+
results.push(result);
|
|
271
|
+
}
|
|
272
|
+
return results;
|
|
273
|
+
},
|
|
274
|
+
{ signal: options.signal }
|
|
275
|
+
);
|
|
276
|
+
return nested.flat();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Schedule key for {@link groupEvalsForIterationSchedule}: iteration siblings
|
|
281
|
+
* (`weather/nyc/1`…`/3`) share a key; single-iteration ids keep their full id.
|
|
282
|
+
*/
|
|
283
|
+
export function iterationScheduleKey(discovered: DiscoveredEval): string {
|
|
284
|
+
const iterations = discovered.definition.iterations ?? 1;
|
|
285
|
+
const iteration = discovered.definition.iteration ?? 1;
|
|
286
|
+
if (iterations <= 1) {
|
|
287
|
+
return discovered.id;
|
|
288
|
+
}
|
|
289
|
+
const suffix = `/${iteration}`;
|
|
290
|
+
if (discovered.id.endsWith(suffix)) {
|
|
291
|
+
return discovered.id.slice(0, -suffix.length);
|
|
292
|
+
}
|
|
293
|
+
return discovered.id;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Group expanded iteration cases so each authored datapoint's iterations run
|
|
298
|
+
* back-to-back under one {@link mapPool} slot (sorted by iteration index).
|
|
299
|
+
*/
|
|
300
|
+
export function groupEvalsForIterationSchedule(
|
|
301
|
+
evals: readonly DiscoveredEval[]
|
|
302
|
+
): DiscoveredEval[][] {
|
|
303
|
+
const groups = new Map<string, DiscoveredEval[]>();
|
|
304
|
+
const order: string[] = [];
|
|
305
|
+
for (const discovered of evals) {
|
|
306
|
+
const key = iterationScheduleKey(discovered);
|
|
307
|
+
let group = groups.get(key);
|
|
308
|
+
if (group === undefined) {
|
|
309
|
+
group = [];
|
|
310
|
+
groups.set(key, group);
|
|
311
|
+
order.push(key);
|
|
239
312
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
onEvent: (event) => {
|
|
248
|
-
options.onCaseEvent?.(discovered.id, event);
|
|
249
|
-
options.onEvent?.(event);
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
options.onCaseDone?.(result);
|
|
253
|
-
return result;
|
|
313
|
+
group.push(discovered);
|
|
314
|
+
}
|
|
315
|
+
return order.map((key) => {
|
|
316
|
+
const group = groups.get(key)!;
|
|
317
|
+
return group.sort(
|
|
318
|
+
(a, b) => (a.definition.iteration ?? 1) - (b.definition.iteration ?? 1)
|
|
319
|
+
);
|
|
254
320
|
});
|
|
255
321
|
}
|
|
256
322
|
|
|
@@ -274,9 +340,11 @@ async function runOneEval(
|
|
|
274
340
|
headers?: Record<string, string>;
|
|
275
341
|
verbose: boolean;
|
|
276
342
|
onEvent?: (event: SessionEvent) => void;
|
|
343
|
+
signal?: AbortSignal;
|
|
277
344
|
}
|
|
278
345
|
): Promise<EvalRunResult> {
|
|
279
346
|
const logs: string[] = [];
|
|
347
|
+
const metrics: Record<string, string | number> = {};
|
|
280
348
|
const assertions: EvalAssertionResult[] = [];
|
|
281
349
|
const def = discovered.definition;
|
|
282
350
|
const timeoutMs = def.timeoutMs ?? opts.defaultTimeoutMs;
|
|
@@ -288,6 +356,25 @@ async function runOneEval(
|
|
|
288
356
|
const inputs: string[] = [];
|
|
289
357
|
let reply: string | null = null;
|
|
290
358
|
|
|
359
|
+
if (opts.signal?.aborted) {
|
|
360
|
+
return {
|
|
361
|
+
id: discovered.id,
|
|
362
|
+
path: discovered.path,
|
|
363
|
+
...(def.description === undefined
|
|
364
|
+
? {}
|
|
365
|
+
: { description: def.description }),
|
|
366
|
+
ok: false,
|
|
367
|
+
assertions,
|
|
368
|
+
error: "cancelled",
|
|
369
|
+
logs,
|
|
370
|
+
metrics,
|
|
371
|
+
inputs,
|
|
372
|
+
toolCalls: [],
|
|
373
|
+
tools: [],
|
|
374
|
+
durationMs: Date.now() - startedAt,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
291
378
|
const record = (name: string, passed: boolean, detail?: string): void => {
|
|
292
379
|
assertions.push({
|
|
293
380
|
name,
|
|
@@ -321,6 +408,12 @@ async function runOneEval(
|
|
|
321
408
|
get events() {
|
|
322
409
|
return allEvents;
|
|
323
410
|
},
|
|
411
|
+
get iteration() {
|
|
412
|
+
return def.iteration ?? 1;
|
|
413
|
+
},
|
|
414
|
+
get iterations() {
|
|
415
|
+
return def.iterations ?? 1;
|
|
416
|
+
},
|
|
324
417
|
async send(
|
|
325
418
|
message: string,
|
|
326
419
|
options: EvalSendOptions = {}
|
|
@@ -337,7 +430,9 @@ async function runOneEval(
|
|
|
337
430
|
headers: opts.headers,
|
|
338
431
|
workspaceDir: options.workspaceDir,
|
|
339
432
|
workspaceFiles: options.workspaceFiles,
|
|
433
|
+
cloud: options.cloud,
|
|
340
434
|
timeoutMs,
|
|
435
|
+
signal: opts.signal,
|
|
341
436
|
onEvent: opts.onEvent,
|
|
342
437
|
});
|
|
343
438
|
sessionId = result.sessionId;
|
|
@@ -400,6 +495,16 @@ async function runOneEval(
|
|
|
400
495
|
process.stderr.write(` ${message}\n`);
|
|
401
496
|
}
|
|
402
497
|
},
|
|
498
|
+
metric(name: string, value: string | number) {
|
|
499
|
+
const key = name.trim();
|
|
500
|
+
if (key === "") {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
metrics[key] = value;
|
|
504
|
+
if (opts.verbose) {
|
|
505
|
+
process.stderr.write(` metric ${key}=${String(value)}\n`);
|
|
506
|
+
}
|
|
507
|
+
},
|
|
403
508
|
};
|
|
404
509
|
|
|
405
510
|
try {
|
|
@@ -419,6 +524,7 @@ async function runOneEval(
|
|
|
419
524
|
assertions,
|
|
420
525
|
error: message,
|
|
421
526
|
logs,
|
|
527
|
+
metrics,
|
|
422
528
|
inputs,
|
|
423
529
|
toolCalls: toolCallIo(),
|
|
424
530
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
@@ -441,6 +547,7 @@ async function runOneEval(
|
|
|
441
547
|
ok,
|
|
442
548
|
assertions,
|
|
443
549
|
logs,
|
|
550
|
+
metrics,
|
|
444
551
|
inputs,
|
|
445
552
|
toolCalls: toolCallIo(),
|
|
446
553
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
@@ -472,9 +579,13 @@ function matchExpectation(
|
|
|
472
579
|
}
|
|
473
580
|
case "equals": {
|
|
474
581
|
const passed = deepEqual(value, assertion.value);
|
|
582
|
+
// Always attach a short detail so values like recall `40.0% (4/10)`
|
|
583
|
+
// show up on PASS lines / playground, not only on failure.
|
|
584
|
+
const rendered =
|
|
585
|
+
typeof value === "string" ? value : JSON.stringify(value);
|
|
475
586
|
return {
|
|
476
587
|
passed,
|
|
477
|
-
|
|
588
|
+
detail: passed ? rendered : `got ${rendered}`,
|
|
478
589
|
};
|
|
479
590
|
}
|
|
480
591
|
case "satisfies": {
|
|
@@ -17,9 +17,11 @@ const DEFAULT_POLL_MS = 1_000;
|
|
|
17
17
|
export type RemoteEvalErrorCode =
|
|
18
18
|
| "no_matching_evals"
|
|
19
19
|
| "eval_run_in_progress"
|
|
20
|
+
| "eval_run_not_running"
|
|
20
21
|
| "unknown_eval_run"
|
|
21
22
|
| "start_failed"
|
|
22
23
|
| "poll_failed"
|
|
24
|
+
| "cancel_failed"
|
|
23
25
|
| "aborted";
|
|
24
26
|
|
|
25
27
|
export class RemoteEvalError extends Error {
|
|
@@ -269,6 +271,85 @@ export async function startRemoteEvalRun(args: {
|
|
|
269
271
|
return json as EvalRunSnapshot;
|
|
270
272
|
}
|
|
271
273
|
|
|
274
|
+
/** Cancel a running batch via `POST /v1/dev/evals/runs/:runId/cancel`. */
|
|
275
|
+
export async function cancelRemoteEvalRun(args: {
|
|
276
|
+
baseUrl: string;
|
|
277
|
+
runId: string;
|
|
278
|
+
auth?: RequestAuth;
|
|
279
|
+
fetchImpl?: typeof fetch;
|
|
280
|
+
signal?: AbortSignal;
|
|
281
|
+
}): Promise<EvalRunSnapshot> {
|
|
282
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
283
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
284
|
+
let resp: Response;
|
|
285
|
+
try {
|
|
286
|
+
resp = await fetchImpl(
|
|
287
|
+
`${baseUrl}/v1/dev/evals/runs/${encodeURIComponent(args.runId)}/cancel`,
|
|
288
|
+
{
|
|
289
|
+
method: "POST",
|
|
290
|
+
headers: mergeRequestHeaders(args.auth),
|
|
291
|
+
signal: args.signal,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw new RemoteEvalError(
|
|
296
|
+
error instanceof Error ? error.message : "Could not cancel eval run.",
|
|
297
|
+
undefined,
|
|
298
|
+
"cancel_failed"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const json = (await resp.json().catch(() => ({}))) as {
|
|
303
|
+
ok?: boolean;
|
|
304
|
+
error?: string;
|
|
305
|
+
message?: string;
|
|
306
|
+
status?: string;
|
|
307
|
+
runId?: string;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
if (resp.status === 404 || json.error === "unknown_eval_run") {
|
|
311
|
+
throw new RemoteEvalError(
|
|
312
|
+
`Unknown eval run ${args.runId}.`,
|
|
313
|
+
404,
|
|
314
|
+
"unknown_eval_run"
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (resp.status === 409 || json.error === "eval_run_not_running") {
|
|
318
|
+
const status =
|
|
319
|
+
typeof json.status === "string" && json.status !== ""
|
|
320
|
+
? json.status
|
|
321
|
+
: "unknown";
|
|
322
|
+
throw new RemoteEvalError(
|
|
323
|
+
typeof json.message === "string" && json.message !== ""
|
|
324
|
+
? json.message
|
|
325
|
+
: `Eval run ${args.runId} is not running (status: ${status}).`,
|
|
326
|
+
409,
|
|
327
|
+
"eval_run_not_running"
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (!resp.ok) {
|
|
331
|
+
const detail =
|
|
332
|
+
typeof json.message === "string" && json.message !== ""
|
|
333
|
+
? json.message
|
|
334
|
+
: typeof json.error === "string" && json.error !== ""
|
|
335
|
+
? json.error
|
|
336
|
+
: `HTTP ${resp.status}`;
|
|
337
|
+
throw new RemoteEvalError(
|
|
338
|
+
`Could not cancel eval run: ${detail}`,
|
|
339
|
+
resp.status,
|
|
340
|
+
"cancel_failed"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if (typeof json.runId !== "string" || json.runId === "") {
|
|
344
|
+
throw new RemoteEvalError(
|
|
345
|
+
"Eval cancel response missing runId.",
|
|
346
|
+
undefined,
|
|
347
|
+
"cancel_failed"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return json as EvalRunSnapshot;
|
|
351
|
+
}
|
|
352
|
+
|
|
272
353
|
/** One-shot `GET /v1/dev/evals/runs/:runId` (no polling). */
|
|
273
354
|
export async function getRemoteEvalRun(args: {
|
|
274
355
|
baseUrl: string;
|
|
@@ -408,6 +489,7 @@ export function snapshotToEvalRunResults(
|
|
|
408
489
|
...(c.toolCalls === undefined ? {} : { toolCalls: c.toolCalls }),
|
|
409
490
|
...(c.error === undefined ? {} : { error: c.error }),
|
|
410
491
|
logs: c.logs ?? [],
|
|
492
|
+
metrics: c.metrics ?? {},
|
|
411
493
|
durationMs: c.durationMs ?? 0,
|
|
412
494
|
}));
|
|
413
495
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
|
+
AgentCloudOptions,
|
|
15
16
|
AuthContext,
|
|
16
17
|
AuthPolicy,
|
|
17
18
|
HttpChannelDefinition,
|
|
@@ -100,12 +101,16 @@ export function buildHttpChannelRoutes(args: {
|
|
|
100
101
|
}
|
|
101
102
|
const message = await transformMessage(body.message, ctx.auth);
|
|
102
103
|
const workspaceFiles = readWorkspaceFiles(body.workspaceFiles);
|
|
104
|
+
// Per-session cloud attach (create only). Eval harness uses this to pin
|
|
105
|
+
// fixture repos without putting them on the agent's default cloud.repos.
|
|
106
|
+
const cloud = readCloudOptions(body.cloud);
|
|
103
107
|
const session = await engine.send(HTTP_CHANNEL_ID, message, {
|
|
104
108
|
auth: ctx.auth,
|
|
105
109
|
...(body.state === undefined ? {} : { state: body.state as JsonValue }),
|
|
106
110
|
...(typeof body.title === "string" ? { title: body.title } : {}),
|
|
107
111
|
...(body.purpose === "eval" ? { purpose: "eval" as const } : {}),
|
|
108
112
|
...(workspaceFiles === undefined ? {} : { workspaceFiles }),
|
|
113
|
+
...(cloud === undefined ? {} : { cloud }),
|
|
109
114
|
});
|
|
110
115
|
return jsonResponse(
|
|
111
116
|
{
|
|
@@ -129,7 +134,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
129
134
|
auth,
|
|
130
135
|
handler: async (request, ctx) => {
|
|
131
136
|
const sessionId = ctx.params.sessionId ?? "";
|
|
132
|
-
const record = await engine.
|
|
137
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
133
138
|
if (record === undefined) {
|
|
134
139
|
return errorResponse(
|
|
135
140
|
404,
|
|
@@ -230,7 +235,9 @@ export function buildHttpChannelRoutes(args: {
|
|
|
230
235
|
// custom channels or schedules are observable here too when the caller
|
|
231
236
|
// matches the session's stored principal (or `--dev` relax). Follow-ups
|
|
232
237
|
// use the same ownership gate plus the session's continuation token.
|
|
233
|
-
|
|
238
|
+
// Lazy-restore by id so playground Open-trace works after redeploy
|
|
239
|
+
// when the session is durable but not in the local state root yet.
|
|
240
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
234
241
|
if (record === undefined) {
|
|
235
242
|
return errorResponse(
|
|
236
243
|
404,
|
|
@@ -276,7 +283,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
276
283
|
auth,
|
|
277
284
|
handler: async (request, ctx) => {
|
|
278
285
|
const sessionId = ctx.params.sessionId ?? "";
|
|
279
|
-
const record = await engine.
|
|
286
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
280
287
|
if (record === undefined) {
|
|
281
288
|
return errorResponse(
|
|
282
289
|
404,
|
|
@@ -323,7 +330,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
323
330
|
auth,
|
|
324
331
|
handler: async (_request, ctx) => {
|
|
325
332
|
const sessionId = ctx.params.sessionId ?? "";
|
|
326
|
-
const record = await engine.
|
|
333
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
327
334
|
if (record === undefined) {
|
|
328
335
|
return errorResponse(
|
|
329
336
|
404,
|
|
@@ -351,7 +358,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
351
358
|
handler: async (request, ctx) => {
|
|
352
359
|
const sessionId = ctx.params.sessionId ?? "";
|
|
353
360
|
const callId = ctx.params.callId ?? "";
|
|
354
|
-
const record = await engine.
|
|
361
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
355
362
|
if (record === undefined) {
|
|
356
363
|
return errorResponse(
|
|
357
364
|
404,
|
|
@@ -420,7 +427,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
420
427
|
auth,
|
|
421
428
|
handler: async (_request, ctx) => {
|
|
422
429
|
const sessionId = ctx.params.sessionId ?? "";
|
|
423
|
-
const record = await engine.
|
|
430
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
424
431
|
if (record === undefined) {
|
|
425
432
|
return errorResponse(
|
|
426
433
|
404,
|
|
@@ -527,6 +534,80 @@ function readWorkspaceFiles(
|
|
|
527
534
|
return Object.keys(files).length === 0 ? undefined : files;
|
|
528
535
|
}
|
|
529
536
|
|
|
537
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
538
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Best-effort parse of {@link AgentCloudOptions} from a JSON body field. */
|
|
542
|
+
function readCloudOptions(value: unknown): AgentCloudOptions | undefined {
|
|
543
|
+
if (!isRecord(value)) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const cloud: AgentCloudOptions = {};
|
|
547
|
+
|
|
548
|
+
if (isRecord(value.env) && typeof value.env.type === "string") {
|
|
549
|
+
const name =
|
|
550
|
+
typeof value.env.name === "string" ? value.env.name : undefined;
|
|
551
|
+
if (value.env.type === "cloud") {
|
|
552
|
+
cloud.env =
|
|
553
|
+
name === undefined ? { type: "cloud" } : { type: "cloud", name };
|
|
554
|
+
} else if (value.env.type === "pool") {
|
|
555
|
+
cloud.env =
|
|
556
|
+
name === undefined ? { type: "pool" } : { type: "pool", name };
|
|
557
|
+
} else if (value.env.type === "machine") {
|
|
558
|
+
cloud.env =
|
|
559
|
+
name === undefined ? { type: "machine" } : { type: "machine", name };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (Array.isArray(value.repos)) {
|
|
564
|
+
const repos: NonNullable<AgentCloudOptions["repos"]> = [];
|
|
565
|
+
for (const entry of value.repos) {
|
|
566
|
+
if (
|
|
567
|
+
!isRecord(entry) ||
|
|
568
|
+
typeof entry.url !== "string" ||
|
|
569
|
+
entry.url === ""
|
|
570
|
+
) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
repos.push({
|
|
574
|
+
url: entry.url,
|
|
575
|
+
...(typeof entry.startingRef === "string"
|
|
576
|
+
? { startingRef: entry.startingRef }
|
|
577
|
+
: {}),
|
|
578
|
+
...(typeof entry.prUrl === "string" ? { prUrl: entry.prUrl } : {}),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (repos.length > 0) {
|
|
582
|
+
cloud.repos = repos;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (typeof value.workOnCurrentBranch === "boolean") {
|
|
587
|
+
cloud.workOnCurrentBranch = value.workOnCurrentBranch;
|
|
588
|
+
}
|
|
589
|
+
if (typeof value.autoCreatePR === "boolean") {
|
|
590
|
+
cloud.autoCreatePR = value.autoCreatePR;
|
|
591
|
+
}
|
|
592
|
+
if (typeof value.skipReviewerRequest === "boolean") {
|
|
593
|
+
cloud.skipReviewerRequest = value.skipReviewerRequest;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (isRecord(value.envVars)) {
|
|
597
|
+
const envVars: Record<string, string> = {};
|
|
598
|
+
for (const [key, envValue] of Object.entries(value.envVars)) {
|
|
599
|
+
if (typeof key === "string" && typeof envValue === "string") {
|
|
600
|
+
envVars[key] = envValue;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (Object.keys(envVars).length > 0) {
|
|
604
|
+
cloud.envVars = envVars;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return Object.keys(cloud).length === 0 ? undefined : cloud;
|
|
609
|
+
}
|
|
610
|
+
|
|
530
611
|
export async function readJsonObject(
|
|
531
612
|
request: Request
|
|
532
613
|
): Promise<Record<string, unknown> | undefined> {
|
package/src/internal/map-pool.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run `fn` over `items` with at most `concurrency` in flight; preserve order
|
|
3
3
|
* of results (and of side-effect completion relative to index assignment).
|
|
4
|
+
*
|
|
5
|
+
* When `signal` aborts, workers stop claiming new items; in-flight `fn` calls
|
|
6
|
+
* are responsible for honoring the same signal.
|
|
4
7
|
*/
|
|
5
8
|
export async function mapPool<T, R>(
|
|
6
9
|
items: readonly T[],
|
|
7
10
|
concurrency: number,
|
|
8
|
-
fn: (item: T, index: number) => Promise<R
|
|
11
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
12
|
+
options?: { signal?: AbortSignal }
|
|
9
13
|
): Promise<R[]> {
|
|
10
14
|
const results = new Array<R>(items.length);
|
|
11
15
|
let next = 0;
|
|
12
16
|
async function worker(): Promise<void> {
|
|
13
17
|
for (;;) {
|
|
18
|
+
if (options?.signal?.aborted) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
14
21
|
const index = next;
|
|
15
22
|
next += 1;
|
|
16
23
|
if (index >= items.length) {
|
package/src/internal/mcp-host.ts
CHANGED
|
@@ -52,17 +52,18 @@ export class McpHost implements HostMcpRegistry {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async listTools(connectionName: string): Promise<HostMcpToolInfo[]> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
return this.withClient(connectionName, async (client) => {
|
|
56
|
+
const listed = await client.listTools();
|
|
57
|
+
return listed.tools.map((tool) => ({
|
|
58
|
+
name: tool.name,
|
|
59
|
+
...(tool.description === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { description: tool.description }),
|
|
62
|
+
...(tool.inputSchema === undefined
|
|
63
|
+
? {}
|
|
64
|
+
: { inputSchema: tool.inputSchema as JsonObject }),
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
async callTool(
|
|
@@ -70,12 +71,13 @@ export class McpHost implements HostMcpRegistry {
|
|
|
70
71
|
toolName: string,
|
|
71
72
|
args: Record<string, unknown> = {}
|
|
72
73
|
): Promise<HostMcpCallResult> {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
return this.withClient(connectionName, async (client) => {
|
|
75
|
+
const result = await client.callTool({
|
|
76
|
+
name: toolName,
|
|
77
|
+
arguments: args,
|
|
78
|
+
});
|
|
79
|
+
return normalizeCallResult(result);
|
|
77
80
|
});
|
|
78
|
-
return normalizeCallResult(result);
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
async close(): Promise<void> {
|
|
@@ -90,6 +92,32 @@ export class McpHost implements HostMcpRegistry {
|
|
|
90
92
|
);
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Run against a cached host MCP client, reconnecting once when the remote
|
|
97
|
+
* Streamable HTTP session is gone (`session_terminated` / HTTP 404).
|
|
98
|
+
*
|
|
99
|
+
* Anytool (and the MCP spec) return 404 for unknown/terminated
|
|
100
|
+
* `Mcp-Session-Id`s so clients can re-initialize. The SDK transport keeps
|
|
101
|
+
* the dead session id, so without this invalidation every later host.mcp
|
|
102
|
+
* call fails until the serve process restarts.
|
|
103
|
+
*/
|
|
104
|
+
private async withClient<T>(
|
|
105
|
+
name: string,
|
|
106
|
+
run: (client: Client) => Promise<T>
|
|
107
|
+
): Promise<T> {
|
|
108
|
+
const opened = await this.getClient(name);
|
|
109
|
+
try {
|
|
110
|
+
return await run(opened.client);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!isStaleMcpSessionError(error)) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
await this.invalidateClient(name, opened);
|
|
116
|
+
const reopened = await this.getClient(name);
|
|
117
|
+
return await run(reopened.client);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
private getClient(name: string): Promise<OpenedConnection> {
|
|
94
122
|
let pending = this.clients.get(name);
|
|
95
123
|
if (pending === undefined) {
|
|
@@ -102,6 +130,23 @@ export class McpHost implements HostMcpRegistry {
|
|
|
102
130
|
return pending;
|
|
103
131
|
}
|
|
104
132
|
|
|
133
|
+
private async invalidateClient(
|
|
134
|
+
name: string,
|
|
135
|
+
opened: OpenedConnection
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
const pending = this.clients.get(name);
|
|
138
|
+
if (pending !== undefined) {
|
|
139
|
+
try {
|
|
140
|
+
if ((await pending) === opened) {
|
|
141
|
+
this.clients.delete(name);
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
this.clients.delete(name);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
await opened.close();
|
|
148
|
+
}
|
|
149
|
+
|
|
105
150
|
private async connect(name: string): Promise<OpenedConnection> {
|
|
106
151
|
const connection = this.byName.get(name);
|
|
107
152
|
if (connection === undefined) {
|
|
@@ -114,6 +159,19 @@ export class McpHost implements HostMcpRegistry {
|
|
|
114
159
|
}
|
|
115
160
|
}
|
|
116
161
|
|
|
162
|
+
/** Exported for unit tests. */
|
|
163
|
+
export function isStaleMcpSessionError(error: unknown): boolean {
|
|
164
|
+
if (!(error instanceof Error)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (error.message.includes("session_terminated")) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// StreamableHTTPError from the MCP SDK carries the HTTP status as `.code`.
|
|
171
|
+
const code = (error as { code?: unknown }).code;
|
|
172
|
+
return code === 404;
|
|
173
|
+
}
|
|
174
|
+
|
|
117
175
|
async function openConnection(
|
|
118
176
|
connection: DiscoveredConnection
|
|
119
177
|
): Promise<OpenedConnection> {
|