@cursor/july 0.1.6 → 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.
- package/dist/bin/agent-serve.js +42 -17
- 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 +1 -1
- package/dist/docs/ab.html +2 -2
- package/dist/docs/assets/{app.DMboGKUD.js → app.COTN7wgo.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DkEqEnJU.js → VPLocalSearchBox.BW3TBdT0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.ASugdMNm.js → theme.BEJW0vE7.js} +2 -2
- package/dist/docs/building-with-agents.html +2 -2
- package/dist/docs/concepts.html +2 -2
- package/dist/docs/deployment.html +2 -2
- package/dist/docs/evals.html +2 -2
- package/dist/docs/example-agents/approval-buddy.html +2 -2
- package/dist/docs/example-agents/benny.html +2 -2
- package/dist/docs/example-agents/bugbot.html +2 -2
- package/dist/docs/example-agents/codebase-wiki.html +2 -2
- package/dist/docs/example-agents/codeowners-review.html +2 -2
- package/dist/docs/example-agents/concierge.html +2 -2
- package/dist/docs/example-agents/fsd.html +2 -2
- package/dist/docs/example-agents/index.html +2 -2
- package/dist/docs/example-agents/knowledge-base.html +2 -2
- package/dist/docs/example-agents/security-reviewer.html +2 -2
- package/dist/docs/example-agents/slack-agent.html +2 -2
- package/dist/docs/example-agents/weather-agent.html +2 -2
- package/dist/docs/guides/agent-to-agent.html +2 -2
- package/dist/docs/guides/cloud-runtime.html +2 -2
- package/dist/docs/guides/github.html +2 -2
- package/dist/docs/guides/human-in-the-loop.html +2 -2
- package/dist/docs/guides/slack.html +2 -2
- package/dist/docs/guides/webhooks.html +2 -2
- package/dist/docs/hillclimbing.html +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/reference/agent-config.html +2 -2
- package/dist/docs/reference/channels.html +2 -2
- package/dist/docs/reference/cli.html +2 -2
- package/dist/docs/reference/connections.html +2 -2
- package/dist/docs/reference/hooks.html +2 -2
- package/dist/docs/reference/http-api.html +2 -2
- package/dist/docs/reference/instructions.html +2 -2
- package/dist/docs/reference/playground.html +2 -2
- package/dist/docs/reference/project-layout.html +2 -2
- package/dist/docs/reference/schedules.html +2 -2
- package/dist/docs/reference/sessions.html +2 -2
- package/dist/docs/reference/skills.html +2 -2
- package/dist/docs/reference/subagents.html +2 -2
- package/dist/docs/reference/tools.html +2 -2
- package/dist/docs/scaffolding-agents.html +2 -2
- package/dist/docs/storage.html +2 -2
- package/dist/docs/troubleshooting.html +2 -2
- package/dist/evals.d.ts +2 -2
- package/dist/internal/cli-ax.d.ts +23 -10
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +358 -55
- package/dist/internal/eval-run-store.d.ts +2 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +4 -4
- package/dist/internal/evals-client.d.ts +96 -0
- package/dist/internal/evals-client.d.ts.map +1 -0
- package/dist/internal/evals-client.js +262 -0
- package/dist/internal/init-project.js +3 -3
- package/dist/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/persistence-coordinator.d.ts +127 -0
- package/dist/internal/persistence-coordinator.d.ts.map +1 -0
- package/dist/internal/server.js +11 -2
- package/dist/persistence.d.ts +184 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/playground/assets/index-72vCOBWO.js +86 -0
- package/dist/playground/assets/index-BjnMwYoR.css +1 -0
- package/dist/playground/index.html +2 -2
- package/package.json +1 -1
- package/src/bin/agent-serve.ts +47 -16
- package/src/evals.ts +2 -2
- package/src/internal/cli-ax.ts +466 -75
- package/src/internal/eval-run-store.ts +5 -0
- package/src/internal/evals-client.ts +431 -0
- package/src/internal/init-project.ts +3 -3
- package/src/internal/server.ts +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.O6eP7QZr.js +0 -1
- package/dist/playground/assets/index-7GJIUFQj.css +0 -1
- package/dist/playground/assets/index-mux9aj5P.js +0 -319
package/src/internal/cli-ax.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join, resolve } from "node:path";
|
|
13
|
+
import type { EvalRunSnapshot } from "../evals.js";
|
|
13
14
|
import { serve } from "../index.js";
|
|
14
15
|
import type { AgentServeHandle } from "../types.js";
|
|
15
16
|
import { lookupSessionContinuation, runChat } from "./chat-client.js";
|
|
@@ -23,6 +24,14 @@ import {
|
|
|
23
24
|
filterDiscoveredEvals,
|
|
24
25
|
runDiscoveredEvals,
|
|
25
26
|
} from "./eval-runner.js";
|
|
27
|
+
import {
|
|
28
|
+
getRemoteEvalRun,
|
|
29
|
+
listRemoteEvalRuns,
|
|
30
|
+
listRemoteEvals,
|
|
31
|
+
RemoteEvalError,
|
|
32
|
+
runRemoteEvals,
|
|
33
|
+
startRemoteEvalRun,
|
|
34
|
+
} from "./evals-client.js";
|
|
26
35
|
import {
|
|
27
36
|
formatInitNextSteps,
|
|
28
37
|
formatInitScaffoldSummary,
|
|
@@ -95,17 +104,18 @@ export interface AxCliOptions {
|
|
|
95
104
|
url?: string;
|
|
96
105
|
/**
|
|
97
106
|
* Route chat/resume/run/call/eval/logs/sessions at the team's hosted
|
|
98
|
-
* deployment
|
|
99
|
-
*
|
|
100
|
-
* `
|
|
101
|
-
*
|
|
107
|
+
* deployment instead of a local ephemeral server. Mutually exclusive with
|
|
108
|
+
* `--url`. For `eval`, this runs a server-side batch via
|
|
109
|
+
* `/v1/dev/evals/runs` (persisted when `defineStorage` has an `evals` table);
|
|
110
|
+
* without `--prod`, eval defaults to an ephemeral local harness.
|
|
102
111
|
*/
|
|
103
112
|
prod?: boolean;
|
|
104
113
|
/**
|
|
105
|
-
* `eval
|
|
106
|
-
*
|
|
114
|
+
* `eval` (`--prod` / `--url` only): start the batch via
|
|
115
|
+
* `POST /v1/dev/evals/runs` and return the `runId` without polling.
|
|
116
|
+
* Mutually exclusive with `--list`.
|
|
107
117
|
*/
|
|
108
|
-
|
|
118
|
+
noWait?: boolean;
|
|
109
119
|
/** Cursor team id for `--prod` (defaults to the account's team). */
|
|
110
120
|
team?: string;
|
|
111
121
|
/** Cursor API key override for `--prod` management calls. */
|
|
@@ -667,28 +677,27 @@ export async function cmdTrajectory(options: AxCliOptions): Promise<number> {
|
|
|
667
677
|
}
|
|
668
678
|
|
|
669
679
|
/**
|
|
670
|
-
* Which surface `eval` should target.
|
|
671
|
-
*
|
|
680
|
+
* Which surface `eval` should target. Local (ephemeral) is the default;
|
|
681
|
+
* `--prod` uses the hosted deployment, `--url` a running server.
|
|
672
682
|
* Exported for tests.
|
|
673
683
|
*/
|
|
674
684
|
export function resolveEvalTargetMode(
|
|
675
|
-
options: Pick<AxCliOptions, "
|
|
685
|
+
options: Pick<AxCliOptions, "prod" | "url">
|
|
676
686
|
): "local" | "url" | "deployment" | { error: string } {
|
|
677
687
|
const flags = [
|
|
678
|
-
options.local === true ? "--local" : undefined,
|
|
679
688
|
options.prod === true ? "--prod" : undefined,
|
|
680
689
|
options.url !== undefined ? "--url" : undefined,
|
|
681
690
|
].filter((flag) => flag !== undefined);
|
|
682
691
|
if (flags.length > 1) {
|
|
683
692
|
return { error: `${flags.join(" and ")} are mutually exclusive` };
|
|
684
693
|
}
|
|
685
|
-
if (options.local === true) {
|
|
686
|
-
return "local";
|
|
687
|
-
}
|
|
688
694
|
if (options.url !== undefined) {
|
|
689
695
|
return "url";
|
|
690
696
|
}
|
|
691
|
-
|
|
697
|
+
if (options.prod === true) {
|
|
698
|
+
return "deployment";
|
|
699
|
+
}
|
|
700
|
+
return "local";
|
|
692
701
|
}
|
|
693
702
|
|
|
694
703
|
/** Resolve the eval target; prints a CLI-ready error and returns an exit code on failure. */
|
|
@@ -723,9 +732,8 @@ async function resolveEvalTarget(
|
|
|
723
732
|
// genuinely missing deployment.
|
|
724
733
|
process.stderr.write(
|
|
725
734
|
`${sanitizeCustomerError(error.message)}\n` +
|
|
726
|
-
`Evals run on the hosted deployment by default.\n` +
|
|
727
735
|
` deploy it first: ${CLI_COMMAND_NAME} deploy --slug ${error.slug}\n` +
|
|
728
|
-
` or
|
|
736
|
+
` or omit --prod: ${CLI_COMMAND_NAME} eval --dir ${options.dir}\n`
|
|
729
737
|
);
|
|
730
738
|
return 1;
|
|
731
739
|
}
|
|
@@ -733,7 +741,7 @@ async function resolveEvalTarget(
|
|
|
733
741
|
`${sanitizeCustomerError(
|
|
734
742
|
error instanceof Error ? error.message : String(error)
|
|
735
743
|
)}\n` +
|
|
736
|
-
`
|
|
744
|
+
`Pass --prod for the hosted deployment, or omit it to run on an ephemeral local server.\n`
|
|
737
745
|
);
|
|
738
746
|
return 1;
|
|
739
747
|
}
|
|
@@ -741,43 +749,76 @@ async function resolveEvalTarget(
|
|
|
741
749
|
|
|
742
750
|
export async function cmdEval(options: AxCliOptions): Promise<number> {
|
|
743
751
|
const projectRoot = resolve(options.cwd ?? process.cwd(), options.dir);
|
|
752
|
+
const mode = resolveEvalTargetMode(options);
|
|
753
|
+
if (typeof mode === "object") {
|
|
754
|
+
process.stderr.write(`${mode.error}\n`);
|
|
755
|
+
return 2;
|
|
756
|
+
}
|
|
757
|
+
if (options.noWait === true) {
|
|
758
|
+
if (options.list === true) {
|
|
759
|
+
process.stderr.write("--no-wait and --list are mutually exclusive\n");
|
|
760
|
+
return 2;
|
|
761
|
+
}
|
|
762
|
+
if (mode === "local") {
|
|
763
|
+
process.stderr.write(
|
|
764
|
+
"--no-wait requires --prod or --url (not the default local target)\n"
|
|
765
|
+
);
|
|
766
|
+
return 2;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const filterIds =
|
|
771
|
+
options.evalIds === undefined || options.evalIds.length === 0
|
|
772
|
+
? undefined
|
|
773
|
+
: options.evalIds;
|
|
774
|
+
const tags =
|
|
775
|
+
options.tags === undefined || options.tags.length === 0
|
|
776
|
+
? undefined
|
|
777
|
+
: options.tags;
|
|
744
778
|
|
|
745
779
|
if (options.list) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
780
|
+
// List the same surface a run will execute: local filesystem by default,
|
|
781
|
+
// GET /v1/dev/evals on --prod / --url.
|
|
782
|
+
if (mode === "local") {
|
|
783
|
+
return listLocalEvals(projectRoot, options, filterIds, tags);
|
|
784
|
+
}
|
|
785
|
+
const target = await resolveEvalTarget(options);
|
|
786
|
+
if (typeof target === "number") {
|
|
787
|
+
return target;
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
const listed = await listRemoteEvals({
|
|
791
|
+
baseUrl: target.agentUrl,
|
|
792
|
+
auth: {
|
|
793
|
+
bearerToken: options.bearerToken,
|
|
794
|
+
headers: target.headers,
|
|
795
|
+
},
|
|
796
|
+
fetchImpl: options.fetchImpl,
|
|
797
|
+
filterIds,
|
|
798
|
+
tags,
|
|
799
|
+
});
|
|
800
|
+
return printListedEvals(
|
|
801
|
+
listed.map((e) => ({
|
|
802
|
+
id: e.id,
|
|
803
|
+
fileId: e.fileId,
|
|
804
|
+
description: e.description,
|
|
805
|
+
tags: e.tags ?? [],
|
|
806
|
+
})),
|
|
807
|
+
options,
|
|
808
|
+
`No evals on ${target.agentUrl}`
|
|
764
809
|
);
|
|
765
|
-
}
|
|
766
|
-
process.
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
? ` — ${e.definition.description}`
|
|
776
|
-
: "";
|
|
777
|
-
process.stdout.write(`${e.id}${tags}${desc}\n`);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
process.stderr.write(
|
|
812
|
+
`${sanitizeCustomerError(
|
|
813
|
+
error instanceof Error ? error.message : String(error)
|
|
814
|
+
)}\n`
|
|
815
|
+
);
|
|
816
|
+
return 1;
|
|
817
|
+
} finally {
|
|
818
|
+
if (target.close !== undefined) {
|
|
819
|
+
await target.close();
|
|
778
820
|
}
|
|
779
821
|
}
|
|
780
|
-
return 0;
|
|
781
822
|
}
|
|
782
823
|
|
|
783
824
|
const target = await resolveEvalTarget(options);
|
|
@@ -785,52 +826,402 @@ export async function cmdEval(options: AxCliOptions): Promise<number> {
|
|
|
785
826
|
return target;
|
|
786
827
|
}
|
|
787
828
|
const stream = shouldStreamProgress(options);
|
|
788
|
-
|
|
829
|
+
|
|
789
830
|
try {
|
|
790
831
|
if (options.verbose || stream) {
|
|
791
832
|
process.stderr.write(`eval target: ${target.agentUrl}\n`);
|
|
792
833
|
}
|
|
793
|
-
|
|
794
|
-
|
|
834
|
+
|
|
835
|
+
// Default local: ephemeral in-process harness against local eval sources.
|
|
836
|
+
// --prod / --url: run on the serve process via /v1/dev/evals/runs so the
|
|
837
|
+
// batch lands in playground history and defineStorage evals (when set).
|
|
838
|
+
if (mode === "local") {
|
|
839
|
+
const progress = stream ? createStreamProgress() : undefined;
|
|
840
|
+
try {
|
|
841
|
+
const results = await runDiscoveredEvals({
|
|
842
|
+
projectRoot,
|
|
843
|
+
baseUrl: target.agentUrl,
|
|
844
|
+
filterIds,
|
|
845
|
+
tags,
|
|
846
|
+
timeoutMs: options.timeoutMs,
|
|
847
|
+
bearerToken: options.bearerToken,
|
|
848
|
+
headers: target.headers,
|
|
849
|
+
verbose: Boolean(options.verbose) || stream,
|
|
850
|
+
onEvent:
|
|
851
|
+
progress === undefined ? undefined : (e) => progress.onEvent(e),
|
|
852
|
+
});
|
|
853
|
+
if (results.length === 0) {
|
|
854
|
+
process.stderr.write("No matching evals found.\n");
|
|
855
|
+
return 2;
|
|
856
|
+
}
|
|
857
|
+
return printEvalResults(results, options);
|
|
858
|
+
} finally {
|
|
859
|
+
progress?.close();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
try {
|
|
864
|
+
if (options.noWait === true) {
|
|
865
|
+
const started = await startRemoteEvalRun({
|
|
866
|
+
baseUrl: target.agentUrl,
|
|
867
|
+
filterIds,
|
|
868
|
+
tags,
|
|
869
|
+
timeoutMs: options.timeoutMs,
|
|
870
|
+
verbose: Boolean(options.verbose),
|
|
871
|
+
auth: {
|
|
872
|
+
bearerToken: options.bearerToken,
|
|
873
|
+
headers: target.headers,
|
|
874
|
+
},
|
|
875
|
+
fetchImpl: options.fetchImpl,
|
|
876
|
+
});
|
|
877
|
+
return printEvalAccepted(started, options, target.agentUrl);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const progress = createRemoteEvalProgress(
|
|
881
|
+
options.verbose === true || stream
|
|
882
|
+
);
|
|
883
|
+
const { snapshot, results } = await runRemoteEvals({
|
|
884
|
+
baseUrl: target.agentUrl,
|
|
885
|
+
filterIds,
|
|
886
|
+
tags,
|
|
887
|
+
timeoutMs: options.timeoutMs,
|
|
888
|
+
verbose: Boolean(options.verbose) || stream,
|
|
889
|
+
bearerToken: options.bearerToken,
|
|
890
|
+
headers: target.headers,
|
|
891
|
+
fetchImpl: options.fetchImpl,
|
|
892
|
+
onSnapshot: progress === undefined ? undefined : progress.onSnapshot,
|
|
893
|
+
});
|
|
894
|
+
if (results.length === 0 && snapshot.status !== "failed") {
|
|
895
|
+
process.stderr.write("No matching evals found.\n");
|
|
896
|
+
return 2;
|
|
897
|
+
}
|
|
898
|
+
return printEvalResults(results, options, {
|
|
899
|
+
runId: snapshot.runId,
|
|
900
|
+
status: snapshot.status,
|
|
901
|
+
error: snapshot.error,
|
|
902
|
+
});
|
|
903
|
+
} catch (error) {
|
|
904
|
+
if (
|
|
905
|
+
error instanceof RemoteEvalError &&
|
|
906
|
+
error.code === "no_matching_evals"
|
|
907
|
+
) {
|
|
908
|
+
process.stderr.write(`${error.message}\n`);
|
|
909
|
+
return 2;
|
|
910
|
+
}
|
|
911
|
+
process.stderr.write(
|
|
912
|
+
`${sanitizeCustomerError(
|
|
913
|
+
error instanceof Error ? error.message : String(error)
|
|
914
|
+
)}\n`
|
|
915
|
+
);
|
|
916
|
+
return 1;
|
|
917
|
+
}
|
|
918
|
+
} finally {
|
|
919
|
+
if (target.close !== undefined) {
|
|
920
|
+
await target.close();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Flags to re-target the same surface from an `eval --no-wait` hint.
|
|
927
|
+
* Exported for tests.
|
|
928
|
+
*/
|
|
929
|
+
export function formatEvalStatusFlags(
|
|
930
|
+
options: Pick<AxCliOptions, "prod" | "url" | "slug" | "team">
|
|
931
|
+
): string {
|
|
932
|
+
const parts: string[] = [];
|
|
933
|
+
if (options.url !== undefined) {
|
|
934
|
+
parts.push(`--url ${options.url.replace(/\/$/, "")}`);
|
|
935
|
+
} else {
|
|
936
|
+
parts.push("--prod");
|
|
937
|
+
if (options.slug !== undefined && options.slug !== "") {
|
|
938
|
+
parts.push(`--slug ${options.slug}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (options.team !== undefined && options.team !== "") {
|
|
942
|
+
parts.push(`--team ${options.team}`);
|
|
943
|
+
}
|
|
944
|
+
return parts.length === 0 ? "" : ` ${parts.join(" ")}`;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** `--no-wait`: print the accepted batch id (and how to poll) then exit. */
|
|
948
|
+
function printEvalAccepted(
|
|
949
|
+
started: EvalRunSnapshot,
|
|
950
|
+
options: AxCliOptions,
|
|
951
|
+
agentUrl: string
|
|
952
|
+
): number {
|
|
953
|
+
const pollPath = `${agentUrl.replace(/\/$/, "")}/v1/dev/evals/runs/${started.runId}`;
|
|
954
|
+
const statusFlags = formatEvalStatusFlags(options);
|
|
955
|
+
if (options.json) {
|
|
956
|
+
console.log(
|
|
957
|
+
JSON.stringify(
|
|
958
|
+
{
|
|
959
|
+
ok: true,
|
|
960
|
+
accepted: true,
|
|
961
|
+
runId: started.runId,
|
|
962
|
+
status: started.status,
|
|
963
|
+
summary: started.summary,
|
|
964
|
+
pollUrl: pollPath,
|
|
965
|
+
durableRuns: started.config.durableRuns === true,
|
|
966
|
+
},
|
|
967
|
+
null,
|
|
968
|
+
2
|
|
969
|
+
)
|
|
970
|
+
);
|
|
971
|
+
} else {
|
|
972
|
+
const durable = started.config.durableRuns === true ? " (persisted)" : "";
|
|
973
|
+
process.stdout.write(
|
|
974
|
+
`Accepted eval run ${started.runId}${durable} — ${started.summary.total} case(s)\n`
|
|
975
|
+
);
|
|
976
|
+
process.stdout.write(
|
|
977
|
+
`Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
|
|
978
|
+
);
|
|
979
|
+
process.stdout.write(`Poll: ${pollPath}\n`);
|
|
980
|
+
}
|
|
981
|
+
return 0;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* `eval status [runId]` — fetch one batch (or list recent) on the deployment /
|
|
986
|
+
* `--url` target. Exit `0` when the batch is completed with all cases passing,
|
|
987
|
+
* `1` when failed / any case failed / unknown, `2` for usage errors, `3` when
|
|
988
|
+
* the batch is still running (for poll loops after `--no-wait`).
|
|
989
|
+
*/
|
|
990
|
+
export async function cmdEvalStatus(
|
|
991
|
+
runId: string | undefined,
|
|
992
|
+
options: AxCliOptions
|
|
993
|
+
): Promise<number> {
|
|
994
|
+
const mode = resolveEvalTargetMode(options);
|
|
995
|
+
if (typeof mode === "object") {
|
|
996
|
+
process.stderr.write(`${mode.error}\n`);
|
|
997
|
+
return 2;
|
|
998
|
+
}
|
|
999
|
+
if (mode === "local") {
|
|
1000
|
+
process.stderr.write(
|
|
1001
|
+
"eval status requires --prod or --url (not the default local target)\n"
|
|
1002
|
+
);
|
|
1003
|
+
return 2;
|
|
1004
|
+
}
|
|
1005
|
+
if (options.list === true || options.noWait === true) {
|
|
1006
|
+
process.stderr.write("eval status does not accept --list or --no-wait\n");
|
|
1007
|
+
return 2;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const target = await resolveEvalTarget(options);
|
|
1011
|
+
if (typeof target === "number") {
|
|
1012
|
+
return target;
|
|
1013
|
+
}
|
|
1014
|
+
const auth = {
|
|
1015
|
+
bearerToken: options.bearerToken,
|
|
1016
|
+
headers: target.headers,
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
try {
|
|
1020
|
+
if (runId === undefined || runId === "") {
|
|
1021
|
+
const { runs, activeRunId } = await listRemoteEvalRuns({
|
|
1022
|
+
baseUrl: target.agentUrl,
|
|
1023
|
+
auth,
|
|
1024
|
+
fetchImpl: options.fetchImpl,
|
|
1025
|
+
});
|
|
1026
|
+
if (options.json) {
|
|
1027
|
+
console.log(JSON.stringify({ runs, activeRunId }, null, 2));
|
|
1028
|
+
} else if (runs.length === 0) {
|
|
1029
|
+
process.stdout.write("No eval runs on this agent.\n");
|
|
1030
|
+
} else {
|
|
1031
|
+
if (activeRunId !== undefined) {
|
|
1032
|
+
process.stdout.write(`active: ${activeRunId}\n`);
|
|
1033
|
+
}
|
|
1034
|
+
for (const run of runs) {
|
|
1035
|
+
process.stdout.write(`${formatEvalRunSummaryLine(run)}\n`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return 0;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const snap = await getRemoteEvalRun({
|
|
795
1042
|
baseUrl: target.agentUrl,
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
: options.evalIds,
|
|
800
|
-
tags:
|
|
801
|
-
options.tags === undefined || options.tags.length === 0
|
|
802
|
-
? undefined
|
|
803
|
-
: options.tags,
|
|
804
|
-
timeoutMs: options.timeoutMs,
|
|
805
|
-
bearerToken: options.bearerToken,
|
|
806
|
-
headers: target.headers,
|
|
807
|
-
verbose: Boolean(options.verbose) || stream,
|
|
808
|
-
onEvent: progress === undefined ? undefined : (e) => progress.onEvent(e),
|
|
1043
|
+
runId,
|
|
1044
|
+
auth,
|
|
1045
|
+
fetchImpl: options.fetchImpl,
|
|
809
1046
|
});
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1047
|
+
if (options.json) {
|
|
1048
|
+
console.log(JSON.stringify(snap, null, 2));
|
|
1049
|
+
} else {
|
|
1050
|
+
process.stdout.write(`${formatEvalRunSummaryLine(snap)}\n`);
|
|
1051
|
+
if (snap.error !== undefined && snap.error !== "") {
|
|
1052
|
+
process.stdout.write(`error: ${snap.error}\n`);
|
|
1053
|
+
}
|
|
1054
|
+
for (const c of snap.cases) {
|
|
1055
|
+
const mark =
|
|
1056
|
+
c.status !== "done" ? c.status : c.ok === true ? "PASS" : "FAIL";
|
|
1057
|
+
const dur =
|
|
1058
|
+
c.durationMs === undefined
|
|
1059
|
+
? ""
|
|
1060
|
+
: ` (${(c.durationMs / 1000).toFixed(1)}s)`;
|
|
1061
|
+
process.stdout.write(` ${mark} ${c.id}${dur}\n`);
|
|
1062
|
+
if (c.error !== undefined && c.error !== "") {
|
|
1063
|
+
process.stdout.write(` error: ${c.error}\n`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (snap.status === "running") {
|
|
1068
|
+
return 3;
|
|
814
1069
|
}
|
|
815
|
-
|
|
1070
|
+
const failed =
|
|
1071
|
+
snap.status === "failed" || snap.cases.some((c) => c.ok !== true);
|
|
1072
|
+
return failed ? 1 : 0;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
process.stderr.write(
|
|
1075
|
+
`${sanitizeCustomerError(
|
|
1076
|
+
error instanceof Error ? error.message : String(error)
|
|
1077
|
+
)}\n`
|
|
1078
|
+
);
|
|
1079
|
+
return 1;
|
|
816
1080
|
} finally {
|
|
817
|
-
progress?.close();
|
|
818
1081
|
if (target.close !== undefined) {
|
|
819
1082
|
await target.close();
|
|
820
1083
|
}
|
|
821
1084
|
}
|
|
822
1085
|
}
|
|
823
1086
|
|
|
1087
|
+
function formatEvalRunSummaryLine(run: EvalRunSnapshot): string {
|
|
1088
|
+
const { passed, failed, total, done } = run.summary;
|
|
1089
|
+
const durable = run.config.durableRuns === true ? " persisted" : "";
|
|
1090
|
+
return `${run.runId} ${run.status} ${done}/${total} done (${passed} passed, ${failed} failed)${durable}`;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
async function listLocalEvals(
|
|
1094
|
+
projectRoot: string,
|
|
1095
|
+
options: AxCliOptions,
|
|
1096
|
+
filterIds: string[] | undefined,
|
|
1097
|
+
tags: string[] | undefined
|
|
1098
|
+
): Promise<number> {
|
|
1099
|
+
const { evals } = await discoverEvals(projectRoot);
|
|
1100
|
+
const listed = filterDiscoveredEvals(evals, { filterIds, tags });
|
|
1101
|
+
return printListedEvals(
|
|
1102
|
+
listed.map((e) => ({
|
|
1103
|
+
id: e.id,
|
|
1104
|
+
fileId: e.fileId,
|
|
1105
|
+
path: e.path,
|
|
1106
|
+
description: e.definition.description,
|
|
1107
|
+
tags: e.definition.tags ?? [],
|
|
1108
|
+
})),
|
|
1109
|
+
options,
|
|
1110
|
+
`No evals under ${projectRoot}/evals`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function printListedEvals(
|
|
1115
|
+
listed: Array<{
|
|
1116
|
+
id: string;
|
|
1117
|
+
fileId: string;
|
|
1118
|
+
path?: string;
|
|
1119
|
+
description?: string;
|
|
1120
|
+
tags: string[];
|
|
1121
|
+
}>,
|
|
1122
|
+
options: AxCliOptions,
|
|
1123
|
+
emptyMessage: string
|
|
1124
|
+
): number {
|
|
1125
|
+
if (options.json) {
|
|
1126
|
+
console.log(JSON.stringify(listed, null, 2));
|
|
1127
|
+
} else if (listed.length === 0) {
|
|
1128
|
+
process.stdout.write(`${emptyMessage}\n`);
|
|
1129
|
+
} else {
|
|
1130
|
+
for (const e of listed) {
|
|
1131
|
+
const tagSuffix = e.tags.length > 0 ? ` [${e.tags.join(", ")}]` : "";
|
|
1132
|
+
const desc = e.description !== undefined ? ` — ${e.description}` : "";
|
|
1133
|
+
process.stdout.write(`${e.id}${tagSuffix}${desc}\n`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return 0;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Remote batches don't stream turn events to the CLI; print run/case progress
|
|
1141
|
+
* (and case logs once they appear on the snapshot) instead.
|
|
1142
|
+
*/
|
|
1143
|
+
function createRemoteEvalProgress(
|
|
1144
|
+
enabled: boolean
|
|
1145
|
+
): { onSnapshot: (snap: EvalRunSnapshot) => void } | undefined {
|
|
1146
|
+
if (!enabled) {
|
|
1147
|
+
return undefined;
|
|
1148
|
+
}
|
|
1149
|
+
let announced = false;
|
|
1150
|
+
let lastSummaryKey = "";
|
|
1151
|
+
const seenCaseKeys = new Set<string>();
|
|
1152
|
+
return {
|
|
1153
|
+
onSnapshot(snap) {
|
|
1154
|
+
if (!announced) {
|
|
1155
|
+
announced = true;
|
|
1156
|
+
const durable = snap.config.durableRuns === true ? " (persisted)" : "";
|
|
1157
|
+
process.stderr.write(`eval run: ${snap.runId}${durable}\n`);
|
|
1158
|
+
}
|
|
1159
|
+
const summaryKey = `${snap.status}:${snap.summary.done}:${snap.summary.passed}:${snap.summary.failed}`;
|
|
1160
|
+
if (summaryKey !== lastSummaryKey) {
|
|
1161
|
+
lastSummaryKey = summaryKey;
|
|
1162
|
+
const { done, total, passed, failed } = snap.summary;
|
|
1163
|
+
process.stderr.write(
|
|
1164
|
+
`eval progress: ${done}/${total} done (${passed} passed, ${failed} failed) [${snap.status}]\n`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
for (const c of snap.cases) {
|
|
1168
|
+
if (c.status !== "done") {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
const caseKey = `${c.id}:${c.ok === true ? "pass" : "fail"}:${c.durationMs ?? 0}`;
|
|
1172
|
+
if (seenCaseKeys.has(caseKey)) {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
seenCaseKeys.add(caseKey);
|
|
1176
|
+
process.stderr.write(
|
|
1177
|
+
` eval ${c.id}: ${c.ok === true ? "PASS" : "FAIL"}${
|
|
1178
|
+
c.error !== undefined ? ` — ${c.error}` : ""
|
|
1179
|
+
}\n`
|
|
1180
|
+
);
|
|
1181
|
+
for (const line of c.logs ?? []) {
|
|
1182
|
+
process.stderr.write(` ${line}\n`);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
824
1189
|
function printEvalResults(
|
|
825
1190
|
results: EvalRunResult[],
|
|
826
|
-
options: AxCliOptions
|
|
1191
|
+
options: AxCliOptions,
|
|
1192
|
+
batch?: {
|
|
1193
|
+
runId?: string;
|
|
1194
|
+
status?: EvalRunSnapshot["status"];
|
|
1195
|
+
error?: string;
|
|
1196
|
+
}
|
|
827
1197
|
): number {
|
|
828
1198
|
const passed = results.filter((r) => r.ok).length;
|
|
829
1199
|
const failed = results.length - passed;
|
|
1200
|
+
const batchFailed = batch?.status === "failed";
|
|
1201
|
+
const ok = failed === 0 && !batchFailed;
|
|
1202
|
+
|
|
1203
|
+
if (batch?.error !== undefined && batch.error !== "" && !options.json) {
|
|
1204
|
+
// Always surface harness-level failures (not only --verbose).
|
|
1205
|
+
process.stderr.write(`eval run error: ${batch.error}\n`);
|
|
1206
|
+
}
|
|
830
1207
|
|
|
831
1208
|
if (options.json) {
|
|
832
1209
|
console.log(
|
|
833
|
-
JSON.stringify(
|
|
1210
|
+
JSON.stringify(
|
|
1211
|
+
{
|
|
1212
|
+
ok,
|
|
1213
|
+
passed,
|
|
1214
|
+
failed,
|
|
1215
|
+
results,
|
|
1216
|
+
...(batch?.runId === undefined ? {} : { runId: batch.runId }),
|
|
1217
|
+
...(batch?.status === undefined ? {} : { status: batch.status }),
|
|
1218
|
+
...(batch?.error === undefined || batch.error === ""
|
|
1219
|
+
? {}
|
|
1220
|
+
: { error: batch.error }),
|
|
1221
|
+
},
|
|
1222
|
+
null,
|
|
1223
|
+
2
|
|
1224
|
+
)
|
|
834
1225
|
);
|
|
835
1226
|
} else {
|
|
836
1227
|
for (const r of results) {
|
|
@@ -861,7 +1252,7 @@ function printEvalResults(
|
|
|
861
1252
|
`\n${passed} passed, ${failed} failed, ${results.length} total\n`
|
|
862
1253
|
);
|
|
863
1254
|
}
|
|
864
|
-
return
|
|
1255
|
+
return ok ? 0 : 1;
|
|
865
1256
|
}
|
|
866
1257
|
|
|
867
1258
|
/**
|
|
@@ -47,6 +47,8 @@ export interface StartEvalRunInput {
|
|
|
47
47
|
tags?: string[];
|
|
48
48
|
bearerToken?: string;
|
|
49
49
|
verbose?: boolean;
|
|
50
|
+
/** Per-case timeout override (same semantics as CLI `--timeout-ms`). */
|
|
51
|
+
timeoutMs?: number;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export type EvalListConfig = {
|
|
@@ -190,6 +192,7 @@ export class EvalRunStore {
|
|
|
190
192
|
? {}
|
|
191
193
|
: { bearerToken: input.bearerToken }),
|
|
192
194
|
...(input.verbose === undefined ? {} : { verbose: input.verbose }),
|
|
195
|
+
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
|
|
193
196
|
});
|
|
194
197
|
|
|
195
198
|
return snapshot;
|
|
@@ -316,6 +319,7 @@ export class EvalRunStore {
|
|
|
316
319
|
config: Partial<EvalConfigFile>;
|
|
317
320
|
bearerToken?: string;
|
|
318
321
|
verbose?: boolean;
|
|
322
|
+
timeoutMs?: number;
|
|
319
323
|
}
|
|
320
324
|
): Promise<void> {
|
|
321
325
|
if (this.runs.get(runId) === undefined) {
|
|
@@ -330,6 +334,7 @@ export class EvalRunStore {
|
|
|
330
334
|
? {}
|
|
331
335
|
: { bearerToken: opts.bearerToken }),
|
|
332
336
|
...(opts.verbose === undefined ? {} : { verbose: opts.verbose }),
|
|
337
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
333
338
|
onCaseStart: (id) => {
|
|
334
339
|
const run = this.runs.get(runId);
|
|
335
340
|
if (run === undefined) {
|