@cursor/july 0.1.5 → 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.
- package/dist/ab.d.ts +8 -95
- package/dist/ab.d.ts.map +1 -1
- package/dist/ab.js +9 -150
- package/dist/bin/agent-serve.js +14 -6
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.DabPG-io.js → app.DMboGKUD.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.O6eP7QZr.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.jmyr0bU0.js → VPLocalSearchBox.DkEqEnJU.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DysN9-VN.js → theme.ASugdMNm.js} +2 -2
- package/dist/docs/assets/deployment.md.BtfEsc9S.js +55 -0
- package/dist/docs/assets/deployment.md.BtfEsc9S.lean.js +1 -0
- package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.js +10 -0
- package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.lean.js +1 -0
- package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.js +7 -0
- package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.lean.js +1 -0
- package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.js +11 -0
- package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.lean.js +1 -0
- package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.js +8 -0
- package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.lean.js +1 -0
- package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.js +8 -0
- package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.lean.js +1 -0
- package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.js +23 -0
- package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.lean.js +1 -0
- package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.js +15 -0
- package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.lean.js +1 -0
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +2 -0
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +1 -0
- package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.js +11 -0
- package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.lean.js +1 -0
- package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.js +19 -0
- package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.lean.js +1 -0
- package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.js +5 -0
- package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.lean.js +1 -0
- package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.js +24 -0
- package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.lean.js +1 -0
- package/dist/docs/assets/index.md.COiu-1jL.js +20 -0
- package/dist/docs/assets/{index.md.Cylk70gg.lean.js → index.md.COiu-1jL.lean.js} +1 -1
- package/dist/docs/assets/reference_cli.md.D189RBCH.js +60 -0
- package/dist/docs/assets/reference_cli.md.D189RBCH.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +58 -17
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +34 -0
- package/dist/docs/example-agents/benny.html +31 -0
- package/dist/docs/example-agents/bugbot.html +35 -0
- package/dist/docs/example-agents/codebase-wiki.html +32 -0
- package/dist/docs/example-agents/codeowners-review.html +32 -0
- package/dist/docs/example-agents/concierge.html +47 -0
- package/dist/docs/example-agents/fsd.html +39 -0
- package/dist/docs/example-agents/index.html +26 -0
- package/dist/docs/example-agents/knowledge-base.html +35 -0
- package/dist/docs/example-agents/security-reviewer.html +43 -0
- package/dist/docs/example-agents/slack-agent.html +29 -0
- package/dist/docs/example-agents/weather-agent.html +48 -0
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +7 -7
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +52 -30
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/evals.d.ts +4 -61
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +3 -66
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/ab-collector.d.ts +7 -5
- package/dist/internal/ab-collector.d.ts.map +1 -1
- package/dist/internal/ab-collector.js +3 -14
- package/dist/internal/ab-snapshot.d.ts +2 -4
- package/dist/internal/ab-snapshot.d.ts.map +1 -1
- package/dist/internal/cli-ax.d.ts +18 -3
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +80 -42
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/discovery.js +3 -3
- package/dist/internal/eval-run-store.d.ts +33 -30
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +84 -96
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +3 -2
- package/dist/internal/playground-proxy.d.ts +5 -5
- package/dist/internal/playground-proxy.js +3 -3
- package/dist/internal/resolve-prod-target.d.ts +30 -0
- package/dist/internal/resolve-prod-target.d.ts.map +1 -1
- package/dist/internal/resolve-prod-target.js +74 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +5 -3
- package/dist/internal/session-engine.d.ts +1 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +14 -31
- package/dist/internal/storage-coordinator.d.ts +16 -15
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +73 -80
- package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
- package/dist/playground/assets/index-7GJIUFQj.css +1 -0
- package/dist/playground/assets/index-mux9aj5P.js +319 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +51 -10
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +27 -10
- package/docs/README.md +34 -5
- package/docs/deployment.md +352 -149
- package/docs/example-agents/approval-buddy.md +270 -0
- package/docs/example-agents/benny.md +186 -0
- package/docs/example-agents/bugbot.md +231 -0
- package/docs/example-agents/codebase-wiki.md +174 -0
- package/docs/example-agents/codeowners-review.md +195 -0
- package/docs/example-agents/concierge.md +205 -0
- package/docs/example-agents/fsd.md +330 -0
- package/docs/example-agents/index.md +102 -0
- package/docs/example-agents/knowledge-base.md +171 -0
- package/docs/example-agents/security-reviewer.md +296 -0
- package/docs/example-agents/slack-agent.md +146 -0
- package/docs/example-agents/weather-agent.md +302 -0
- package/docs/reference/cli.md +546 -147
- package/package.json +1 -1
- package/src/ab.ts +9 -261
- package/src/bin/agent-serve.ts +14 -6
- package/src/evals.ts +4 -118
- package/src/index.ts +2 -0
- package/src/internal/ab-collector.ts +12 -22
- package/src/internal/ab-snapshot.ts +2 -4
- package/src/internal/cli-ax.ts +101 -45
- package/src/internal/cli-deploy.ts +1 -1
- package/src/internal/discovery.ts +2 -2
- package/src/internal/eval-run-store.ts +86 -100
- package/src/internal/init-project.ts +3 -2
- package/src/internal/playground-proxy.ts +5 -5
- package/src/internal/resolve-prod-target.ts +101 -3
- package/src/internal/server.ts +5 -3
- package/src/internal/session-engine.ts +9 -29
- package/src/internal/storage-coordinator.ts +109 -101
- package/src/storage.ts +79 -14
- package/dist/docs/assets/chunks/@localSearchIndexroot.QwK5BtEH.js +0 -1
- package/dist/docs/assets/deployment.md.DTKwE15Z.js +0 -14
- package/dist/docs/assets/deployment.md.DTKwE15Z.lean.js +0 -1
- package/dist/docs/assets/index.md.Cylk70gg.js +0 -20
- package/dist/docs/assets/reference_cli.md.Bv6pOxcF.js +0 -38
- package/dist/docs/assets/reference_cli.md.Bv6pOxcF.lean.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
- package/dist/playground/assets/index-BEauYlII.css +0 -1
- package/dist/playground/assets/index-BtM0wEGg.js +0 -319
- package/src/internal/json-dir-store.ts +0 -109
package/src/internal/cli-ax.ts
CHANGED
|
@@ -35,7 +35,12 @@ import { offerInstallCursorSkills } from "./install-cursor-skills.js";
|
|
|
35
35
|
import { runLogsTail, sanitizeCustomerError } from "./logs-client.js";
|
|
36
36
|
import { openBrowser } from "./open-browser.js";
|
|
37
37
|
import { startPlaygroundProxy } from "./playground-proxy.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
ProdTargetError,
|
|
40
|
+
resolveProdAliasPlaygroundUrl,
|
|
41
|
+
resolveProdSlug,
|
|
42
|
+
resolveProdTarget,
|
|
43
|
+
} from "./resolve-prod-target.js";
|
|
39
44
|
import { runSession } from "./run-client.js";
|
|
40
45
|
import {
|
|
41
46
|
fetchSessionEvents,
|
|
@@ -92,8 +97,15 @@ export interface AxCliOptions {
|
|
|
92
97
|
* Route chat/resume/run/call/eval/logs/sessions at the team's hosted
|
|
93
98
|
* deployment
|
|
94
99
|
* instead of a local ephemeral server. Mutually exclusive with `--url`.
|
|
100
|
+
* `eval` targets the deployment by default, so there `--prod` is just
|
|
101
|
+
* being explicit.
|
|
95
102
|
*/
|
|
96
103
|
prod?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* `eval`: run against an ephemeral local server instead of the hosted
|
|
106
|
+
* deployment (the eval default). Mutually exclusive with `--url`/`--prod`.
|
|
107
|
+
*/
|
|
108
|
+
local?: boolean;
|
|
97
109
|
/** Cursor team id for `--prod` (defaults to the account's team). */
|
|
98
110
|
team?: string;
|
|
99
111
|
/** Cursor API key override for `--prod` management calls. */
|
|
@@ -654,6 +666,79 @@ export async function cmdTrajectory(options: AxCliOptions): Promise<number> {
|
|
|
654
666
|
return trajectory.ok ? 0 : 1;
|
|
655
667
|
}
|
|
656
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Which surface `eval` should target. Deployment is the default; `--local`
|
|
671
|
+
* opts into an ephemeral local server, `--url` into a running one.
|
|
672
|
+
* Exported for tests.
|
|
673
|
+
*/
|
|
674
|
+
export function resolveEvalTargetMode(
|
|
675
|
+
options: Pick<AxCliOptions, "local" | "prod" | "url">
|
|
676
|
+
): "local" | "url" | "deployment" | { error: string } {
|
|
677
|
+
const flags = [
|
|
678
|
+
options.local === true ? "--local" : undefined,
|
|
679
|
+
options.prod === true ? "--prod" : undefined,
|
|
680
|
+
options.url !== undefined ? "--url" : undefined,
|
|
681
|
+
].filter((flag) => flag !== undefined);
|
|
682
|
+
if (flags.length > 1) {
|
|
683
|
+
return { error: `${flags.join(" and ")} are mutually exclusive` };
|
|
684
|
+
}
|
|
685
|
+
if (options.local === true) {
|
|
686
|
+
return "local";
|
|
687
|
+
}
|
|
688
|
+
if (options.url !== undefined) {
|
|
689
|
+
return "url";
|
|
690
|
+
}
|
|
691
|
+
return "deployment";
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Resolve the eval target; prints a CLI-ready error and returns an exit code on failure. */
|
|
695
|
+
async function resolveEvalTarget(
|
|
696
|
+
options: AxCliOptions
|
|
697
|
+
): Promise<ResolvedAgentTarget | number> {
|
|
698
|
+
const mode = resolveEvalTargetMode(options);
|
|
699
|
+
if (typeof mode === "object") {
|
|
700
|
+
process.stderr.write(`${mode.error}\n`);
|
|
701
|
+
return 2;
|
|
702
|
+
}
|
|
703
|
+
if (mode === "url") {
|
|
704
|
+
return { agentUrl: options.url!.replace(/\/$/, "") };
|
|
705
|
+
}
|
|
706
|
+
if (mode === "local") {
|
|
707
|
+
return startEphemeral(options);
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
const prod = await resolveProdTarget({
|
|
711
|
+
dir: options.dir,
|
|
712
|
+
cwd: options.cwd,
|
|
713
|
+
slug: options.slug,
|
|
714
|
+
team: options.team,
|
|
715
|
+
apiKey: options.apiKey,
|
|
716
|
+
fetchImpl: options.fetchImpl,
|
|
717
|
+
});
|
|
718
|
+
return { agentUrl: prod.agentUrl, headers: prod.headers };
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if (error instanceof ProdTargetError && error.notFound) {
|
|
721
|
+
// Keep the API error text: a 404 can also mean a wrong API host or a
|
|
722
|
+
// disabled feature gate, and the message distinguishes those from a
|
|
723
|
+
// genuinely missing deployment.
|
|
724
|
+
process.stderr.write(
|
|
725
|
+
`${sanitizeCustomerError(error.message)}\n` +
|
|
726
|
+
`Evals run on the hosted deployment by default.\n` +
|
|
727
|
+
` deploy it first: ${CLI_COMMAND_NAME} deploy --slug ${error.slug}\n` +
|
|
728
|
+
` or run locally: ${CLI_COMMAND_NAME} eval --local --dir ${options.dir}\n`
|
|
729
|
+
);
|
|
730
|
+
return 1;
|
|
731
|
+
}
|
|
732
|
+
process.stderr.write(
|
|
733
|
+
`${sanitizeCustomerError(
|
|
734
|
+
error instanceof Error ? error.message : String(error)
|
|
735
|
+
)}\n` +
|
|
736
|
+
`Evals run on the hosted deployment by default; pass --local to run on an ephemeral local server.\n`
|
|
737
|
+
);
|
|
738
|
+
return 1;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
657
742
|
export async function cmdEval(options: AxCliOptions): Promise<number> {
|
|
658
743
|
const projectRoot = resolve(options.cwd ?? process.cwd(), options.dir);
|
|
659
744
|
|
|
@@ -695,7 +780,10 @@ export async function cmdEval(options: AxCliOptions): Promise<number> {
|
|
|
695
780
|
return 0;
|
|
696
781
|
}
|
|
697
782
|
|
|
698
|
-
const target = await
|
|
783
|
+
const target = await resolveEvalTarget(options);
|
|
784
|
+
if (typeof target === "number") {
|
|
785
|
+
return target;
|
|
786
|
+
}
|
|
699
787
|
const stream = shouldStreamProgress(options);
|
|
700
788
|
const progress = stream ? createStreamProgress() : undefined;
|
|
701
789
|
try {
|
|
@@ -991,9 +1079,9 @@ export async function cmdSession(
|
|
|
991
1079
|
/**
|
|
992
1080
|
* `playground` — open the web UI against local / `--url` / `--prod`.
|
|
993
1081
|
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1082
|
+
* `--prod` opens the stable hosted alias playground URL; the SPA collects
|
|
1083
|
+
* the alias token. `--bearer-token` (and other injected headers) still run a
|
|
1084
|
+
* loopback reverse proxy — Ctrl-C stops that proxy.
|
|
997
1085
|
*/
|
|
998
1086
|
export async function cmdPlayground(options: AxCliOptions): Promise<number> {
|
|
999
1087
|
if (options.prod === true && options.url !== undefined) {
|
|
@@ -1006,17 +1094,18 @@ export async function cmdPlayground(options: AxCliOptions): Promise<number> {
|
|
|
1006
1094
|
? options.sessionId.trim()
|
|
1007
1095
|
: undefined;
|
|
1008
1096
|
|
|
1009
|
-
// Hosted:
|
|
1097
|
+
// Hosted: open the alias playground; SPA owns alias-token sign-in.
|
|
1010
1098
|
if (options.prod === true) {
|
|
1011
|
-
let
|
|
1099
|
+
let resolved: Awaited<ReturnType<typeof resolveProdAliasPlaygroundUrl>>;
|
|
1012
1100
|
try {
|
|
1013
|
-
|
|
1101
|
+
resolved = await resolveProdAliasPlaygroundUrl({
|
|
1014
1102
|
dir: options.dir,
|
|
1015
1103
|
cwd: options.cwd,
|
|
1016
1104
|
slug: options.slug,
|
|
1017
1105
|
team: options.team,
|
|
1018
1106
|
apiKey: options.apiKey,
|
|
1019
1107
|
fetchImpl: options.fetchImpl,
|
|
1108
|
+
sessionId,
|
|
1020
1109
|
});
|
|
1021
1110
|
} catch (error) {
|
|
1022
1111
|
process.stderr.write(
|
|
@@ -1027,44 +1116,11 @@ export async function cmdPlayground(options: AxCliOptions): Promise<number> {
|
|
|
1027
1116
|
return 1;
|
|
1028
1117
|
}
|
|
1029
1118
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
upstreamUrl: initial.agentUrl,
|
|
1034
|
-
headers: initial.headers,
|
|
1035
|
-
expiresAt: initial.expiresAt,
|
|
1036
|
-
refreshUpstream: async () => {
|
|
1037
|
-
const next = await resolveProdTarget({
|
|
1038
|
-
dir: options.dir,
|
|
1039
|
-
cwd: options.cwd,
|
|
1040
|
-
slug: options.slug ?? initial.slug,
|
|
1041
|
-
team: options.team,
|
|
1042
|
-
apiKey: options.apiKey,
|
|
1043
|
-
fetchImpl: options.fetchImpl,
|
|
1044
|
-
err: () => {},
|
|
1045
|
-
});
|
|
1046
|
-
return {
|
|
1047
|
-
upstreamUrl: next.agentUrl,
|
|
1048
|
-
headers: next.headers,
|
|
1049
|
-
expiresAt: next.expiresAt,
|
|
1050
|
-
};
|
|
1051
|
-
},
|
|
1052
|
-
});
|
|
1053
|
-
} catch (error) {
|
|
1054
|
-
process.stderr.write(
|
|
1055
|
-
`${sanitizeCustomerError(
|
|
1056
|
-
error instanceof Error ? error.message : String(error)
|
|
1057
|
-
)}\n`
|
|
1058
|
-
);
|
|
1059
|
-
return 1;
|
|
1119
|
+
process.stdout.write(`${resolved.playgroundUrl}\n`);
|
|
1120
|
+
if (options.print !== true) {
|
|
1121
|
+
openBrowser(resolved.playgroundUrl);
|
|
1060
1122
|
}
|
|
1061
|
-
|
|
1062
|
-
return await runPlaygroundProxySession({
|
|
1063
|
-
proxy,
|
|
1064
|
-
sessionId,
|
|
1065
|
-
print: options.print === true,
|
|
1066
|
-
signal: options.signal,
|
|
1067
|
-
});
|
|
1123
|
+
return 0;
|
|
1068
1124
|
}
|
|
1069
1125
|
|
|
1070
1126
|
const target = await resolveSessionTarget(options);
|
|
@@ -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
|
-
`
|
|
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.
|
|
1615
|
-
durableSnapshots: project.
|
|
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
|
|
5
|
-
*
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
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.
|
|
215
|
+
this.hydratePromise = this.hydrateFromStorage();
|
|
223
216
|
}
|
|
224
217
|
await this.hydratePromise;
|
|
225
218
|
}
|
|
226
219
|
|
|
227
|
-
private async
|
|
220
|
+
private async hydrateFromStorage(): Promise<void> {
|
|
228
221
|
try {
|
|
229
|
-
|
|
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
|
|
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
|
-
|
|
239
|
+
this.persist(run);
|
|
257
240
|
}
|
|
258
241
|
this.runs.set(run.runId, run);
|
|
259
242
|
}
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
256
|
+
this.pruneOldRuns(config);
|
|
264
257
|
this.logger(
|
|
265
|
-
`[agentkit] hydrated ${this.runs.size} playground eval run(s) from
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
18
|
-
* re-minted shortly before `expiresAt` and once on upstream
|
|
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
|
|
88
|
-
*
|
|
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. */
|