@cursor/july 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/bin/agent-serve.js +0 -0
  3. package/dist/channels/github/instrument.d.ts +20 -0
  4. package/dist/channels/github/instrument.d.ts.map +1 -0
  5. package/dist/channels/slack/api.d.ts +2 -0
  6. package/dist/channels/slack/api.d.ts.map +1 -1
  7. package/dist/channels/slack/api.js +3 -0
  8. package/dist/channels/slack/bot-mentions.d.ts +106 -0
  9. package/dist/channels/slack/bot-mentions.d.ts.map +1 -0
  10. package/dist/channels/slack/bot-mentions.js +243 -0
  11. package/dist/channels/slack/dispatch.d.ts +6 -0
  12. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  13. package/dist/channels/slack/dispatch.js +24 -3
  14. package/dist/channels/slack/inbound.d.ts +10 -1
  15. package/dist/channels/slack/inbound.d.ts.map +1 -1
  16. package/dist/channels/slack/inbound.js +12 -4
  17. package/dist/channels/slack/index.d.ts +1 -0
  18. package/dist/channels/slack/index.d.ts.map +1 -1
  19. package/dist/channels/slack/index.js +1 -0
  20. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  21. package/dist/channels/slack/slack-channel.js +61 -22
  22. package/dist/channels/slack/types.d.ts +58 -0
  23. package/dist/channels/slack/types.d.ts.map +1 -1
  24. package/dist/docs/404.html +1 -1
  25. package/dist/docs/ab.html +2 -2
  26. package/dist/docs/assets/{app.CrsWMchO.js → app.jDxLzWv4.js} +1 -1
  27. package/dist/docs/assets/chunks/@localSearchIndexroot.DoJHJjqF.js +1 -0
  28. package/dist/docs/assets/chunks/{VPLocalSearchBox.D1JqzSh8.js → VPLocalSearchBox.Y6bDR1-a.js} +1 -1
  29. package/dist/docs/assets/chunks/{theme.DaBvZYwl.js → theme.CLazCWlJ.js} +2 -2
  30. package/dist/docs/building-with-agents.html +2 -2
  31. package/dist/docs/concepts.html +2 -2
  32. package/dist/docs/deployment.html +2 -2
  33. package/dist/docs/evals.html +2 -2
  34. package/dist/docs/example-agents/approval-buddy.html +2 -2
  35. package/dist/docs/example-agents/benny.html +2 -2
  36. package/dist/docs/example-agents/bugbot.html +2 -2
  37. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  38. package/dist/docs/example-agents/codeowners-review.html +2 -2
  39. package/dist/docs/example-agents/concierge.html +2 -2
  40. package/dist/docs/example-agents/fsd.html +2 -2
  41. package/dist/docs/example-agents/index.html +2 -2
  42. package/dist/docs/example-agents/knowledge-base.html +2 -2
  43. package/dist/docs/example-agents/oncall.html +2 -2
  44. package/dist/docs/example-agents/security-reviewer.html +2 -2
  45. package/dist/docs/example-agents/slack-agent.html +2 -2
  46. package/dist/docs/example-agents/weather-agent.html +2 -2
  47. package/dist/docs/guides/agent-to-agent.html +2 -2
  48. package/dist/docs/guides/cloud-runtime.html +2 -2
  49. package/dist/docs/guides/github.html +2 -2
  50. package/dist/docs/guides/human-in-the-loop.html +2 -2
  51. package/dist/docs/guides/mcp-oauth.html +2 -2
  52. package/dist/docs/guides/slack.html +2 -2
  53. package/dist/docs/guides/webhooks.html +2 -2
  54. package/dist/docs/hillclimbing.html +2 -2
  55. package/dist/docs/index.html +2 -2
  56. package/dist/docs/quickstart.html +2 -2
  57. package/dist/docs/reference/agent-config.html +2 -2
  58. package/dist/docs/reference/channels.html +2 -2
  59. package/dist/docs/reference/cli.html +2 -2
  60. package/dist/docs/reference/connections.html +2 -2
  61. package/dist/docs/reference/hooks.html +2 -2
  62. package/dist/docs/reference/http-api.html +2 -2
  63. package/dist/docs/reference/instructions.html +2 -2
  64. package/dist/docs/reference/playground.html +2 -2
  65. package/dist/docs/reference/project-layout.html +2 -2
  66. package/dist/docs/reference/prompt.html +2 -2
  67. package/dist/docs/reference/schedules.html +2 -2
  68. package/dist/docs/reference/sessions.html +2 -2
  69. package/dist/docs/reference/skills.html +2 -2
  70. package/dist/docs/reference/subagents.html +2 -2
  71. package/dist/docs/reference/tools.html +2 -2
  72. package/dist/docs/scaffolding-agents.html +2 -2
  73. package/dist/docs/storage.html +2 -2
  74. package/dist/docs/troubleshooting.html +2 -2
  75. package/dist/internal/json-dir-store.d.ts +32 -0
  76. package/dist/internal/json-dir-store.d.ts.map +1 -0
  77. package/dist/internal/json-dir-store.js +100 -0
  78. package/dist/internal/resolved-connections.d.ts +1 -1
  79. package/dist/internal/resolved-connections.d.ts.map +1 -1
  80. package/dist/internal/resolved-connections.js +10 -0
  81. package/dist/internal/session-engine.d.ts +4 -1
  82. package/dist/internal/session-engine.d.ts.map +1 -1
  83. package/dist/internal/session-engine.js +13 -2
  84. package/dist/playground/assets/index-CjOQ4hN9.css +1 -0
  85. package/dist/playground/assets/{index-C2SU2xV5.js → index-dshZQJCp.js} +46 -46
  86. package/dist/playground/index.html +2 -2
  87. package/dist/types.d.ts +7 -0
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/types.js +9 -0
  90. package/package.json +25 -25
  91. package/skills/create-agent/SKILL.md +36 -4
  92. package/skills/evals/SKILL.md +3 -0
  93. package/skills/framework-map/SKILL.md +14 -0
  94. package/skills/github/SKILL.md +6 -1
  95. package/skills/hillclimb/SKILL.md +28 -7
  96. package/src/bin/agent-serve.version.test.ts +62 -0
  97. package/src/channels/github/api.test.ts +64 -0
  98. package/src/channels/github/auth.test.ts +105 -0
  99. package/src/channels/github/cursor-account.test.ts +204 -0
  100. package/src/channels/github/forward.test.ts +457 -0
  101. package/src/channels/github/github.test.ts +937 -0
  102. package/src/channels/github/replay.test.ts +179 -0
  103. package/src/channels/slack/api.post-message.test.ts +148 -0
  104. package/src/channels/slack/api.ts +5 -0
  105. package/src/channels/slack/approvals.test.ts +328 -0
  106. package/src/channels/slack/block-actions.test.ts +452 -0
  107. package/src/channels/slack/bot-mentions.test.ts +217 -0
  108. package/src/channels/slack/bot-mentions.ts +315 -0
  109. package/src/channels/slack/channel-watch.test.ts +363 -0
  110. package/src/channels/slack/cursor-account.test.ts +253 -0
  111. package/src/channels/slack/defaults.final-post.test.ts +182 -0
  112. package/src/channels/slack/dispatch.test.ts +795 -0
  113. package/src/channels/slack/dispatch.ts +27 -1
  114. package/src/channels/slack/eval-directive.test.ts +273 -0
  115. package/src/channels/slack/inbound.ts +25 -4
  116. package/src/channels/slack/index.ts +1 -0
  117. package/src/channels/slack/message-body.test.ts +54 -0
  118. package/src/channels/slack/nudge-store.test.ts +143 -0
  119. package/src/channels/slack/slack-channel.ts +66 -8
  120. package/src/channels/slack/slack.test.ts +391 -0
  121. package/src/channels/slack/stop.test.ts +23 -0
  122. package/src/channels/slack/thread-context.test.ts +202 -0
  123. package/src/channels/slack/types.ts +60 -0
  124. package/src/evals/assertions.test.ts +580 -0
  125. package/src/evals/expect.test.ts +144 -0
  126. package/src/evals/judge.test.ts +181 -0
  127. package/src/evals/loaders.test.ts +132 -0
  128. package/src/evals/matchers.test.ts +95 -0
  129. package/src/evals/reporters.test.ts +303 -0
  130. package/src/evals/run-facts.test.ts +259 -0
  131. package/src/internal/ab-snapshot.test.ts +325 -0
  132. package/src/internal/approval-gate.test.ts +49 -0
  133. package/src/internal/approvals.integration.test.ts +383 -0
  134. package/src/internal/authored-loaders.test.ts +31 -0
  135. package/src/internal/builtin-tools/reminders.test.ts +201 -0
  136. package/src/internal/channel-route-schema.test.ts +294 -0
  137. package/src/internal/chat-attach.test.ts +262 -0
  138. package/src/internal/cli-deploy.test.ts +1991 -0
  139. package/src/internal/cli-mcp.test.ts +789 -0
  140. package/src/internal/cli-skills.test.ts +133 -0
  141. package/src/internal/cli-slack.test.ts +1647 -0
  142. package/src/internal/cloud-merge.test.ts +74 -0
  143. package/src/internal/cron.test.ts +22 -0
  144. package/src/internal/cursor/account-mcp.test.ts +807 -0
  145. package/src/internal/cursor/backend-client.test.ts +591 -0
  146. package/src/internal/cursor/credentials.test.ts +351 -0
  147. package/src/internal/cursor/github-credentials.test.ts +136 -0
  148. package/src/internal/cursor-account-mcp-auth.test.ts +310 -0
  149. package/src/internal/cursor-account.integration.test.ts +441 -0
  150. package/src/internal/cursor-event-relay.test.ts +746 -0
  151. package/src/internal/cursor-github-credentials.integration.test.ts +271 -0
  152. package/src/internal/cursor-slack-relay.test.ts +525 -0
  153. package/src/internal/deploy-source.test.ts +111 -0
  154. package/src/internal/discovery.builtin-tools.test.ts +94 -0
  155. package/src/internal/discovery.concurrency.test.ts +60 -0
  156. package/src/internal/discovery.cursor-account.test.ts +133 -0
  157. package/src/internal/discovery.cwd.test.ts +83 -0
  158. package/src/internal/discovery.hosting.test.ts +80 -0
  159. package/src/internal/discovery.identity.test.ts +44 -0
  160. package/src/internal/docs-site.test.ts +66 -0
  161. package/src/internal/duration.test.ts +29 -0
  162. package/src/internal/eval-judge-model.test.ts +187 -0
  163. package/src/internal/eval-run-store.cancel.test.ts +142 -0
  164. package/src/internal/eval-run-store.storage.test.ts +211 -0
  165. package/src/internal/eval-runner.http.test.ts +403 -0
  166. package/src/internal/eval-runner.run.test.ts +928 -0
  167. package/src/internal/evals-client.test.ts +307 -0
  168. package/src/internal/event-mapper.test.ts +243 -0
  169. package/src/internal/github-fanout.test.ts +213 -0
  170. package/src/internal/handleAgentServeTrigger.test.ts +179 -0
  171. package/src/internal/host-kv.test.ts +82 -0
  172. package/src/internal/host-platforms.test.ts +126 -0
  173. package/src/internal/http-channel.test.ts +402 -0
  174. package/src/internal/init-project.test.ts +269 -0
  175. package/src/internal/install-cursor-skills.test.ts +262 -0
  176. package/src/internal/local-env.test.ts +100 -0
  177. package/src/internal/log-ring.test.ts +31 -0
  178. package/src/internal/logs-client.test.ts +350 -0
  179. package/src/internal/mcp-endpoint.test.ts +436 -0
  180. package/src/internal/mcp-host.test.ts +298 -0
  181. package/src/internal/mcp-oauth.test.ts +148 -0
  182. package/src/internal/net.test.ts +17 -0
  183. package/src/internal/peer-connections.test.ts +128 -0
  184. package/src/internal/peer-mcp.integration.test.ts +289 -0
  185. package/src/internal/playground/toolchain.test.ts +53 -0
  186. package/src/internal/playground-cli.test.ts +187 -0
  187. package/src/internal/playground-proxy.test.ts +376 -0
  188. package/src/internal/prompt-context.integration.test.ts +232 -0
  189. package/src/internal/prompt-context.test.ts +127 -0
  190. package/src/internal/reminder-runner.test.ts +390 -0
  191. package/src/internal/reminder-store.test.ts +53 -0
  192. package/src/internal/request-headers.test.ts +27 -0
  193. package/src/internal/resolve-prod-target.test.ts +787 -0
  194. package/src/internal/resolved-connections.test.ts +295 -0
  195. package/src/internal/resolved-connections.ts +18 -1
  196. package/src/internal/router.test.ts +57 -0
  197. package/src/internal/sdk-runner.test.ts +290 -0
  198. package/src/internal/session-engine.coalesce.test.ts +169 -0
  199. package/src/internal/session-engine.concurrency.test.ts +250 -0
  200. package/src/internal/session-engine.host-oauth-mcp.test.ts +110 -0
  201. package/src/internal/session-engine.interrupt.test.ts +577 -0
  202. package/src/internal/session-engine.storage.test.ts +547 -0
  203. package/src/internal/session-engine.ts +15 -1
  204. package/src/internal/session-urls.test.ts +28 -0
  205. package/src/internal/sessions-client.test.ts +518 -0
  206. package/src/internal/storage-coordinator.test.ts +517 -0
  207. package/src/internal/tool-call.test.ts +458 -0
  208. package/src/internal/tool-result.test.ts +52 -0
  209. package/src/internal/trajectory.approvals.test.ts +83 -0
  210. package/src/internal/trajectory.subagents.test.ts +198 -0
  211. package/src/internal/turn-governor.test.ts +137 -0
  212. package/src/internal/update-check.test.ts +485 -0
  213. package/src/internal/workspace.test.ts +81 -0
  214. package/src/storage-backends/cursor-hosted.test.ts +121 -0
  215. package/src/types.ts +12 -0
  216. package/dist/docs/assets/chunks/@localSearchIndexroot.BnHRjfoe.js +0 -1
  217. package/dist/playground/assets/index-CidizGZv.css +0 -1
