@cursor/july 0.1.12 → 0.1.14
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/github/instrument.d.ts +20 -0
- package/dist/channels/github/instrument.d.ts.map +1 -0
- 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/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.Dh_JkJjI.js → app.CKFFLL4Z.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DGWkvXJc.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CptQNLkR.js → VPLocalSearchBox.BXk19JBN.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DWflH-LQ.js → theme.BXSPBMyn.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 +41 -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 +163 -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 +87 -8
- package/dist/internal/eval-runner.d.ts +12 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +97 -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 +43 -0
- 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/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/playground/assets/index-CuXb4j4V.css +1 -0
- package/dist/playground/assets/{index-1dzluIcW.js → index-DeyYpOit.js} +46 -46
- 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/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 +104 -25
- package/src/internal/cli-ax.ts +192 -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 +89 -6
- package/src/internal/eval-runner.ts +113 -18
- package/src/internal/evals-client.ts +81 -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/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
package/src/internal/cli-ax.ts
CHANGED
|
@@ -25,11 +25,13 @@ import {
|
|
|
25
25
|
runDiscoveredEvals,
|
|
26
26
|
} from "./eval-runner.js";
|
|
27
27
|
import {
|
|
28
|
+
cancelRemoteEvalRun,
|
|
28
29
|
getRemoteEvalRun,
|
|
29
30
|
listRemoteEvalRuns,
|
|
30
31
|
listRemoteEvals,
|
|
32
|
+
pollRemoteEvalRun,
|
|
31
33
|
RemoteEvalError,
|
|
32
|
-
|
|
34
|
+
snapshotToEvalRunResults,
|
|
33
35
|
startRemoteEvalRun,
|
|
34
36
|
} from "./evals-client.js";
|
|
35
37
|
import {
|
|
@@ -874,23 +876,38 @@ export async function cmdEval(options: AxCliOptions): Promise<number> {
|
|
|
874
876
|
},
|
|
875
877
|
fetchImpl: options.fetchImpl,
|
|
876
878
|
});
|
|
877
|
-
return printEvalAccepted(started, options, target.agentUrl);
|
|
879
|
+
return await printEvalAccepted(started, options, target.agentUrl);
|
|
878
880
|
}
|
|
879
881
|
|
|
880
882
|
const progress = createRemoteEvalProgress(
|
|
881
883
|
options.verbose === true || stream
|
|
882
884
|
);
|
|
883
|
-
|
|
885
|
+
// Start first so we can print the Eval ID before the (often long) wait.
|
|
886
|
+
const started = await startRemoteEvalRun({
|
|
884
887
|
baseUrl: target.agentUrl,
|
|
885
888
|
filterIds,
|
|
886
889
|
tags,
|
|
887
890
|
timeoutMs: options.timeoutMs,
|
|
888
891
|
verbose: Boolean(options.verbose) || stream,
|
|
889
|
-
|
|
890
|
-
|
|
892
|
+
auth: {
|
|
893
|
+
bearerToken: options.bearerToken,
|
|
894
|
+
headers: target.headers,
|
|
895
|
+
},
|
|
896
|
+
fetchImpl: options.fetchImpl,
|
|
897
|
+
});
|
|
898
|
+
await printEvalIdBanner(started, options, target.agentUrl);
|
|
899
|
+
progress?.onSnapshot?.(started);
|
|
900
|
+
const snapshot = await pollRemoteEvalRun({
|
|
901
|
+
baseUrl: target.agentUrl,
|
|
902
|
+
runId: started.runId,
|
|
903
|
+
auth: {
|
|
904
|
+
bearerToken: options.bearerToken,
|
|
905
|
+
headers: target.headers,
|
|
906
|
+
},
|
|
891
907
|
fetchImpl: options.fetchImpl,
|
|
892
908
|
onSnapshot: progress === undefined ? undefined : progress.onSnapshot,
|
|
893
909
|
});
|
|
910
|
+
const results = snapshotToEvalRunResults(snapshot);
|
|
894
911
|
if (results.length === 0 && snapshot.status !== "failed") {
|
|
895
912
|
process.stderr.write("No matching evals found.\n");
|
|
896
913
|
return 2;
|
|
@@ -944,25 +961,74 @@ export function formatEvalStatusFlags(
|
|
|
944
961
|
return parts.length === 0 ? "" : ` ${parts.join(" ")}`;
|
|
945
962
|
}
|
|
946
963
|
|
|
964
|
+
/** Print Eval ID + cancel/status hints after a remote batch is accepted. */
|
|
965
|
+
async function printEvalIdBanner(
|
|
966
|
+
started: EvalRunSnapshot,
|
|
967
|
+
options: AxCliOptions,
|
|
968
|
+
agentUrl: string
|
|
969
|
+
): Promise<void> {
|
|
970
|
+
const pollPath = `${agentUrl.replace(/\/$/, "")}/v1/dev/evals/runs/${started.runId}`;
|
|
971
|
+
const statusFlags = formatEvalStatusFlags(options);
|
|
972
|
+
const playgroundUrl = await resolveEvalPlaygroundDeepLink(
|
|
973
|
+
options,
|
|
974
|
+
started.runId,
|
|
975
|
+
agentUrl
|
|
976
|
+
);
|
|
977
|
+
if (options.json) {
|
|
978
|
+
// Waiters still want the id on stderr so progress text stays readable.
|
|
979
|
+
process.stderr.write(
|
|
980
|
+
`${JSON.stringify({
|
|
981
|
+
evalId: started.runId,
|
|
982
|
+
runId: started.runId,
|
|
983
|
+
status: started.status,
|
|
984
|
+
summary: started.summary,
|
|
985
|
+
pollUrl: pollPath,
|
|
986
|
+
...(playgroundUrl === undefined ? {} : { playgroundUrl }),
|
|
987
|
+
})}\n`
|
|
988
|
+
);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const durable = started.config.durableRuns === true ? " (persisted)" : "";
|
|
992
|
+
process.stderr.write(
|
|
993
|
+
`Eval ID: ${started.runId}${durable} — ${started.summary.total} case(s)\n`
|
|
994
|
+
);
|
|
995
|
+
process.stderr.write(
|
|
996
|
+
`Cancel: ${CLI_COMMAND_NAME} eval cancel ${started.runId}${statusFlags}\n`
|
|
997
|
+
);
|
|
998
|
+
process.stderr.write(
|
|
999
|
+
`Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
|
|
1000
|
+
);
|
|
1001
|
+
if (playgroundUrl !== undefined) {
|
|
1002
|
+
process.stderr.write(`Playground: ${playgroundUrl}\n`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
947
1006
|
/** `--no-wait`: print the accepted batch id (and how to poll) then exit. */
|
|
948
|
-
function printEvalAccepted(
|
|
1007
|
+
async function printEvalAccepted(
|
|
949
1008
|
started: EvalRunSnapshot,
|
|
950
1009
|
options: AxCliOptions,
|
|
951
1010
|
agentUrl: string
|
|
952
|
-
): number {
|
|
1011
|
+
): Promise<number> {
|
|
953
1012
|
const pollPath = `${agentUrl.replace(/\/$/, "")}/v1/dev/evals/runs/${started.runId}`;
|
|
954
1013
|
const statusFlags = formatEvalStatusFlags(options);
|
|
1014
|
+
const playgroundUrl = await resolveEvalPlaygroundDeepLink(
|
|
1015
|
+
options,
|
|
1016
|
+
started.runId,
|
|
1017
|
+
agentUrl
|
|
1018
|
+
);
|
|
955
1019
|
if (options.json) {
|
|
956
1020
|
console.log(
|
|
957
1021
|
JSON.stringify(
|
|
958
1022
|
{
|
|
959
1023
|
ok: true,
|
|
960
1024
|
accepted: true,
|
|
1025
|
+
evalId: started.runId,
|
|
961
1026
|
runId: started.runId,
|
|
962
1027
|
status: started.status,
|
|
963
1028
|
summary: started.summary,
|
|
964
1029
|
pollUrl: pollPath,
|
|
965
1030
|
durableRuns: started.config.durableRuns === true,
|
|
1031
|
+
...(playgroundUrl === undefined ? {} : { playgroundUrl }),
|
|
966
1032
|
},
|
|
967
1033
|
null,
|
|
968
1034
|
2
|
|
@@ -971,16 +1037,126 @@ function printEvalAccepted(
|
|
|
971
1037
|
} else {
|
|
972
1038
|
const durable = started.config.durableRuns === true ? " (persisted)" : "";
|
|
973
1039
|
process.stdout.write(
|
|
974
|
-
`
|
|
1040
|
+
`Eval ID: ${started.runId}${durable} — ${started.summary.total} case(s)\n`
|
|
1041
|
+
);
|
|
1042
|
+
process.stdout.write(
|
|
1043
|
+
`Cancel: ${CLI_COMMAND_NAME} eval cancel ${started.runId}${statusFlags}\n`
|
|
975
1044
|
);
|
|
976
1045
|
process.stdout.write(
|
|
977
|
-
`Status:
|
|
1046
|
+
`Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
|
|
978
1047
|
);
|
|
1048
|
+
if (playgroundUrl !== undefined) {
|
|
1049
|
+
process.stdout.write(`Playground: ${playgroundUrl}\n`);
|
|
1050
|
+
}
|
|
979
1051
|
process.stdout.write(`Poll: ${pollPath}\n`);
|
|
980
1052
|
}
|
|
981
1053
|
return 0;
|
|
982
1054
|
}
|
|
983
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* Hosted alias playground URL with `view=evals` + `evalRunId` so the SPA
|
|
1058
|
+
* opens the batch that CLI / Slack started (not a stale sessionStorage id).
|
|
1059
|
+
*/
|
|
1060
|
+
async function resolveEvalPlaygroundDeepLink(
|
|
1061
|
+
options: AxCliOptions,
|
|
1062
|
+
runId: string,
|
|
1063
|
+
agentUrl: string
|
|
1064
|
+
): Promise<string | undefined> {
|
|
1065
|
+
if (options.prod !== true) {
|
|
1066
|
+
const base = `${agentUrl.replace(/\/$/, "")}/playground`;
|
|
1067
|
+
try {
|
|
1068
|
+
const url = new URL(base);
|
|
1069
|
+
url.searchParams.set("view", "evals");
|
|
1070
|
+
url.searchParams.set("evalRunId", runId);
|
|
1071
|
+
return url.toString();
|
|
1072
|
+
} catch {
|
|
1073
|
+
return undefined;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
const resolved = await resolveProdAliasPlaygroundUrl({
|
|
1078
|
+
dir: options.dir,
|
|
1079
|
+
cwd: options.cwd,
|
|
1080
|
+
slug: options.slug,
|
|
1081
|
+
team: options.team,
|
|
1082
|
+
apiKey: options.apiKey,
|
|
1083
|
+
fetchImpl: options.fetchImpl,
|
|
1084
|
+
// Avoid a second "Using hosted deployment" line; banner already has context.
|
|
1085
|
+
err: () => {},
|
|
1086
|
+
});
|
|
1087
|
+
const url = new URL(resolved.playgroundUrl);
|
|
1088
|
+
url.searchParams.set("view", "evals");
|
|
1089
|
+
url.searchParams.set("evalRunId", runId);
|
|
1090
|
+
return url.toString();
|
|
1091
|
+
} catch {
|
|
1092
|
+
return undefined;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* `eval cancel <runId>` — cancel a running remote batch by Eval ID.
|
|
1098
|
+
*/
|
|
1099
|
+
export async function cmdEvalCancel(
|
|
1100
|
+
runId: string | undefined,
|
|
1101
|
+
options: AxCliOptions
|
|
1102
|
+
): Promise<number> {
|
|
1103
|
+
const mode = resolveEvalTargetMode(options);
|
|
1104
|
+
if (typeof mode === "object") {
|
|
1105
|
+
process.stderr.write(`${mode.error}\n`);
|
|
1106
|
+
return 2;
|
|
1107
|
+
}
|
|
1108
|
+
if (mode === "local") {
|
|
1109
|
+
process.stderr.write(
|
|
1110
|
+
"eval cancel requires --prod or --url (not the default local target)\n"
|
|
1111
|
+
);
|
|
1112
|
+
return 2;
|
|
1113
|
+
}
|
|
1114
|
+
if (runId === undefined || runId === "") {
|
|
1115
|
+
process.stderr.write("usage: eval cancel <evalId> --prod|--url …\n");
|
|
1116
|
+
return 2;
|
|
1117
|
+
}
|
|
1118
|
+
if (options.list === true || options.noWait === true) {
|
|
1119
|
+
process.stderr.write("eval cancel does not accept --list or --no-wait\n");
|
|
1120
|
+
return 2;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const target = await resolveEvalTarget(options);
|
|
1124
|
+
if (typeof target === "number") {
|
|
1125
|
+
return target;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
try {
|
|
1129
|
+
const snap = await cancelRemoteEvalRun({
|
|
1130
|
+
baseUrl: target.agentUrl,
|
|
1131
|
+
runId,
|
|
1132
|
+
auth: {
|
|
1133
|
+
bearerToken: options.bearerToken,
|
|
1134
|
+
headers: target.headers,
|
|
1135
|
+
},
|
|
1136
|
+
fetchImpl: options.fetchImpl,
|
|
1137
|
+
});
|
|
1138
|
+
if (options.json) {
|
|
1139
|
+
console.log(JSON.stringify(snap, null, 2));
|
|
1140
|
+
} else {
|
|
1141
|
+
process.stdout.write(
|
|
1142
|
+
`Cancelled Eval ID ${snap.runId} (${snap.summary.done}/${snap.summary.total} cases finalized)\n`
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
return 0;
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
process.stderr.write(
|
|
1148
|
+
`${sanitizeCustomerError(
|
|
1149
|
+
error instanceof Error ? error.message : String(error)
|
|
1150
|
+
)}\n`
|
|
1151
|
+
);
|
|
1152
|
+
return 1;
|
|
1153
|
+
} finally {
|
|
1154
|
+
if (target.close !== undefined) {
|
|
1155
|
+
await target.close();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
984
1160
|
/**
|
|
985
1161
|
* `eval status [runId]` — fetch one batch (or list recent) on the deployment /
|
|
986
1162
|
* `--url` target. Exit `0` when the batch is completed with all cases passing,
|
|
@@ -1068,7 +1244,9 @@ export async function cmdEvalStatus(
|
|
|
1068
1244
|
return 3;
|
|
1069
1245
|
}
|
|
1070
1246
|
const failed =
|
|
1071
|
-
snap.status === "failed" ||
|
|
1247
|
+
snap.status === "failed" ||
|
|
1248
|
+
snap.status === "cancelled" ||
|
|
1249
|
+
snap.cases.some((c) => c.ok !== true);
|
|
1072
1250
|
return failed ? 1 : 0;
|
|
1073
1251
|
} catch (error) {
|
|
1074
1252
|
process.stderr.write(
|
|
@@ -1146,16 +1324,11 @@ function createRemoteEvalProgress(
|
|
|
1146
1324
|
if (!enabled) {
|
|
1147
1325
|
return undefined;
|
|
1148
1326
|
}
|
|
1149
|
-
let announced = false;
|
|
1150
1327
|
let lastSummaryKey = "";
|
|
1151
1328
|
const seenCaseKeys = new Set<string>();
|
|
1152
1329
|
return {
|
|
1153
1330
|
onSnapshot(snap) {
|
|
1154
|
-
|
|
1155
|
-
announced = true;
|
|
1156
|
-
const durable = snap.config.durableRuns === true ? " (persisted)" : "";
|
|
1157
|
-
process.stderr.write(`eval run: ${snap.runId}${durable}\n`);
|
|
1158
|
-
}
|
|
1331
|
+
// Eval ID is printed by printEvalIdBanner / printEvalAccepted.
|
|
1159
1332
|
const summaryKey = `${snap.status}:${snap.summary.done}:${snap.summary.passed}:${snap.summary.failed}`;
|
|
1160
1333
|
if (summaryKey !== lastSummaryKey) {
|
|
1161
1334
|
lastSummaryKey = summaryKey;
|
|
@@ -1239,6 +1412,9 @@ function printEvalResults(
|
|
|
1239
1412
|
}\n`
|
|
1240
1413
|
);
|
|
1241
1414
|
}
|
|
1415
|
+
for (const line of r.logs ?? []) {
|
|
1416
|
+
process.stdout.write(` ${line}\n`);
|
|
1417
|
+
}
|
|
1242
1418
|
if (r.error !== undefined) {
|
|
1243
1419
|
process.stdout.write(` error: ${r.error}\n`);
|
|
1244
1420
|
}
|
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { basename, resolve } from "node:path";
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
type AgentHostingOptions,
|
|
15
|
+
isCursorAccountTransport,
|
|
16
|
+
} from "../types.js";
|
|
14
17
|
import { CursorBackendClient } from "./cursor/backend-client.js";
|
|
15
18
|
import {
|
|
16
19
|
cursorExternalApiUrl,
|
|
17
20
|
resolveApiKey,
|
|
18
21
|
SIGN_IN_HINT,
|
|
19
22
|
} from "./cursor/credentials.js";
|
|
23
|
+
import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
|
|
20
24
|
import {
|
|
21
25
|
AgentServeDeployClient,
|
|
22
26
|
type DeployAccepted,
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
type DeploymentDetail,
|
|
26
30
|
type DeploymentStatus,
|
|
27
31
|
type DeploySource,
|
|
32
|
+
deploymentServiceAccountId,
|
|
28
33
|
type SecretInfo,
|
|
29
34
|
type StopAccepted,
|
|
30
35
|
slugFromDirectoryName,
|
|
@@ -97,6 +102,11 @@ export interface DeployCliOptions {
|
|
|
97
102
|
allowDomains?: string[];
|
|
98
103
|
/** Test seams. */
|
|
99
104
|
fetchImpl?: typeof fetch;
|
|
105
|
+
/** Inject the connector-auth browser opener (tests; also forces interactive). */
|
|
106
|
+
openBrowserImpl?: (url: string) => void;
|
|
107
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
108
|
+
connectorPollIntervalMs?: number;
|
|
109
|
+
connectorPollTimeoutMs?: number;
|
|
100
110
|
/** Inject git probing for deploy source inference (tests). */
|
|
101
111
|
git?: GitRunner;
|
|
102
112
|
/** Inject the multi-agent picker (tests / non-interactive hosts). */
|
|
@@ -118,6 +128,8 @@ export type SecretInputStream = NodeJS.ReadableStream & {
|
|
|
118
128
|
interface DeployCliContext {
|
|
119
129
|
client: AgentServeDeployClient;
|
|
120
130
|
teamId: number;
|
|
131
|
+
apiKey: string;
|
|
132
|
+
backendUrl: string;
|
|
121
133
|
json: boolean;
|
|
122
134
|
noWait: boolean;
|
|
123
135
|
pollIntervalMs: number;
|
|
@@ -137,6 +149,9 @@ export interface ResolveDeployAuthOptions {
|
|
|
137
149
|
export interface ResolvedDeployAuth {
|
|
138
150
|
client: AgentServeDeployClient;
|
|
139
151
|
teamId: number;
|
|
152
|
+
/** The resolved account credential, for dashboard RPCs beside the deploy API. */
|
|
153
|
+
apiKey: string;
|
|
154
|
+
backendUrl: string;
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
/**
|
|
@@ -205,7 +220,12 @@ export async function resolveDeployAuth(
|
|
|
205
220
|
);
|
|
206
221
|
}
|
|
207
222
|
|
|
208
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
client,
|
|
225
|
+
teamId,
|
|
226
|
+
apiKey: resolved.apiKey,
|
|
227
|
+
backendUrl: resolved.backendUrl,
|
|
228
|
+
};
|
|
209
229
|
}
|
|
210
230
|
|
|
211
231
|
/**
|
|
@@ -233,6 +253,8 @@ async function prepare(
|
|
|
233
253
|
return {
|
|
234
254
|
client: auth.client,
|
|
235
255
|
teamId: auth.teamId,
|
|
256
|
+
apiKey: auth.apiKey,
|
|
257
|
+
backendUrl: auth.backendUrl,
|
|
236
258
|
json: options.json === true,
|
|
237
259
|
noWait: options.noWait === true,
|
|
238
260
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
@@ -604,8 +626,10 @@ async function deployOne(
|
|
|
604
626
|
// The package manifest (`defineAgent({ hosting })`) is the source of
|
|
605
627
|
// truth for egress domains and expected secret names; flags override or
|
|
606
628
|
// augment it. Directories without an agent project (static placeholder
|
|
607
|
-
// deploys) simply have no manifest.
|
|
608
|
-
|
|
629
|
+
// deploys) simply have no manifest. The same load supplies the
|
|
630
|
+
// cursorAccount connection refs for the post-deploy connector auth.
|
|
631
|
+
const manifest = isAgent ? await readDeployManifest(dir) : undefined;
|
|
632
|
+
const hosting = manifest?.hosting;
|
|
609
633
|
const egressAllowedDomains = [
|
|
610
634
|
...new Set([
|
|
611
635
|
...(hosting?.egressDomains ?? []),
|
|
@@ -695,22 +719,125 @@ async function deployOne(
|
|
|
695
719
|
` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
|
|
696
720
|
` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
|
|
697
721
|
);
|
|
722
|
+
|
|
723
|
+
await runPostDeployConnectorAuth(ctx, options, {
|
|
724
|
+
slug,
|
|
725
|
+
connections: manifest?.cursorAccountConnections ?? [],
|
|
726
|
+
serviceAccountId: deploymentServiceAccountId(detail),
|
|
727
|
+
});
|
|
698
728
|
return 0;
|
|
699
729
|
}
|
|
700
730
|
|
|
701
731
|
/**
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
732
|
+
* Post-deploy connector authorization for `cursorAccount` connections: the
|
|
733
|
+
* agent's connectors execute with tokens owned by the deployment's service
|
|
734
|
+
* account, which start out unauthorized. On a TTY this runs the dashboard's
|
|
735
|
+
* Authenticate flow inline (browser OAuth per connector, already-authorized
|
|
736
|
+
* ones skipped); otherwise it reports status only. Never affects the deploy
|
|
737
|
+
* outcome — anything left unauthorized just gets a playground-page pointer.
|
|
705
738
|
*/
|
|
706
|
-
async function
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
739
|
+
async function runPostDeployConnectorAuth(
|
|
740
|
+
ctx: DeployCliContext,
|
|
741
|
+
options: DeployCliOptions,
|
|
742
|
+
args: {
|
|
743
|
+
slug: string;
|
|
744
|
+
connections: DeployManifest["cursorAccountConnections"];
|
|
745
|
+
serviceAccountId: string | undefined;
|
|
746
|
+
}
|
|
747
|
+
): Promise<void> {
|
|
748
|
+
if (args.connections.length === 0) {
|
|
749
|
+
return;
|
|
711
750
|
}
|
|
751
|
+
|
|
752
|
+
const { serviceAccountId } = args;
|
|
753
|
+
if (serviceAccountId === undefined) {
|
|
754
|
+
ctx.err(
|
|
755
|
+
`warning: this agent uses Cursor account connectors, but the deployment has no service account; redeploy to mint one.\n`
|
|
756
|
+
);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const backendClient = new CursorBackendClient({
|
|
761
|
+
backendUrl: ctx.backendUrl,
|
|
762
|
+
apiKey: ctx.apiKey,
|
|
763
|
+
fetchImpl: options.fetchImpl,
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
const interactive =
|
|
767
|
+
options.openBrowserImpl !== undefined ||
|
|
768
|
+
(process.stdout.isTTY === true && process.stdin.isTTY === true);
|
|
769
|
+
|
|
770
|
+
let anyUnauthorized = false;
|
|
771
|
+
for (const ref of args.connections) {
|
|
772
|
+
ctx.out(
|
|
773
|
+
`\nThis agent uses Cursor account connectors (connection "${ref.name}"${
|
|
774
|
+
ref.servers === undefined || ref.servers.length === 0
|
|
775
|
+
? ""
|
|
776
|
+
: `: ${ref.servers.join(", ")}`
|
|
777
|
+
}).\n`
|
|
778
|
+
);
|
|
779
|
+
try {
|
|
780
|
+
const result = await authorizeCursorAccountConnectors({
|
|
781
|
+
backendClient,
|
|
782
|
+
teamId: ctx.teamId,
|
|
783
|
+
slug: args.slug,
|
|
784
|
+
serviceAccountId,
|
|
785
|
+
connectionName: ref.name,
|
|
786
|
+
servers: ref.servers,
|
|
787
|
+
interactive,
|
|
788
|
+
out: ctx.out,
|
|
789
|
+
err: ctx.err,
|
|
790
|
+
openBrowserImpl: options.openBrowserImpl,
|
|
791
|
+
sleep: options.sleepImpl,
|
|
792
|
+
pollIntervalMs: options.connectorPollIntervalMs,
|
|
793
|
+
pollTimeoutMs: options.connectorPollTimeoutMs,
|
|
794
|
+
});
|
|
795
|
+
if (result.pending.length > 0 || result.errored.length > 0) {
|
|
796
|
+
anyUnauthorized = true;
|
|
797
|
+
if (result.pending.length > 0) {
|
|
798
|
+
ctx.out(
|
|
799
|
+
`Connectors not authorized yet: ${result.pending.join(", ")}.\n`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} catch (error) {
|
|
804
|
+
anyUnauthorized = true;
|
|
805
|
+
ctx.err(
|
|
806
|
+
`warning: could not check connector authorization for "${ref.name}": ${describeError(error)}\n`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (anyUnauthorized) {
|
|
812
|
+
ctx.out(
|
|
813
|
+
`You can authorize connectors at the playground page (cursor.com dashboard → Deployed Agents → ${args.slug}) or with \`${CLI} mcp oauth <connection>\`.\n`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
interface DeployManifest {
|
|
819
|
+
hosting: AgentHostingOptions | undefined;
|
|
820
|
+
/** `cursorAccount` connections declared by the agent. */
|
|
821
|
+
cursorAccountConnections: Array<{
|
|
822
|
+
name: string;
|
|
823
|
+
servers: string[] | undefined;
|
|
824
|
+
}>;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Read the deploy-relevant bits of the project manifest at `dir` in one
|
|
829
|
+
* load: the `hosting` block plus the agent's cursorAccount connections.
|
|
830
|
+
*/
|
|
831
|
+
async function readDeployManifest(dir: string): Promise<DeployManifest> {
|
|
712
832
|
const project = await loadAgentProject(dir);
|
|
713
|
-
return
|
|
833
|
+
return {
|
|
834
|
+
hosting: project.agent.hosting,
|
|
835
|
+
cursorAccountConnections: project.agent.connections.flatMap((connection) =>
|
|
836
|
+
isCursorAccountTransport(connection.transport)
|
|
837
|
+
? [{ name: connection.name, servers: connection.transport.servers }]
|
|
838
|
+
: []
|
|
839
|
+
),
|
|
840
|
+
};
|
|
714
841
|
}
|
|
715
842
|
|
|
716
843
|
/**
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agentkit mcp oauth <connection> [--store]
|
|
2
|
+
* `agentkit mcp oauth <connection> [--store]`.
|
|
3
|
+
*
|
|
4
|
+
* Host MCP OAuth for `defineConnection({ url, oauth: true })` (local tokens,
|
|
5
|
+
* optionally stored as deployment secrets), and service-account connector
|
|
6
|
+
* authorization for `defineConnection({ cursorAccount: true })` (tokens
|
|
7
|
+
* stored backend-side against the hosted deployment's service account — see
|
|
8
|
+
* `cursor-account-mcp-auth.ts`).
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
11
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
@@ -7,7 +13,11 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
7
13
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8
14
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
15
|
import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
16
|
+
import { isCursorAccountTransport } from "../types.js";
|
|
10
17
|
import { resolveDeployAuth } from "./cli-deploy.js";
|
|
18
|
+
import { CursorBackendClient } from "./cursor/backend-client.js";
|
|
19
|
+
import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
|
|
20
|
+
import { deploymentServiceAccountId } from "./deploy-client.js";
|
|
11
21
|
import { loadAgentProject } from "./discovery.js";
|
|
12
22
|
import { CLI_COMMAND_NAME as CLI, LOG_PREFIX } from "./distribution.js";
|
|
13
23
|
import {
|
|
@@ -22,13 +32,19 @@ import { resolveProdSlug } from "./resolve-prod-target.js";
|
|
|
22
32
|
export type McpOauthCliOptions = {
|
|
23
33
|
dir: string;
|
|
24
34
|
connection: string;
|
|
25
|
-
/** Upsert MCP_OAUTH_* on the hosted deployment. */
|
|
35
|
+
/** Upsert MCP_OAUTH_* on the hosted deployment (host-OAuth connections only). */
|
|
26
36
|
store?: boolean;
|
|
27
37
|
slug?: string;
|
|
28
38
|
team?: string;
|
|
29
39
|
apiKey?: string;
|
|
30
40
|
err?: (text: string) => void;
|
|
31
41
|
out?: (text: string) => void;
|
|
42
|
+
/** Test seams. */
|
|
43
|
+
fetchImpl?: typeof fetch;
|
|
44
|
+
openBrowserImpl?: (url: string) => void;
|
|
45
|
+
sleep?: (ms: number) => Promise<void>;
|
|
46
|
+
pollIntervalMs?: number;
|
|
47
|
+
pollTimeoutMs?: number;
|
|
32
48
|
};
|
|
33
49
|
|
|
34
50
|
function describe(error: unknown): string {
|
|
@@ -66,9 +82,16 @@ export async function cmdMcpOauth(
|
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
const transport = connection.transport;
|
|
85
|
+
if (isCursorAccountTransport(transport)) {
|
|
86
|
+
return cmdMcpOauthCursorAccount(options, {
|
|
87
|
+
servers: transport.servers,
|
|
88
|
+
out,
|
|
89
|
+
err,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
69
92
|
if (!("url" in transport) || transport.oauth !== true) {
|
|
70
93
|
err(
|
|
71
|
-
`${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }).\n`
|
|
94
|
+
`${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }) or defineConnection({ cursorAccount: true }).\n`
|
|
72
95
|
);
|
|
73
96
|
return 1;
|
|
74
97
|
}
|
|
@@ -184,6 +207,102 @@ export async function cmdMcpOauth(
|
|
|
184
207
|
});
|
|
185
208
|
}
|
|
186
209
|
|
|
210
|
+
/**
|
|
211
|
+
* `mcp oauth` for a `cursorAccount` connection: authorize the hosted
|
|
212
|
+
* deployment's service account for the connection's connectors. Tokens are
|
|
213
|
+
* stored backend-side by the cursor.com OAuth callback, so `--store` (a
|
|
214
|
+
* deployment-secrets concept) is a no-op here.
|
|
215
|
+
*/
|
|
216
|
+
async function cmdMcpOauthCursorAccount(
|
|
217
|
+
options: McpOauthCliOptions,
|
|
218
|
+
args: {
|
|
219
|
+
servers: string[] | undefined;
|
|
220
|
+
out: (text: string) => void;
|
|
221
|
+
err: (text: string) => void;
|
|
222
|
+
}
|
|
223
|
+
): Promise<number> {
|
|
224
|
+
const { out, err } = args;
|
|
225
|
+
|
|
226
|
+
let slug: string;
|
|
227
|
+
try {
|
|
228
|
+
slug = resolveProdSlug({ dir: options.dir, slug: options.slug });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
err(`${LOG_PREFIX} ${describe(error)}\n`);
|
|
231
|
+
return 1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const auth = await resolveDeployAuth({
|
|
235
|
+
team: options.team,
|
|
236
|
+
apiKey: options.apiKey,
|
|
237
|
+
fetchImpl: options.fetchImpl,
|
|
238
|
+
err,
|
|
239
|
+
});
|
|
240
|
+
if (typeof auth === "number") {
|
|
241
|
+
return auth;
|
|
242
|
+
}
|
|
243
|
+
const backendClient = new CursorBackendClient({
|
|
244
|
+
backendUrl: auth.backendUrl,
|
|
245
|
+
apiKey: auth.apiKey,
|
|
246
|
+
fetchImpl: options.fetchImpl,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
let serviceAccountId: string | undefined;
|
|
250
|
+
try {
|
|
251
|
+
const detail = await auth.client.getDeployment({
|
|
252
|
+
teamId: auth.teamId,
|
|
253
|
+
slug,
|
|
254
|
+
});
|
|
255
|
+
serviceAccountId = deploymentServiceAccountId(detail);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
err(
|
|
258
|
+
`${LOG_PREFIX} Could not load deployment "${slug}" (team ${auth.teamId}): ${describe(error)}\n` +
|
|
259
|
+
`${LOG_PREFIX} Deploy first with \`${CLI} deploy\`.\n`
|
|
260
|
+
);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
if (serviceAccountId === undefined) {
|
|
264
|
+
err(
|
|
265
|
+
`${LOG_PREFIX} Deployment "${slug}" has no service account yet (it predates service-account-backed deploys). Redeploy with \`${CLI} deploy\` and rerun.\n`
|
|
266
|
+
);
|
|
267
|
+
return 1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (options.store === true) {
|
|
271
|
+
out(
|
|
272
|
+
`${LOG_PREFIX} Note: --store is not needed for cursorAccount connections — tokens are stored on the Cursor backend against the deployment's service account.\n`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
out(
|
|
277
|
+
`${LOG_PREFIX} Authorizing connection "${options.connection}" for ${slug}'s service account…\n`
|
|
278
|
+
);
|
|
279
|
+
const result = await authorizeCursorAccountConnectors({
|
|
280
|
+
backendClient,
|
|
281
|
+
teamId: auth.teamId,
|
|
282
|
+
slug,
|
|
283
|
+
serviceAccountId,
|
|
284
|
+
connectionName: options.connection,
|
|
285
|
+
servers: args.servers,
|
|
286
|
+
interactive: true,
|
|
287
|
+
out,
|
|
288
|
+
err,
|
|
289
|
+
openBrowserImpl: options.openBrowserImpl,
|
|
290
|
+
sleep: options.sleep,
|
|
291
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
292
|
+
pollTimeoutMs: options.pollTimeoutMs,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (result.pending.length > 0 || result.errored.length > 0) {
|
|
296
|
+
err(
|
|
297
|
+
`${LOG_PREFIX} You can authorize connectors any time from the playground page ` +
|
|
298
|
+
`(cursor.com dashboard → Deployed Agents → ${slug}) or by rerunning ` +
|
|
299
|
+
`\`${CLI} mcp oauth ${options.connection}\`.\n`
|
|
300
|
+
);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
187
306
|
async function storeOauthSecrets(args: {
|
|
188
307
|
connection: string;
|
|
189
308
|
dir: string;
|