@cursor/july 0.1.1 → 0.1.2

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 (199) hide show
  1. package/AGENTS.md +24 -2
  2. package/README.md +25 -15
  3. package/dist/bin/agent-serve.js +101 -12
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +31 -14
  6. package/dist/channels/github/cursor-account.d.ts +43 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -0
  8. package/dist/channels/github/cursor-account.js +95 -0
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +46 -9
  11. package/dist/channels/github/index.d.ts +2 -2
  12. package/dist/channels/github/index.js +2 -2
  13. package/dist/channels/github/types.d.ts +17 -0
  14. package/dist/channels/github/types.d.ts.map +1 -1
  15. package/dist/channels/slack/slack-channel.d.ts +8 -2
  16. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  17. package/dist/channels/slack/slack-channel.js +8 -0
  18. package/dist/channels/slack/types.d.ts +24 -3
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/channels/slack/types.js +15 -1
  21. package/dist/docs/404.html +2 -2
  22. package/dist/docs/ab.html +8 -8
  23. package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
  24. package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
  25. package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
  26. package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
  27. package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
  28. package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
  29. package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
  30. package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
  31. package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
  32. package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
  33. package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
  34. package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
  35. package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
  36. package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
  37. package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
  38. package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
  39. package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
  40. package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
  41. package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
  42. package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
  43. package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
  44. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
  45. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
  46. package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
  47. package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
  48. package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
  49. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
  50. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
  51. package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
  52. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
  53. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
  54. package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
  55. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
  56. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
  58. package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
  59. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
  60. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
  61. package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
  62. package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
  63. package/dist/docs/building-with-agents.html +4 -4
  64. package/dist/docs/concepts.html +4 -4
  65. package/dist/docs/deployment.html +7 -7
  66. package/dist/docs/evals.html +7 -7
  67. package/dist/docs/guides/agent-to-agent.html +6 -6
  68. package/dist/docs/guides/cloud-runtime.html +5 -5
  69. package/dist/docs/guides/github.html +14 -7
  70. package/dist/docs/guides/human-in-the-loop.html +6 -6
  71. package/dist/docs/guides/slack.html +8 -8
  72. package/dist/docs/guides/webhooks.html +6 -6
  73. package/dist/docs/hashmap.json +1 -1
  74. package/dist/docs/hillclimbing.html +5 -5
  75. package/dist/docs/index.html +7 -7
  76. package/dist/docs/quickstart.html +180 -23
  77. package/dist/docs/reference/agent-config.html +7 -7
  78. package/dist/docs/reference/channels.html +8 -8
  79. package/dist/docs/reference/cli.html +4 -4
  80. package/dist/docs/reference/connections.html +5 -5
  81. package/dist/docs/reference/hooks.html +5 -5
  82. package/dist/docs/reference/http-api.html +6 -6
  83. package/dist/docs/reference/instructions.html +13 -11
  84. package/dist/docs/reference/playground.html +4 -4
  85. package/dist/docs/reference/project-layout.html +4 -4
  86. package/dist/docs/reference/schedules.html +7 -7
  87. package/dist/docs/reference/sessions.html +4 -4
  88. package/dist/docs/reference/skills.html +6 -6
  89. package/dist/docs/reference/subagents.html +6 -6
  90. package/dist/docs/reference/tools.html +7 -7
  91. package/dist/docs/scaffolding-agents.html +5 -5
  92. package/dist/docs/storage.html +41 -0
  93. package/dist/docs/troubleshooting.html +4 -4
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1 -0
  97. package/dist/internal/cli-deploy.d.ts +52 -0
  98. package/dist/internal/cli-deploy.d.ts.map +1 -0
  99. package/dist/internal/cli-deploy.js +731 -0
  100. package/dist/internal/cursor/backend-client.d.ts +10 -1
  101. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  102. package/dist/internal/cursor/backend-client.js +79 -1
  103. package/dist/internal/cursor/github-credentials.d.ts +44 -0
  104. package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
  105. package/dist/internal/cursor/github-credentials.js +195 -0
  106. package/dist/internal/deploy-client.d.ts +176 -0
  107. package/dist/internal/deploy-client.d.ts.map +1 -0
  108. package/dist/internal/deploy-client.js +375 -0
  109. package/dist/internal/discovery.d.ts.map +1 -1
  110. package/dist/internal/discovery.js +73 -5
  111. package/dist/internal/distribution.d.ts.map +1 -1
  112. package/dist/internal/distribution.js +1 -0
  113. package/dist/internal/eval-run-store.d.ts +22 -3
  114. package/dist/internal/eval-run-store.d.ts.map +1 -1
  115. package/dist/internal/eval-run-store.js +37 -19
  116. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
  117. package/dist/internal/handleAgentServeTrigger.js +10 -12
  118. package/dist/internal/host-platforms.d.ts +7 -2
  119. package/dist/internal/host-platforms.d.ts.map +1 -1
  120. package/dist/internal/host-platforms.js +15 -10
  121. package/dist/internal/hosting.d.ts +37 -0
  122. package/dist/internal/hosting.d.ts.map +1 -0
  123. package/dist/internal/hosting.js +67 -0
  124. package/dist/internal/reminder-runner.d.ts +7 -0
  125. package/dist/internal/reminder-runner.d.ts.map +1 -1
  126. package/dist/internal/reminder-runner.js +50 -6
  127. package/dist/internal/reminder-store.d.ts +2 -0
  128. package/dist/internal/reminder-store.d.ts.map +1 -1
  129. package/dist/internal/reminder-store.js +18 -0
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +173 -42
  132. package/dist/internal/session-engine.d.ts +49 -0
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +246 -17
  135. package/dist/internal/storage-coordinator.d.ts +139 -0
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -0
  137. package/dist/internal/storage-coordinator.js +499 -0
  138. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  139. package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
  140. package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
  141. package/dist/playground/index.html +2 -2
  142. package/dist/storage.d.ts +204 -0
  143. package/dist/storage.d.ts.map +1 -0
  144. package/dist/storage.js +153 -0
  145. package/dist/types.d.ts +44 -3
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/README.md +3 -2
  148. package/docs/guides/github.md +43 -8
  149. package/docs/guides/slack.md +1 -1
  150. package/docs/quickstart.md +329 -51
  151. package/docs/reference/instructions.md +8 -6
  152. package/docs/scaffolding-agents.md +1 -1
  153. package/docs/storage.md +98 -0
  154. package/package.json +8 -1
  155. package/skills/github/SKILL.md +9 -1
  156. package/src/bin/agent-serve.ts +139 -0
  157. package/src/channels/github/api.ts +42 -23
  158. package/src/channels/github/cursor-account.ts +165 -0
  159. package/src/channels/github/github-channel.ts +66 -6
  160. package/src/channels/github/index.ts +2 -2
  161. package/src/channels/github/types.ts +19 -0
  162. package/src/channels/slack/slack-channel.ts +17 -3
  163. package/src/channels/slack/types.ts +44 -3
  164. package/src/index.ts +13 -0
  165. package/src/internal/cli-deploy.ts +940 -0
  166. package/src/internal/cursor/backend-client.ts +103 -1
  167. package/src/internal/cursor/github-credentials.ts +248 -0
  168. package/src/internal/deploy-client.ts +591 -0
  169. package/src/internal/discovery.ts +88 -1
  170. package/src/internal/distribution.ts +1 -0
  171. package/src/internal/eval-run-store.ts +48 -19
  172. package/src/internal/handleAgentServeTrigger.ts +10 -12
  173. package/src/internal/host-platforms.ts +28 -11
  174. package/src/internal/hosting.ts +77 -0
  175. package/src/internal/reminder-runner.ts +50 -6
  176. package/src/internal/reminder-store.ts +21 -0
  177. package/src/internal/server.ts +213 -27
  178. package/src/internal/session-engine.ts +285 -7
  179. package/src/internal/storage-coordinator.ts +615 -0
  180. package/src/storage.ts +325 -0
  181. package/src/types.ts +40 -2
  182. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
  183. package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
  184. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
  185. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
  186. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
  187. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
  188. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  189. package/dist/playground/assets/index-1K-hG-7p.css +0 -1
  190. /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
  191. /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
  192. /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
  193. /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
  194. /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
  198. /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
  199. /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