@@ -0,0 +1,787 @@
1
+ import { once } from "node:events";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { createServer, type Server } from "node:http";
4
+ import type { AddressInfo } from "node:net";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import {
9
+ cmdCall,
10
+ cmdEval,
11
+ cmdEvalStatus,
12
+ cmdRun,
13
+ formatEvalStatusFlags,
14
+ resolveEvalTargetMode,
15
+ resolveTarget,
16
+ } from "./cli-ax.js";
17
+ import {
18
+ PROD_ENGINE_WAKE_HEADERS,
19
+ ProdTargetError,
20
+ resolveProdAliasPlaygroundUrl,
21
+ resolveProdSlug,
22
+ resolveProdTarget,
23
+ } from "./resolve-prod-target.js";
24
+
25
+ const API_KEY = "test-api-key";
26
+ const TEAM_ID = 42;
27
+
28
+ interface MockBackend {
29
+ url: string;
30
+ close: () => Promise<void>;
31
+ setDeployment: (slug: string, body: Record<string, unknown>) => void;
32
+ }
33
+
34
+ async function startMockBackend(): Promise<MockBackend> {
35
+ const deployments = new Map<string, Record<string, unknown>>();
36
+ const server: Server = createServer((req, res) => {
37
+ let raw = "";
38
+ req.on("data", (chunk: Buffer) => {
39
+ raw += chunk.toString("utf8");
40
+ });
41
+ req.on("end", () => {
42
+ void raw;
43
+ const url = new URL(req.url ?? "/", "http://localhost");
44
+ const send = (status: number, body: unknown): void => {
45
+ res.writeHead(status, { "content-type": "application/json" });
46
+ res.end(JSON.stringify(body));
47
+ };
48
+
49
+ if (url.pathname === "/auth/exchange_user_api_key") {
50
+ const claims = Buffer.from(
51
+ JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 3600 })
52
+ ).toString("base64url");
53
+ send(200, { accessToken: `header.${claims}.signature` });
54
+ return;
55
+ }
56
+ if (url.pathname === "/aiserver.v1.DashboardService/GetMe") {
57
+ send(200, {
58
+ authId: "auth0|1",
59
+ email: "dev@example.com",
60
+ teamId: TEAM_ID,
61
+ teamName: "Acme",
62
+ });
63
+ return;
64
+ }
65
+
66
+ const prefix = "/internal/agent-serve/deployments/";
67
+ if (
68
+ req.method === "GET" &&
69
+ url.pathname.startsWith(prefix) &&
70
+ req.headers.authorization === `Bearer ${API_KEY}`
71
+ ) {
72
+ const slug = decodeURIComponent(url.pathname.slice(prefix.length));
73
+ const detail = deployments.get(slug);
74
+ if (detail === undefined) {
75
+ send(404, { error: "Deployment not found" });
76
+ return;
77
+ }
78
+ send(200, detail);
79
+ return;
80
+ }
81
+
82
+ send(404, { error: "Not found" });
83
+ });
84
+ });
85
+
86
+ server.listen(0, "127.0.0.1");
87
+ await once(server, "listening");
88
+ const { port } = server.address() as AddressInfo;
89
+ return {
90
+ url: `http://127.0.0.1:${port}`,
91
+ setDeployment: (slug, body) => {
92
+ deployments.set(slug, body);
93
+ },
94
+ close: () =>
95
+ new Promise<void>((resolvePromise, reject) =>
96
+ server.close((error) => (error ? reject(error) : resolvePromise()))
97
+ ),
98
+ };
99
+ }
100
+
101
+ interface StubEngine {
102
+ url: string;
103
+ close: () => Promise<void>;
104
+ sessionHeaders: Array<Record<string, string | string[] | undefined>>;
105
+ toolHeaders: Array<Record<string, string | string[] | undefined>>;
106
+ evalRunHeaders: Array<Record<string, string | string[] | undefined>>;
107
+ evalRunBodies: unknown[];
108
+ }
109
+
110
+ async function startStubEngine(): Promise<StubEngine> {
111
+ const sessionHeaders: StubEngine["sessionHeaders"] = [];
112
+ const toolHeaders: StubEngine["toolHeaders"] = [];
113
+ const evalRunHeaders: StubEngine["evalRunHeaders"] = [];
114
+ const evalRunBodies: unknown[] = [];
115
+ const evalSnapshot = {
116
+ runId: "evalrun_test",
117
+ status: "completed" as const,
118
+ startedAt: "2026-07-20T00:00:00.000Z",
119
+ finishedAt: "2026-07-20T00:00:01.000Z",
120
+ summary: { passed: 1, failed: 0, total: 1, done: 1 },
121
+ cases: [
122
+ {
123
+ id: "smoke",
124
+ fileId: "smoke",
125
+ status: "done" as const,
126
+ ok: true,
127
+ assertions: [{ name: "succeeded", passed: true }],
128
+ durationMs: 10,
129
+ inputs: [],
130
+ logs: [],
131
+ },
132
+ ],
133
+ config: { maxConcurrency: 1, durableRuns: true },
134
+ };
135
+ const server: Server = createServer((req, res) => {
136
+ let raw = "";
137
+ req.on("data", (chunk: Buffer) => {
138
+ raw += chunk.toString("utf8");
139
+ });
140
+ req.on("end", () => {
141
+ const url = new URL(req.url ?? "/", "http://localhost");
142
+ const send = (status: number, body: unknown): void => {
143
+ res.writeHead(status, { "content-type": "application/json" });
144
+ res.end(JSON.stringify(body));
145
+ };
146
+ const sendNdjson = (lines: unknown[]): void => {
147
+ res.writeHead(200, { "content-type": "application/x-ndjson" });
148
+ res.end(`${lines.map((line) => JSON.stringify(line)).join("\n")}\n`);
149
+ };
150
+
151
+ if (req.method === "POST" && url.pathname === "/v1/dev/evals/runs") {
152
+ evalRunHeaders.push({ ...req.headers });
153
+ evalRunBodies.push(raw === "" ? {} : JSON.parse(raw));
154
+ send(202, {
155
+ ...evalSnapshot,
156
+ status: "running",
157
+ finishedAt: undefined,
158
+ });
159
+ return;
160
+ }
161
+ if (req.method === "GET" && url.pathname === "/v1/dev/evals/runs") {
162
+ send(200, { runs: [evalSnapshot], activeRunId: undefined });
163
+ return;
164
+ }
165
+ if (
166
+ req.method === "GET" &&
167
+ url.pathname === "/v1/dev/evals/runs/evalrun_test"
168
+ ) {
169
+ send(200, evalSnapshot);
170
+ return;
171
+ }
172
+ if (
173
+ req.method === "GET" &&
174
+ url.pathname === "/v1/dev/evals/runs/evalrun_running"
175
+ ) {
176
+ send(200, {
177
+ ...evalSnapshot,
178
+ runId: "evalrun_running",
179
+ status: "running",
180
+ finishedAt: undefined,
181
+ cases: evalSnapshot.cases.map((c) => ({
182
+ ...c,
183
+ status: "running" as const,
184
+ ok: undefined,
185
+ })),
186
+ });
187
+ return;
188
+ }
189
+ if (req.method === "POST" && url.pathname === "/v1/session") {
190
+ sessionHeaders.push({ ...req.headers });
191
+ send(200, {
192
+ ok: true,
193
+ sessionId: "ses_test",
194
+ continuationToken: "http:tok",
195
+ });
196
+ return;
197
+ }
198
+ if (
199
+ req.method === "GET" &&
200
+ url.pathname === "/v1/session/ses_test/stream"
201
+ ) {
202
+ sendNdjson([
203
+ {
204
+ index: 0,
205
+ sessionId: "ses_test",
206
+ type: "session.started",
207
+ data: { channelId: "http" },
208
+ },
209
+ {
210
+ index: 1,
211
+ sessionId: "ses_test",
212
+ turnId: "turn_1",
213
+ type: "turn.started",
214
+ data: {},
215
+ },
216
+ {
217
+ index: 2,
218
+ sessionId: "ses_test",
219
+ turnId: "turn_1",
220
+ type: "message.completed",
221
+ data: { text: "pong", finishReason: "stop" },
222
+ },
223
+ {
224
+ index: 3,
225
+ sessionId: "ses_test",
226
+ turnId: "turn_1",
227
+ type: "turn.completed",
228
+ data: { result: "pong" },
229
+ },
230
+ {
231
+ index: 4,
232
+ sessionId: "ses_test",
233
+ type: "session.waiting",
234
+ data: {},
235
+ },
236
+ ]);
237
+ return;
238
+ }
239
+ if (req.method === "POST" && url.pathname.startsWith("/v1/tools/")) {
240
+ toolHeaders.push({ ...req.headers });
241
+ send(200, { ok: true, output: { echoed: "hi" } });
242
+ return;
243
+ }
244
+ send(404, { ok: false, error: "not_found" });
245
+ });
246
+ });
247
+
248
+ server.listen(0, "127.0.0.1");
249
+ await once(server, "listening");
250
+ const { port } = server.address() as AddressInfo;
251
+ return {
252
+ url: `http://127.0.0.1:${port}`,
253
+ sessionHeaders,
254
+ toolHeaders,
255
+ evalRunHeaders,
256
+ evalRunBodies,
257
+ close: () =>
258
+ new Promise<void>((resolvePromise, reject) =>
259
+ server.close((error) => (error ? reject(error) : resolvePromise()))
260
+ ),
261
+ };
262
+ }
263
+
264
+ function runningDetail(
265
+ slug: string,
266
+ engineUrl: string
267
+ ): Record<string, unknown> {
268
+ return {
269
+ id: 1,
270
+ slug,
271
+ teamId: TEAM_ID,
272
+ deploymentKind: "repo",
273
+ status: "running",
274
+ generation: 1,
275
+ engineAlias: `/internal/agent-serve/agents/asd_test/engine`,
276
+ engineAccess: {
277
+ url: engineUrl,
278
+ headers: { "x-anyrun-port-token": "port-tok-1" },
279
+ expiresAt: "2099-01-01T00:00:00.000Z",
280
+ },
281
+ };
282
+ }
283
+
284
+ describe("resolveEvalTargetMode", () => {
285
+ it("defaults to local; --prod and --url opt into remote surfaces", () => {
286
+ expect(resolveEvalTargetMode({})).toBe("local");
287
+ expect(resolveEvalTargetMode({ prod: true })).toBe("deployment");
288
+ expect(resolveEvalTargetMode({ url: "http://127.0.0.1:3000" })).toBe("url");
289
+ });
290
+
291
+ it("rejects conflicting target flags", () => {
292
+ expect(resolveEvalTargetMode({ prod: true, url: "http://x" })).toEqual({
293
+ error: "--prod and --url are mutually exclusive",
294
+ });
295
+ });
296
+ });
297
+
298
+ describe("formatEvalStatusFlags", () => {
299
+ it("includes --slug/--team for --prod and --url/--team for --url", () => {
300
+ expect(formatEvalStatusFlags({ prod: true })).toBe(" --prod");
301
+ expect(
302
+ formatEvalStatusFlags({ prod: true, slug: "approval-buddy", team: "1" })
303
+ ).toBe(" --prod --slug approval-buddy --team 1");
304
+ expect(
305
+ formatEvalStatusFlags({ url: "http://127.0.0.1:3000/", team: "7" })
306
+ ).toBe(" --url http://127.0.0.1:3000 --team 7");
307
+ });
308
+ });
309
+
310
+ describe("resolveProdSlug", () => {
311
+ it("uses --slug when provided", () => {
312
+ expect(resolveProdSlug({ dir: ".", slug: "danny" })).toBe("danny");
313
+ });
314
+
315
+ it("derives from directory basename", async () => {
316
+ const dir = await mkdtemp(join(tmpdir(), "Danny Agent-"));
317
+ // basename is like "Danny Agent-xxxxx" → slugFromDirectoryName lowercases
318
+ const slug = resolveProdSlug({ dir });
319
+ expect(slug).toMatch(/^danny-agent-/);
320
+ await rm(dir, { recursive: true, force: true });
321
+ });
322
+
323
+ it("rejects invalid --slug", () => {
324
+ expect(() => resolveProdSlug({ dir: ".", slug: "Bad Slug" })).toThrow(
325
+ /Invalid slug/
326
+ );
327
+ });
328
+ });
329
+
330
+ describe("resolveProdTarget / --prod", () => {
331
+ let backend: MockBackend;
332
+ let engine: StubEngine;
333
+ const savedEnv: Record<string, string | undefined> = {};
334
+ let agentDir: string;
335
+
336
+ beforeEach(async () => {
337
+ backend = await startMockBackend();
338
+ engine = await startStubEngine();
339
+ for (const key of [
340
+ "CURSOR_API_BASE_URL",
341
+ "CURSOR_API_KEY",
342
+ "AGENT_SERVE_CONFIG_DIR",
343
+ ]) {
344
+ savedEnv[key] = process.env[key];
345
+ }
346
+ process.env.CURSOR_API_BASE_URL = backend.url;
347
+ delete process.env.CURSOR_API_KEY;
348
+ process.env.AGENT_SERVE_CONFIG_DIR = join(
349
+ tmpdir(),
350
+ `agentkit-prod-test-${process.pid}`
351
+ );
352
+ agentDir = await mkdtemp(join(tmpdir(), "danny-"));
353
+ });
354
+
355
+ afterEach(async () => {
356
+ await backend.close();
357
+ await engine.close();
358
+ await rm(agentDir, { recursive: true, force: true });
359
+ for (const [key, value] of Object.entries(savedEnv)) {
360
+ if (value === undefined) {
361
+ delete process.env[key];
362
+ } else {
363
+ process.env[key] = value;
364
+ }
365
+ }
366
+ });
367
+
368
+ it("resolves slug from dir name and returns engineAccess headers", async () => {
369
+ const slug = resolveProdSlug({ dir: agentDir });
370
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
371
+ const stderr: string[] = [];
372
+
373
+ const target = await resolveProdTarget({
374
+ dir: agentDir,
375
+ team: String(TEAM_ID),
376
+ apiKey: API_KEY,
377
+ err: (text) => stderr.push(text),
378
+ });
379
+
380
+ expect(target.slug).toBe(slug);
381
+ expect(target.agentUrl).toBe(engine.url);
382
+ expect(target.headers["x-anyrun-port-token"]).toBe("port-tok-1");
383
+ expect(target.headers["x-anyrun-hibernation-lower-bound-s"]).toBe(
384
+ PROD_ENGINE_WAKE_HEADERS["x-anyrun-hibernation-lower-bound-s"]
385
+ );
386
+ expect(target.headers["x-anyrun-hibernation-upper-bound-s"]).toBe(
387
+ PROD_ENGINE_WAKE_HEADERS["x-anyrun-hibernation-upper-bound-s"]
388
+ );
389
+ expect(stderr.join("")).toContain(`Using hosted deployment "${slug}".`);
390
+ expect(stderr.join("")).not.toMatch(/engine access|expires/i);
391
+ });
392
+
393
+ it("honors --slug override", async () => {
394
+ backend.setDeployment("weather", runningDetail("weather", engine.url));
395
+ const target = await resolveProdTarget({
396
+ dir: agentDir,
397
+ slug: "weather",
398
+ team: String(TEAM_ID),
399
+ apiKey: API_KEY,
400
+ err: () => {},
401
+ });
402
+ expect(target.slug).toBe("weather");
403
+ });
404
+
405
+ it("fails when deployment is not running", async () => {
406
+ backend.setDeployment("danny", {
407
+ ...runningDetail("danny", engine.url),
408
+ status: "stopped",
409
+ engineAccess: null,
410
+ });
411
+ await expect(
412
+ resolveProdTarget({
413
+ dir: agentDir,
414
+ slug: "danny",
415
+ team: String(TEAM_ID),
416
+ apiKey: API_KEY,
417
+ err: () => {},
418
+ })
419
+ ).rejects.toThrow(/stopped, not running/);
420
+ });
421
+
422
+ it("fails when engineAccess is missing", async () => {
423
+ backend.setDeployment("danny", {
424
+ ...runningDetail("danny", engine.url),
425
+ engineAccess: null,
426
+ });
427
+ await expect(
428
+ resolveProdTarget({
429
+ dir: agentDir,
430
+ slug: "danny",
431
+ team: String(TEAM_ID),
432
+ apiKey: API_KEY,
433
+ err: () => {},
434
+ sleep: async () => {},
435
+ })
436
+ ).rejects.toThrow(/is not reachable yet/);
437
+ });
438
+
439
+ it("retries when engineAccess appears after a wake", async () => {
440
+ backend.setDeployment("danny", {
441
+ ...runningDetail("danny", engine.url),
442
+ engineAccess: null,
443
+ });
444
+ const stderr: string[] = [];
445
+ const sleepCalls: number[] = [];
446
+ const target = await resolveProdTarget({
447
+ dir: agentDir,
448
+ slug: "danny",
449
+ team: String(TEAM_ID),
450
+ apiKey: API_KEY,
451
+ err: (text) => stderr.push(text),
452
+ sleep: async (ms) => {
453
+ sleepCalls.push(ms);
454
+ // After the first miss, the engine becomes reachable (wake).
455
+ backend.setDeployment("danny", runningDetail("danny", engine.url));
456
+ },
457
+ });
458
+ expect(target.agentUrl).toBe(engine.url);
459
+ expect(sleepCalls).toEqual([1000]);
460
+ expect(stderr.join("")).toContain("is waking");
461
+ });
462
+
463
+ it("rejects --prod with --url", async () => {
464
+ const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
465
+ throw new Error("process.exit");
466
+ }) as never);
467
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
468
+ try {
469
+ await expect(
470
+ resolveTarget({
471
+ dir: agentDir,
472
+ prod: true,
473
+ url: "http://127.0.0.1:3000",
474
+ })
475
+ ).rejects.toThrow(/process\.exit/);
476
+ expect(
477
+ errSpy.mock.calls.some((c) =>
478
+ String(c[0]).includes("mutually exclusive")
479
+ )
480
+ ).toBe(true);
481
+ } finally {
482
+ exitSpy.mockRestore();
483
+ errSpy.mockRestore();
484
+ }
485
+ });
486
+
487
+ it("cmdRun --prod sends X-Anyrun-Port-Token on /v1/session", async () => {
488
+ const slug = resolveProdSlug({ dir: agentDir });
489
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
490
+
491
+ const code = await cmdRun({
492
+ dir: agentDir,
493
+ prod: true,
494
+ team: String(TEAM_ID),
495
+ apiKey: API_KEY,
496
+ messages: ["hello"],
497
+ events: false,
498
+ stream: false,
499
+ });
500
+
501
+ expect(code).toBe(0);
502
+ expect(engine.sessionHeaders.length).toBe(1);
503
+ expect(engine.sessionHeaders[0]!["x-anyrun-port-token"]).toBe("port-tok-1");
504
+ expect(
505
+ engine.sessionHeaders[0]!["x-anyrun-hibernation-lower-bound-s"]
506
+ ).toBe("1800");
507
+ });
508
+
509
+ it("marks a missing deployment as notFound", async () => {
510
+ const error = await resolveProdTarget({
511
+ dir: agentDir,
512
+ slug: "danny",
513
+ team: String(TEAM_ID),
514
+ apiKey: API_KEY,
515
+ err: () => {},
516
+ }).then(
517
+ () => undefined,
518
+ (thrown: unknown) => thrown
519
+ );
520
+ expect(error).toBeInstanceOf(ProdTargetError);
521
+ expect((error as ProdTargetError).notFound).toBe(true);
522
+ expect((error as ProdTargetError).slug).toBe("danny");
523
+ });
524
+
525
+ it("cmdEval --prod explains when the deployment is missing", async () => {
526
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
527
+ try {
528
+ const code = await cmdEval({
529
+ dir: agentDir,
530
+ prod: true,
531
+ slug: "danny",
532
+ team: String(TEAM_ID),
533
+ apiKey: API_KEY,
534
+ stream: false,
535
+ });
536
+ expect(code).toBe(1);
537
+ const stderr = errSpy.mock.calls.map((c) => String(c[0])).join("");
538
+ // The API 404 text is kept (it distinguishes a missing deployment
539
+ // from a wrong host / disabled gate), plus the deploy/local hints.
540
+ expect(stderr).toContain('Deployment "danny"');
541
+ expect(stderr).toContain("not found");
542
+ expect(stderr).toContain("deploy --slug danny");
543
+ expect(stderr).toContain("omit --prod");
544
+ } finally {
545
+ errSpy.mockRestore();
546
+ }
547
+ });
548
+
549
+ it("cmdEval --prod starts a server-side persisted eval batch", async () => {
550
+ const slug = resolveProdSlug({ dir: agentDir });
551
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
552
+ const outSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
553
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
554
+ try {
555
+ const code = await cmdEval({
556
+ dir: agentDir,
557
+ prod: true,
558
+ team: String(TEAM_ID),
559
+ apiKey: API_KEY,
560
+ stream: false,
561
+ evalIds: ["smoke"],
562
+ });
563
+ expect(code).toBe(0);
564
+ expect(engine.evalRunHeaders.length).toBe(1);
565
+ expect(engine.evalRunHeaders[0]!["x-anyrun-port-token"]).toBe(
566
+ "port-tok-1"
567
+ );
568
+ expect(engine.evalRunBodies[0]).toEqual({ filterIds: ["smoke"] });
569
+ // Must not fall back to the client-side session harness.
570
+ expect(engine.sessionHeaders.length).toBe(0);
571
+ const stdout = outSpy.mock.calls.map((c) => String(c[0])).join("");
572
+ expect(stdout).toContain("PASS smoke");
573
+ } finally {
574
+ outSpy.mockRestore();
575
+ errSpy.mockRestore();
576
+ }
577
+ });
578
+
579
+ it("cmdEval --prod --no-wait accepts the batch without polling", async () => {
580
+ const slug = resolveProdSlug({ dir: agentDir });
581
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
582
+ const outSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
583
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
584
+ try {
585
+ const code = await cmdEval({
586
+ dir: agentDir,
587
+ prod: true,
588
+ team: String(TEAM_ID),
589
+ apiKey: API_KEY,
590
+ stream: false,
591
+ noWait: true,
592
+ evalIds: ["smoke"],
593
+ });
594
+ expect(code).toBe(0);
595
+ expect(engine.evalRunHeaders.length).toBe(1);
596
+ expect(engine.evalRunBodies[0]).toEqual({ filterIds: ["smoke"] });
597
+ const stdout = outSpy.mock.calls.map((c) => String(c[0])).join("");
598
+ expect(stdout).toContain("Eval ID: evalrun_test");
599
+ expect(stdout).toContain(
600
+ `eval status evalrun_test --prod --team ${TEAM_ID}`
601
+ );
602
+ expect(stdout).toContain("/v1/dev/evals/runs/evalrun_test");
603
+ } finally {
604
+ outSpy.mockRestore();
605
+ errSpy.mockRestore();
606
+ }
607
+ });
608
+
609
+ it("cmdEvalStatus --prod fetches a run and lists recent runs", async () => {
610
+ const slug = resolveProdSlug({ dir: agentDir });
611
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
612
+ const outSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
613
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
614
+ try {
615
+ const one = await cmdEvalStatus("evalrun_test", {
616
+ dir: agentDir,
617
+ prod: true,
618
+ team: String(TEAM_ID),
619
+ apiKey: API_KEY,
620
+ });
621
+ expect(one).toBe(0);
622
+ let stdout = outSpy.mock.calls.map((c) => String(c[0])).join("");
623
+ expect(stdout).toContain("evalrun_test completed");
624
+ expect(stdout).toContain("PASS smoke");
625
+
626
+ outSpy.mockClear();
627
+ const listed = await cmdEvalStatus(undefined, {
628
+ dir: agentDir,
629
+ prod: true,
630
+ team: String(TEAM_ID),
631
+ apiKey: API_KEY,
632
+ });
633
+ expect(listed).toBe(0);
634
+ stdout = outSpy.mock.calls.map((c) => String(c[0])).join("");
635
+ expect(stdout).toContain("evalrun_test completed");
636
+ } finally {
637
+ outSpy.mockRestore();
638
+ errSpy.mockRestore();
639
+ }
640
+ });
641
+
642
+ it("cmdEvalStatus exits 3 while the remote batch is still running", async () => {
643
+ const slug = resolveProdSlug({ dir: agentDir });
644
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
645
+ const outSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
646
+ const errSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
647
+ try {
648
+ const code = await cmdEvalStatus("evalrun_running", {
649
+ dir: agentDir,
650
+ prod: true,
651
+ team: String(TEAM_ID),
652
+ apiKey: API_KEY,
653
+ });
654
+ expect(code).toBe(3);
655
+ const stdout = outSpy.mock.calls.map((c) => String(c[0])).join("");
656
+ expect(stdout).toContain("evalrun_running running");
657
+ } finally {
658
+ outSpy.mockRestore();
659
+ errSpy.mockRestore();
660
+ }
661
+ });
662
+
663
+ it("cmdCall --prod sends engineAccess headers", async () => {
664
+ const slug = resolveProdSlug({ dir: agentDir });
665
+ backend.setDeployment(slug, runningDetail(slug, engine.url));
666
+
667
+ const code = await cmdCall({
668
+ dir: agentDir,
669
+ prod: true,
670
+ team: String(TEAM_ID),
671
+ apiKey: API_KEY,
672
+ toolName: "echo",
673
+ toolInput: '{"message":"hi"}',
674
+ });
675
+
676
+ expect(code).toBe(0);
677
+ expect(engine.toolHeaders.length).toBe(1);
678
+ expect(engine.toolHeaders[0]!["x-anyrun-port-token"]).toBe("port-tok-1");
679
+ });
680
+ });
681
+
682
+ describe("resolveProdAliasPlaygroundUrl", () => {
683
+ let backend: MockBackend;
684
+ const savedEnv: Record<string, string | undefined> = {};
685
+ let agentDir: string;
686
+
687
+ beforeEach(async () => {
688
+ backend = await startMockBackend();
689
+ for (const key of [
690
+ "CURSOR_API_BASE_URL",
691
+ "CURSOR_API_KEY",
692
+ "AGENT_SERVE_CONFIG_DIR",
693
+ ]) {
694
+ savedEnv[key] = process.env[key];
695
+ }
696
+ process.env.CURSOR_API_BASE_URL = backend.url;
697
+ delete process.env.CURSOR_API_KEY;
698
+ process.env.AGENT_SERVE_CONFIG_DIR = join(
699
+ tmpdir(),
700
+ `agentkit-prod-alias-test-${process.pid}`
701
+ );
702
+ agentDir = await mkdtemp(join(tmpdir(), "danny-"));
703
+ });
704
+
705
+ afterEach(async () => {
706
+ await backend.close();
707
+ await rm(agentDir, { recursive: true, force: true });
708
+ for (const [key, value] of Object.entries(savedEnv)) {
709
+ if (value === undefined) {
710
+ delete process.env[key];
711
+ } else {
712
+ process.env[key] = value;
713
+ }
714
+ }
715
+ });
716
+
717
+ it("returns the alias playground URL without requiring engineAccess", async () => {
718
+ backend.setDeployment("weather", {
719
+ ...runningDetail("weather", "http://127.0.0.1:9"),
720
+ engineAccess: null,
721
+ });
722
+ const stderr: string[] = [];
723
+
724
+ const resolved = await resolveProdAliasPlaygroundUrl({
725
+ dir: agentDir,
726
+ slug: "weather",
727
+ team: String(TEAM_ID),
728
+ apiKey: API_KEY,
729
+ err: (text) => stderr.push(text),
730
+ });
731
+
732
+ expect(resolved.slug).toBe("weather");
733
+ expect(resolved.playgroundUrl).toBe(
734
+ `${backend.url}/internal/agent-serve/agents/asd_test/engine/playground`
735
+ );
736
+ expect(stderr.join("")).toContain('Using hosted deployment "weather".');
737
+ });
738
+
739
+ it("appends sessionId when provided", async () => {
740
+ backend.setDeployment("weather", {
741
+ ...runningDetail("weather", "http://127.0.0.1:9"),
742
+ engineAccess: null,
743
+ });
744
+
745
+ const resolved = await resolveProdAliasPlaygroundUrl({
746
+ dir: agentDir,
747
+ slug: "weather",
748
+ team: String(TEAM_ID),
749
+ apiKey: API_KEY,
750
+ sessionId: "ses_abc",
751
+ err: () => {},
752
+ });
753
+
754
+ expect(resolved.playgroundUrl).toBe(
755
+ `${backend.url}/internal/agent-serve/agents/asd_test/engine/playground?sessionId=ses_abc`
756
+ );
757
+ });
758
+
759
+ it("fails when deployment is not running", async () => {
760
+ backend.setDeployment("weather", {
761
+ ...runningDetail("weather", "http://127.0.0.1:9"),
762
+ status: "stopped",
763
+ engineAccess: null,
764
+ });
765
+ await expect(
766
+ resolveProdAliasPlaygroundUrl({
767
+ dir: agentDir,
768
+ slug: "weather",
769
+ team: String(TEAM_ID),
770
+ apiKey: API_KEY,
771
+ err: () => {},
772
+ })
773
+ ).rejects.toThrow(/stopped, not running/);
774
+ });
775
+
776
+ it("fails when deployment is missing", async () => {
777
+ await expect(
778
+ resolveProdAliasPlaygroundUrl({
779
+ dir: agentDir,
780
+ slug: "missing",
781
+ team: String(TEAM_ID),
782
+ apiKey: API_KEY,
783
+ err: () => {},
784
+ })
785
+ ).rejects.toThrow(/Deployment "missing"/);
786
+ });
787
+ });