@@ -19,8 +19,10 @@ import {
19
19
  import { pathToFileURL } from "node:url";
20
20
  import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
21
21
  import { httpChannel } from "../channels.js";
22
+ import type { StorageDefinition } from "../storage.js";
22
23
  import {
23
24
  type AgentConfigDefinition,
25
+ type AgentHostingOptions,
24
26
  type AgentProject,
25
27
  type AgentProjectInfo,
26
28
  type ChannelDefinition,
@@ -51,6 +53,12 @@ import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
51
53
  import { getDefinitionKind } from "./brand.js";
52
54
  import { isValidCron } from "./cron.js";
53
55
  import { parseFrontmatter } from "./frontmatter.js";
56
+ import {
57
+ isReservedSecretName,
58
+ MAX_EGRESS_DOMAINS,
59
+ validateEgressDomain,
60
+ validateSecretName,
61
+ } from "./hosting.js";
54
62
  import { toolInputJsonSchema } from "./json-schema.js";
55
63
  import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
56
64
 
@@ -233,6 +241,7 @@ export async function loadAgentProject(
233
241
  const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
234
242
  const abs = await loadABs(ctx, agentDir);
235
243
  const abConfig = await loadABConfig(ctx, agentDir);
244
+ const storage = await loadStorage(ctx, agentDir);
236
245
 
237
246
  if (agentDir !== rootDir) {
238
247
  await warnUnknownSlots(ctx, agentDir);
@@ -248,7 +257,8 @@ export async function loadAgentProject(
248
257
  schedules,
249
258
  hooks,
250
259
  abs,
251
- ...(abConfig === undefined ? {} : { abConfig }),
260
+ abConfig,
261
+ storage,
252
262
  diagnostics,
253
263
  };
254
264
  }
@@ -324,6 +334,7 @@ async function loadAgent(
324
334
  : {
325
335
  cwd: localCwd,
326
336
  };
337
+ const hosting = resolveHosting(ctx, agentDir, config?.hosting);
327
338
 
328
339
  if (runtime === "cloud") {
329
340
  warnCloudRuntimeLimitations(ctx, agentDir, {
@@ -344,6 +355,7 @@ async function loadAgent(
344
355
  runtime,
345
356
  cloud,
346
357
  local,
358
+ hosting,
347
359
  tools,
348
360
  skills,
349
361
  connections,
@@ -367,6 +379,59 @@ function resolveAgentCwd(
367
379
  return isAbsolute(trimmed) ? trimmed : resolve(ctx.rootDir, trimmed);
368
380
  }
369
381
 
382
+ /**
383
+ * Validate the authored `hosting` block against the same grammar the
384
+ * backend enforces on deploy. Violations are warning diagnostics (reported
385
+ * by `agentkit validate`), not hard failures: the backend stays
386
+ * authoritative and `agentkit deploy` re-checks before any network call.
387
+ */
388
+ function resolveHosting(
389
+ ctx: DiscoveryContext,
390
+ agentDir: string,
391
+ hosting: AgentHostingOptions | undefined
392
+ ): AgentHostingOptions | undefined {
393
+ if (hosting === undefined) {
394
+ return undefined;
395
+ }
396
+ const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
397
+ const warn = (message: string): void => {
398
+ ctx.diagnostics.push({ severity: "warning", path: agentPath, message });
399
+ };
400
+
401
+ const egressDomains = hosting.egressDomains ?? [];
402
+ for (const domain of egressDomains) {
403
+ const domainError = validateEgressDomain(domain);
404
+ if (domainError !== undefined) {
405
+ warn(`hosting.egressDomains: ${domainError}`);
406
+ }
407
+ }
408
+ if (egressDomains.length > MAX_EGRESS_DOMAINS) {
409
+ warn(
410
+ `hosting.egressDomains lists ${egressDomains.length} domains; deploys are capped at ${MAX_EGRESS_DOMAINS}.`
411
+ );
412
+ }
413
+
414
+ for (const name of hosting.secretNames ?? []) {
415
+ const nameError = validateSecretName(name);
416
+ if (nameError !== undefined) {
417
+ warn(`hosting.secretNames: ${nameError}`);
418
+ } else if (isReservedSecretName(name)) {
419
+ warn(
420
+ `hosting.secretNames: ${JSON.stringify(name)} is platform-reserved (CURSOR_*) and injected automatically; it cannot be set with \`secrets set\`.`
421
+ );
422
+ }
423
+ }
424
+
425
+ return {
426
+ ...(hosting.egressDomains === undefined
427
+ ? {}
428
+ : { egressDomains: [...hosting.egressDomains] }),
429
+ ...(hosting.secretNames === undefined
430
+ ? {}
431
+ : { secretNames: [...hosting.secretNames] }),
432
+ };
433
+ }
434
+
370
435
  async function loadAgentConfig(
371
436
  ctx: DiscoveryContext,
372
437
  agentDir: string,
@@ -1323,6 +1388,22 @@ async function loadABConfig(
1323
1388
  }
1324
1389
  }
1325
1390
 
1391
+ // ============================================================================
1392
+ // Storage (agent/storage.ts)
1393
+ // ============================================================================
1394
+
1395
+ async function loadStorage(
1396
+ ctx: DiscoveryContext,
1397
+ agentDir: string
1398
+ ): Promise<StorageDefinition | undefined> {
1399
+ const path = await findModuleFile(agentDir, "storage");
1400
+ if (path === undefined) {
1401
+ return undefined;
1402
+ }
1403
+ const definition = await importDefault(ctx, path, "storage");
1404
+ return definition as StorageDefinition | undefined;
1405
+ }
1406
+
1326
1407
  function resolveABName(definition: unknown, fallback: string): string {
1327
1408
  if (
1328
1409
  typeof definition === "object" &&
@@ -1410,6 +1491,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1410
1491
  cloud:
1411
1492
  agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
1412
1493
  local: agent.local,
1494
+ hosting: agent.hosting,
1413
1495
  mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
1414
1496
  instructions:
1415
1497
  agent.instructions === undefined
@@ -1496,6 +1578,10 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1496
1578
  durableSnapshots: project.abConfig?.persistSnapshots !== undefined,
1497
1579
  },
1498
1580
  }),
1581
+ storage:
1582
+ project.storage === undefined
1583
+ ? undefined
1584
+ : { name: project.storage.name },
1499
1585
  diagnostics: project.diagnostics,
1500
1586
  };
1501
1587
  }
@@ -1515,6 +1601,7 @@ async function importDefault(
1515
1601
  | "schedule"
1516
1602
  | "hook"
1517
1603
  | "ab"
1604
+ | "storage"
1518
1605
  ): Promise<unknown> {
1519
1606
  let mod: Record<string, unknown>;
1520
1607
  try {
@@ -94,4 +94,5 @@ export const AUTHORING_ENTRY_FILES: ReadonlyArray<
94
94
  ["hooks", "hooks.js"],
95
95
  ["evals", "evals.js"],
96
96
  ["ab", "ab.js"],
97
+ ["storage", "storage.js"],
97
98
  ];
@@ -62,9 +62,21 @@ export class EvalRunStore {
62
62
 
63
63
  constructor(
64
64
  private readonly projectRoot: string,
65
- private readonly logger: (line: string) => void
65
+ private readonly logger: (line: string) => void,
66
+ /**
67
+ * Project-wide sink from `agent/storage.ts` (`defineStorage`),
68
+ * used when `evals.config.ts` sets no `persistRuns` of its own.
69
+ */
70
+ private readonly fallbackPersistence?: EvalRunPersistence
66
71
  ) {}
67
72
 
73
+ /** `evals.config.ts` `persistRuns` wins; `defineStorage` is fallback. */
74
+ private resolvePersist(
75
+ config: Partial<EvalConfigFile>
76
+ ): EvalRunPersistence | undefined {
77
+ return config.persistRuns ?? this.fallbackPersistence;
78
+ }
79
+
68
80
  async list(): Promise<{
69
81
  evals: Array<{
70
82
  id: string;
@@ -74,7 +86,7 @@ export class EvalRunStore {
74
86
  }>;
75
87
  config: EvalListConfig;
76
88
  }> {
77
- await this.ensureHydrated();
89
+ await this.hydrate();
78
90
  const { evals, config } = await discoverEvals(this.projectRoot);
79
91
  return {
80
92
  evals: evals.map((e) => ({
@@ -85,18 +97,21 @@ export class EvalRunStore {
85
97
  : { description: e.definition.description }),
86
98
  ...(e.definition.tags === undefined ? {} : { tags: e.definition.tags }),
87
99
  })),
88
- config: snapshotListConfig(config),
100
+ config: snapshotListConfig(
101
+ config,
102
+ this.resolvePersist(config) !== undefined
103
+ ),
89
104
  };
90
105
  }
91
106
 
92
107
  async get(runId: string): Promise<EvalRunSnapshot | undefined> {
93
- await this.ensureHydrated();
108
+ await this.hydrate();
94
109
  return this.runs.get(runId);
95
110
  }
96
111
 
97
112
  /** Newest-first snapshots for playground rehydrate after navigation/reload. */
98
113
  async listRuns(): Promise<EvalRunSnapshot[]> {
99
- await this.ensureHydrated();
114
+ await this.hydrate();
100
115
  return this.sortedRuns();
101
116
  }
102
117
 
@@ -109,7 +124,7 @@ export class EvalRunStore {
109
124
  runs: EvalRunSnapshot[];
110
125
  activeRunId?: string;
111
126
  }> {
112
- await this.ensureHydrated();
127
+ await this.hydrate();
113
128
  const activeRunId = this.activeRunId;
114
129
  return {
115
130
  runs: this.sortedRuns(),
@@ -122,7 +137,7 @@ export class EvalRunStore {
122
137
  }
123
138
 
124
139
  async start(input: StartEvalRunInput = {}): Promise<EvalRunSnapshot> {
125
- await this.ensureHydrated();
140
+ await this.hydrate();
126
141
  if (this.activeRunId !== undefined) {
127
142
  const active = this.runs.get(this.activeRunId);
128
143
  if (active !== undefined && active.status === "running") {
@@ -160,12 +175,15 @@ export class EvalRunStore {
160
175
  done: 0,
161
176
  },
162
177
  cases,
163
- config: snapshotRunConfig(config),
178
+ config: snapshotRunConfig(
179
+ config,
180
+ this.resolvePersist(config) !== undefined
181
+ ),
164
182
  };
165
183
  this.runs.set(runId, snapshot);
166
184
  this.activeRunId = runId;
167
185
  await this.pruneOldRuns(config);
168
- await this.enqueuePersist(snapshot, config.persistRuns);
186
+ await this.enqueuePersist(snapshot, this.resolvePersist(config));
169
187
  this.logger(
170
188
  `[agent-serve] eval run ${runId}: starting ${cases.length} case(s)`
171
189
  );
@@ -189,7 +207,14 @@ export class EvalRunStore {
189
207
  );
190
208
  }
191
209
 
192
- private async ensureHydrated(): Promise<void> {
210
+ /**
211
+ * Hydrate playground eval history from durable persistence
212
+ * (`evals.config.ts` `persistRuns`, else the `defineStorage`
213
+ * fallback). Called eagerly from the serve startup restore step —
214
+ * alongside session restore — and lazily as a safety net by every read.
215
+ * Idempotent; concurrent callers share one pass.
216
+ */
217
+ async hydrate(): Promise<void> {
193
218
  if (this.hydrated) {
194
219
  return;
195
220
  }
@@ -202,7 +227,7 @@ export class EvalRunStore {
202
227
  private async hydrateFromPersistence(): Promise<void> {
203
228
  try {
204
229
  const { config } = await discoverEvals(this.projectRoot);
205
- const persist = config.persistRuns;
230
+ const persist = this.resolvePersist(config);
206
231
  if (persist === undefined) {
207
232
  this.hydrated = true;
208
233
  return;
@@ -260,7 +285,7 @@ export class EvalRunStore {
260
285
  continue;
261
286
  }
262
287
  this.runs.delete(stale.runId);
263
- await this.persistDelete(stale.runId, config.persistRuns);
288
+ await this.persistDelete(stale.runId, this.resolvePersist(config));
264
289
  }
265
290
  }
266
291
 
@@ -384,7 +409,7 @@ export class EvalRunStore {
384
409
  this.applyCaseResult(runId, result);
385
410
  const run = this.runs.get(runId);
386
411
  if (run !== undefined) {
387
- void this.enqueuePersist(run, opts.config.persistRuns);
412
+ void this.enqueuePersist(run, this.resolvePersist(opts.config));
388
413
  }
389
414
  },
390
415
  });
@@ -394,7 +419,7 @@ export class EvalRunStore {
394
419
  }
395
420
  run.status = "completed";
396
421
  run.finishedAt = new Date().toISOString();
397
- await this.enqueuePersist(run, opts.config.persistRuns);
422
+ await this.enqueuePersist(run, this.resolvePersist(opts.config));
398
423
  this.logger(
399
424
  `[agent-serve] eval run ${runId}: done — ${run.summary.passed} passed, ${run.summary.failed} failed`
400
425
  );
@@ -406,7 +431,7 @@ export class EvalRunStore {
406
431
  run.status = "failed";
407
432
  run.finishedAt = new Date().toISOString();
408
433
  run.error = error instanceof Error ? error.message : String(error);
409
- await this.enqueuePersist(run, opts.config.persistRuns);
434
+ await this.enqueuePersist(run, this.resolvePersist(opts.config));
410
435
  this.logger(`[agent-serve] eval run ${runId}: failed — ${run.error}`);
411
436
  } finally {
412
437
  if (this.activeRunId === runId) {
@@ -467,21 +492,25 @@ function markInterruptedRun(run: EvalRunSnapshot): void {
467
492
  }
468
493
  }
469
494
 
470
- function snapshotListConfig(config: Partial<EvalConfigFile>): EvalListConfig {
495
+ function snapshotListConfig(
496
+ config: Partial<EvalConfigFile>,
497
+ durableRuns: boolean
498
+ ): EvalListConfig {
471
499
  return {
472
500
  ...(config.maxConcurrency === undefined
473
501
  ? {}
474
502
  : { maxConcurrency: config.maxConcurrency }),
475
503
  ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
476
504
  maxPlaygroundRuns: resolveEvalMaxPlaygroundRuns(config.maxPlaygroundRuns),
477
- durableRuns: config.persistRuns !== undefined,
505
+ durableRuns,
478
506
  };
479
507
  }
480
508
 
481
509
  function snapshotRunConfig(
482
- config: Partial<EvalConfigFile>
510
+ config: Partial<EvalConfigFile>,
511
+ durableRuns: boolean
483
512
  ): EvalRunSnapshot["config"] {
484
- const list = snapshotListConfig(config);
513
+ const list = snapshotListConfig(config, durableRuns);
485
514
  return {
486
515
  ...(list.maxConcurrency === undefined
487
516
  ? {}
@@ -62,19 +62,17 @@ export function collectGitHubTriggerTargets(input: {
62
62
  }): AgentServeGitHubTriggerTarget[] {
63
63
  const targets: AgentServeGitHubTriggerTarget[] = [];
64
64
  for (const mount of input.mounts) {
65
- const github = mount.channels.find((channel) => channel.id === "github");
66
- if (github === undefined) {
67
- continue;
65
+ for (const channel of mount.channels) {
66
+ const options = getGitHubChannelOptions(channel.definition);
67
+ if (options === undefined) {
68
+ continue;
69
+ }
70
+ targets.push({
71
+ slug: mount.slug,
72
+ options,
73
+ args: mount.buildHandlerArgs(channel.id),
74
+ });
68
75
  }
69
- const options = getGitHubChannelOptions(github.definition);
70
- if (options === undefined) {
71
- continue;
72
- }
73
- targets.push({
74
- slug: mount.slug,
75
- options,
76
- args: mount.buildHandlerArgs("github"),
77
- });
78
76
  }
79
77
  return targets;
80
78
  }
@@ -12,6 +12,7 @@ import {
12
12
  resolveGitHubInstallationId,
13
13
  tryResolveGhAuthToken,
14
14
  } from "../channels/github/auth.js";
15
+ import type { GitHubChannelCredentials } from "../channels/github/types.js";
15
16
  import { tryResolveBotToken } from "../channels/slack/credentials.js";
16
17
  import type { HostGitHubClient, HostSlackClient } from "../types.js";
17
18
 
@@ -25,21 +26,34 @@ export interface HostPlatforms {
25
26
  * `app` = GitHub App installation token (preferred in production).
26
27
  * `env` / `gh` = local PAT / CLI fallbacks when App creds are unset.
27
28
  */
28
- export type HostGitHubAuthSource = "app" | "env" | "gh" | "unavailable";
29
+ export type HostGitHubAuthSource =
30
+ | "app"
31
+ | "cursor-account"
32
+ | "env"
33
+ | "gh"
34
+ | "unavailable";
35
+
36
+ export interface HostPlatformsOptions {
37
+ githubCredentials?: GitHubChannelCredentials;
38
+ }
29
39
 
30
40
  /**
31
41
  * Build host platforms from ambient credentials. Prefer App installation
32
42
  * auth when configured; otherwise fall back to PAT / `gh auth` for local
33
43
  * testing. Webhook-scoped calls should still prefer `ctx.github`.
34
44
  */
35
- export function createHostPlatforms(): HostPlatforms {
45
+ export function createHostPlatforms(
46
+ options: HostPlatformsOptions = {}
47
+ ): HostPlatforms {
36
48
  let octokitPromise: ReturnType<HostGitHubClient["getOctokit"]> | undefined;
37
49
  let slackPromise: ReturnType<HostSlackClient["getClient"]> | undefined;
38
50
 
39
51
  return {
40
52
  github: {
41
53
  getOctokit: () => {
42
- octokitPromise ??= createGitHubOctokit({});
54
+ octokitPromise ??= createGitHubOctokit({
55
+ credentials: options.githubCredentials,
56
+ });
43
57
  return octokitPromise;
44
58
  },
45
59
  },
@@ -95,8 +109,9 @@ export async function resolveHostGitHubAuthSource(): Promise<HostGitHubAuthSourc
95
109
  export async function warmupHostGitHubAuth(options: {
96
110
  logger: (line: string) => void;
97
111
  github?: HostGitHubClient;
112
+ authSource?: HostGitHubAuthSource;
98
113
  }): Promise<HostGitHubAuthSource> {
99
- const source = await resolveHostGitHubAuthSource();
114
+ const source = options.authSource ?? (await resolveHostGitHubAuthSource());
100
115
  if (source === "unavailable") {
101
116
  options.logger(
102
117
  "[agent-serve] GitHub auth: unavailable (set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY + GITHUB_APP_INSTALLATION_ID, or GITHUB_TOKEN / GH_TOKEN, or run `gh auth login`)"
@@ -118,13 +133,15 @@ export async function warmupHostGitHubAuth(options: {
118
133
  }
119
134
 
120
135
  const label =
121
- source === "app"
122
- ? exportedGhToken
123
- ? "GitHub App installation token (also exported as GH_TOKEN for gh CLI)"
124
- : "GitHub App installation token"
125
- : source === "env"
126
- ? "GITHUB_TOKEN/GH_TOKEN"
127
- : "gh auth token";
136
+ source === "cursor-account"
137
+ ? "Cursor account repo-scoped credential (also exported as GH_TOKEN for gh CLI)"
138
+ : source === "app"
139
+ ? exportedGhToken
140
+ ? "GitHub App installation token (also exported as GH_TOKEN for gh CLI)"
141
+ : "GitHub App installation token"
142
+ : source === "env"
143
+ ? "GITHUB_TOKEN/GH_TOKEN"
144
+ : "gh auth token";
128
145
  if (options.github === undefined) {
129
146
  options.logger(`[agent-serve] GitHub auth: ${label}`);
130
147
  return source;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Client-side mirrors of the Agent Serve hosting validation rules
3
+ * (backend/server/src/agentServe/agentServeShared.ts). Shared by discovery
4
+ * (`hosting` block diagnostics on `agentkit validate`) and the deploy /
5
+ * secrets CLI verbs (actionable errors before any network call). The
6
+ * backend stays authoritative — these exist so mistakes fail fast and
7
+ * locally with the same grammar the server enforces.
8
+ */
9
+
10
+ /** `hosting.egressDomains` cap (backend caps the deploy body at 20). */
11
+ export const MAX_EGRESS_DOMAINS = 20;
12
+ /** Max secrets per deployment. */
13
+ export const MAX_SECRETS = 32;
14
+ /** Max secret value size in bytes. */
15
+ export const MAX_SECRET_VALUE_BYTES = 4096;
16
+ /** Max secret name length. */
17
+ export const MAX_SECRET_NAME_LENGTH = 64;
18
+ /** Secret names the platform injects; user writes are rejected. */
19
+ export const RESERVED_SECRET_NAME_PREFIX = "CURSOR_";
20
+
21
+ const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
22
+
23
+ // Lowercase hostname with >=2 labels, an alphabetic TLD, and at most one
24
+ // leading `*.` wildcard. Labels: alphanumeric with interior hyphens.
25
+ const EGRESS_DOMAIN_PATTERN =
26
+ /^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]+$/;
27
+
28
+ /** Returns a human error message, or undefined when the domain is valid. */
29
+ export function validateEgressDomain(domain: string): string | undefined {
30
+ if (EGRESS_DOMAIN_PATTERN.test(domain)) {
31
+ return undefined;
32
+ }
33
+ return `Invalid egress domain ${JSON.stringify(domain)}: use a lowercase hostname with at least two labels and an alphabetic TLD (e.g. "api.example.com"), optionally with a single leading "*." wildcard.`;
34
+ }
35
+
36
+ /**
37
+ * Returns a human error message, or undefined when the name is a valid
38
+ * secret name. Grammar only — reserved names are checked separately so the
39
+ * manifest's declarative `secretNames` can mention them with a softer
40
+ * diagnostic than the hard write-time rejection.
41
+ */
42
+ export function validateSecretName(name: string): string | undefined {
43
+ if (name.length > MAX_SECRET_NAME_LENGTH) {
44
+ return `Secret name ${JSON.stringify(name)} is too long (max ${MAX_SECRET_NAME_LENGTH} characters).`;
45
+ }
46
+ if (!SECRET_NAME_PATTERN.test(name)) {
47
+ return `Invalid secret name ${JSON.stringify(name)}: use UPPER_SNAKE_CASE starting with a letter (${SECRET_NAME_PATTERN}).`;
48
+ }
49
+ return undefined;
50
+ }
51
+
52
+ /** True when the platform reserves this name (`CURSOR_*`); writes are rejected. */
53
+ export function isReservedSecretName(name: string): boolean {
54
+ return name.startsWith(RESERVED_SECRET_NAME_PREFIX);
55
+ }
56
+
57
+ /**
58
+ * Human error for a secret name a user is trying to write or delete:
59
+ * grammar plus the reserved `CURSOR_*` prefix.
60
+ */
61
+ export function validateSettableSecretName(name: string): string | undefined {
62
+ const nameError = validateSecretName(name);
63
+ if (nameError !== undefined) {
64
+ return nameError;
65
+ }
66
+ if (isReservedSecretName(name)) {
67
+ return `Secret name ${JSON.stringify(name)} is platform-reserved (CURSOR_*); the platform injects those values automatically.`;
68
+ }
69
+ return undefined;
70
+ }
71
+
72
+ const CURSOR_EVENT_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
73
+
74
+ /** `owner/name` — mirrors the backend's cursor-event repo grammar. */
75
+ export function isValidCursorEventRepo(repo: string): boolean {
76
+ return CURSOR_EVENT_REPO_PATTERN.test(repo) && !repo.includes("..");
77
+ }
@@ -121,6 +121,9 @@ export class ReminderRunner {
121
121
  async start(): Promise<void> {
122
122
  if (this.started) return;
123
123
  this.started = true;
124
+ // Materialize sink-backed reminders onto local disk before arming
125
+ // (local wins when both sides have the same id).
126
+ await this.hydrateFromStorage();
124
127
  const armed = await this.store.listArmed();
125
128
  let restored = 0;
126
129
  let disarmedOrphan = 0;
@@ -148,6 +151,47 @@ export class ReminderRunner {
148
151
  }
149
152
  }
150
153
 
154
+ /**
155
+ * Pull reminder records from `defineStorage` into `<stateRoot>/reminders`
156
+ * when local disk is empty for that id. Failures are logged and skipped.
157
+ */
158
+ private async hydrateFromStorage(): Promise<void> {
159
+ const storage = this.engine.storage;
160
+ if (storage === undefined || !storage.canBulkRestore) {
161
+ return;
162
+ }
163
+ let hydrated = 0;
164
+ try {
165
+ const remote = await storage.listReminders();
166
+ for (const record of remote) {
167
+ const local = await this.store.get(record.id);
168
+ if (local !== undefined) {
169
+ continue;
170
+ }
171
+ // Local write only — re-mirroring would just echo the sink.
172
+ await this.store.save(record);
173
+ hydrated += 1;
174
+ }
175
+ } catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ this.logger(
178
+ `[agent-serve] reminders: storage hydrate failed: ${message}`
179
+ );
180
+ return;
181
+ }
182
+ if (hydrated > 0) {
183
+ this.logger(
184
+ `[agent-serve] reminders: hydrated ${hydrated} reminder(s) from storage`
185
+ );
186
+ }
187
+ }
188
+
189
+ /** Persist locally and mirror into the authored storage sink when present. */
190
+ private async persist(record: ReminderRecord): Promise<void> {
191
+ await this.store.save(record);
192
+ this.engine.storage?.reminder(record);
193
+ }
194
+
151
195
  stop(): void {
152
196
  this.started = false;
153
197
  for (const timer of this.timers.values()) {
@@ -209,7 +253,7 @@ export class ReminderRunner {
209
253
  updatedAt: now,
210
254
  };
211
255
 
212
- await this.store.save(record);
256
+ await this.persist(record);
213
257
  this.handlers.set(id, {
214
258
  run: input.run,
215
259
  untilFn,
@@ -273,7 +317,7 @@ export class ReminderRunner {
273
317
  disarmReason: reason,
274
318
  updatedAt: new Date().toISOString(),
275
319
  };
276
- await this.store.save(updated);
320
+ await this.persist(updated);
277
321
  this.handlers.delete(id);
278
322
  return toInfo(updated);
279
323
  }
@@ -662,7 +706,7 @@ export class ReminderRunner {
662
706
  disarmReason: reason,
663
707
  updatedAt: new Date().toISOString(),
664
708
  };
665
- await this.store.save(updated);
709
+ await this.persist(updated);
666
710
  this.handlers.delete(record.id);
667
711
  }
668
712
 
@@ -711,7 +755,7 @@ export class ReminderRunner {
711
755
  : String(reschedule.at),
712
756
  updatedAt: new Date().toISOString(),
713
757
  };
714
- await this.store.save(updated);
758
+ await this.persist(updated);
715
759
  this.armTimer(updated);
716
760
  return;
717
761
  }
@@ -725,7 +769,7 @@ export class ReminderRunner {
725
769
  disarmReason: "one_shot_complete",
726
770
  updatedAt: new Date().toISOString(),
727
771
  };
728
- await this.store.save(updated);
772
+ await this.persist(updated);
729
773
  this.handlers.delete(current.id);
730
774
  return;
731
775
  }
@@ -747,7 +791,7 @@ export class ReminderRunner {
747
791
  nextFireAtMs,
748
792
  updatedAt: new Date().toISOString(),
749
793
  };
750
- await this.store.save(updated);
794
+ await this.persist(updated);
751
795
  this.armTimer(updated);
752
796
  }
753
797
  }
@@ -66,6 +66,27 @@ export interface ReminderRecord {
66
66
  updatedAt: string;
67
67
  }
68
68
 
69
+ /** Narrow a sink value to a {@link ReminderRecord} (startup hydrate). */
70
+ export function isReminderRecord(value: unknown): value is ReminderRecord {
71
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
72
+ return false;
73
+ }
74
+ const record = value as Partial<ReminderRecord>;
75
+ return (
76
+ record.version === REMINDER_SNAPSHOT_VERSION &&
77
+ typeof record.id === "string" &&
78
+ record.id !== "" &&
79
+ typeof record.purpose === "string" &&
80
+ typeof record.channelId === "string" &&
81
+ typeof record.continuationToken === "string" &&
82
+ typeof record.scheduleKind === "string" &&
83
+ typeof record.scheduleValue === "string" &&
84
+ typeof record.status === "string" &&
85
+ typeof record.nextFireAtMs === "number" &&
86
+ Number.isFinite(record.nextFireAtMs)
87
+ );
88
+ }
89
+
69
90
  export class ReminderStore {
70
91
  constructor(private readonly rootDir: string) {}
71
92