@cursor/july 0.2.0 → 0.2.1

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 (172) hide show
  1. package/dist/channels/slack/channel-watch.d.ts +19 -3
  2. package/dist/channels/slack/channel-watch.d.ts.map +1 -1
  3. package/dist/channels/slack/channel-watch.js +48 -9
  4. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  5. package/dist/channels/slack/slack-channel.js +4 -4
  6. package/dist/channels/slack/types.d.ts +11 -12
  7. package/dist/channels/slack/types.d.ts.map +1 -1
  8. package/dist/docs/404.html +2 -2
  9. package/dist/docs/assets/{app.9eAtsjAM.js → app.D23Y-7Tp.js} +4 -4
  10. package/dist/docs/assets/chunks/@localSearchIndexroot.CzCCM7N8.js +1 -0
  11. package/dist/docs/assets/chunks/{VPLocalSearchBox.C9s69nxj.js → VPLocalSearchBox.CWBeTFRZ.js} +1 -1
  12. package/dist/docs/assets/chunks/{arc.DmWDRaF-.js → arc.DSF2O3pm.js} +1 -1
  13. package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.BE1Wj4f3.js → architectureDiagram-Q4EWVU46.J52Wzbkg.js} +1 -1
  14. package/dist/docs/assets/chunks/{baseUniq.pmEZGnWu.js → baseUniq.CQS3LPCt.js} +1 -1
  15. package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.D6mM-5XN.js → blockDiagram-DXYQGD6D.Dw339Gr5.js} +1 -1
  16. package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.ZQYTPC1b.js → c4Diagram-AHTNJAMY.BUdtOaRZ.js} +1 -1
  17. package/dist/docs/assets/chunks/channel.Bfu4df88.js +1 -0
  18. package/dist/docs/assets/chunks/{chunk-4BX2VUAB.CKJ7gaK8.js → chunk-4BX2VUAB.CuOrkEqk.js} +1 -1
  19. package/dist/docs/assets/chunks/{chunk-4TB4RGXK.CL61HnvB.js → chunk-4TB4RGXK.BJNBcY7U.js} +1 -1
  20. package/dist/docs/assets/chunks/{chunk-55IACEB6.DsA4XA1m.js → chunk-55IACEB6.VJK5LAm_.js} +1 -1
  21. package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BODHCHFW.js → chunk-EDXVE4YY.BYYLihvj.js} +1 -1
  22. package/dist/docs/assets/chunks/{chunk-FMBD7UC4.D6NBaZTa.js → chunk-FMBD7UC4.CmoW8BXP.js} +1 -1
  23. package/dist/docs/assets/chunks/{chunk-OYMX7WX6.BLPuqFXg.js → chunk-OYMX7WX6.DTGY4C-M.js} +1 -1
  24. package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BFs_ML8Z.js → chunk-QZHKN3VN.Cg5n67vl.js} +1 -1
  25. package/dist/docs/assets/chunks/{chunk-YZCP3GAM.BDjuFyZv.js → chunk-YZCP3GAM.C3GR_ia5.js} +1 -1
  26. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.DdfgtaWs.js +1 -0
  27. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.DdfgtaWs.js +1 -0
  28. package/dist/docs/assets/chunks/clone.rkmfti6d.js +1 -0
  29. package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.D85HNEsi.js → cose-bilkent-S5V4N54A.BTRG8N3b.js} +1 -1
  30. package/dist/docs/assets/chunks/{dagre-KV5264BT.V463KlSF.js → dagre-KV5264BT.Bob_bp_p.js} +1 -1
  31. package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DhBDu1ab.js → diagram-5BDNPKRD.ggPcs9uO.js} +1 -1
  32. package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.v6sC68zl.js → diagram-G4DWMVQ6.BP0qyJkp.js} +1 -1
  33. package/dist/docs/assets/chunks/{diagram-MMDJMWI5.DhHsdXYv.js → diagram-MMDJMWI5.B0X24UKr.js} +1 -1
  34. package/dist/docs/assets/chunks/{diagram-TYMM5635.BPWNSFZc.js → diagram-TYMM5635.B4rXHFVt.js} +1 -1
  35. package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.Cq0wTMPL.js → erDiagram-SMLLAGMA._55Rt9oX.js} +1 -1
  36. package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.MveMtucC.js → flowDiagram-DWJPFMVM.DGP4XvR5.js} +1 -1
  37. package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.BzKfGUSf.js → ganttDiagram-T4ZO3ILL.BtXtkL4E.js} +1 -1
  38. package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.OToXTSW_.js → gitGraphDiagram-UUTBAWPF.B9cPWblK.js} +1 -1
  39. package/dist/docs/assets/chunks/{graph.D82tam-l.js → graph.D8HzNexS.js} +1 -1
  40. package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.DzFlRmcE.js → infoDiagram-42DDH7IO.Bw7CQUpi.js} +1 -1
  41. package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.CVPRJiKe.js → ishikawaDiagram-UXIWVN3A.MwkzF6nQ.js} +1 -1
  42. package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CQvNTfQC.js → journeyDiagram-VCZTEJTY.DIGFF-3C.js} +1 -1
  43. package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BwywywUl.js → kanban-definition-6JOO6SKY.DhYef2BN.js} +1 -1
  44. package/dist/docs/assets/chunks/{layout.C4BkPPba.js → layout.C0XUxuPi.js} +1 -1
  45. package/dist/docs/assets/chunks/{linear.FoSfGKD4.js → linear.BwNPpZex.js} +1 -1
  46. package/dist/docs/assets/chunks/{min.yLh8jqfl.js → min.CwAQdL7z.js} +1 -1
  47. package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.C9Dq2_CN.js → mindmap-definition-QFDTVHPH.pWsSVLsP.js} +1 -1
  48. package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.CiGyBcES.js → pieDiagram-DEJITSTG.BDJ3FbBy.js} +1 -1
  49. package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.1FWea9nj.js → quadrantDiagram-34T5L4WZ.Co80izyB.js} +1 -1
  50. package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.B1q7ntiV.js → requirementDiagram-MS252O5E.JveKw4yx.js} +1 -1
  51. package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Cpr4oU_k.js → sankeyDiagram-XADWPNL6.B0A7adPi.js} +1 -1
  52. package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.CUK68STn.js → sequenceDiagram-FGHM5R23.d6JZ5Hre.js} +1 -1
  53. package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.zU0yL2m4.js → stateDiagram-FHFEXIEX.DWnL0NQl.js} +1 -1
  54. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.ZEetPk0E.js +1 -0
  55. package/dist/docs/assets/chunks/{theme.DhnKd0CD.js → theme.MJTLx0hh.js} +2 -2
  56. package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.CNYoXo3F.js → timeline-definition-GMOUNBTQ.CFS7Ai4c.js} +1 -1
  57. package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.DFWH3G9s.js → vennDiagram-DHZGUBPP.CwSlnjCf.js} +1 -1
  58. package/dist/docs/assets/chunks/wardley-RL74JXVD.3gurI8YA.js +162 -0
  59. package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.7WLkqM7s.js → wardleyDiagram-NUSXRM2D.B_8mvtjh.js} +1 -1
  60. package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.-SyUNgWw.js → xychartDiagram-5P7HB3ND.DtjU5H85.js} +1 -1
  61. package/dist/docs/assets/{guides_slack.md.DVjNyqq5.js → guides_slack.md.Bjw2r2gL.js} +4 -4
  62. package/dist/docs/assets/{guides_slack.md.DVjNyqq5.lean.js → guides_slack.md.Bjw2r2gL.lean.js} +1 -1
  63. package/dist/docs/building-with-agents.html +35 -35
  64. package/dist/docs/deployment.html +35 -35
  65. package/dist/docs/evals.html +35 -35
  66. package/dist/docs/guides/agent-to-agent.html +35 -35
  67. package/dist/docs/guides/bitbucket.html +35 -35
  68. package/dist/docs/guides/cloud-agents.html +35 -35
  69. package/dist/docs/guides/convert-automation.html +35 -35
  70. package/dist/docs/guides/github.html +35 -35
  71. package/dist/docs/guides/gitlab.html +35 -35
  72. package/dist/docs/guides/grokbot-agents.html +35 -35
  73. package/dist/docs/guides/hooks.html +35 -35
  74. package/dist/docs/guides/improve.html +35 -35
  75. package/dist/docs/guides/jev.html +35 -35
  76. package/dist/docs/guides/mcp-oauth.html +35 -35
  77. package/dist/docs/guides/opentelemetry.html +35 -35
  78. package/dist/docs/guides/slack.html +39 -39
  79. package/dist/docs/guides/slack.md +15 -7
  80. package/dist/docs/guides/webhooks.html +35 -35
  81. package/dist/docs/hashmap.json +1 -1
  82. package/dist/docs/hillclimbing.html +35 -35
  83. package/dist/docs/index.html +35 -35
  84. package/dist/docs/llms-full.txt +15 -7
  85. package/dist/docs/quickstart.html +35 -35
  86. package/dist/docs/reference/agent-config.html +35 -35
  87. package/dist/docs/reference/artifacts.html +35 -35
  88. package/dist/docs/reference/channels.html +35 -35
  89. package/dist/docs/reference/cli.html +35 -35
  90. package/dist/docs/reference/connections.html +35 -35
  91. package/dist/docs/reference/evals.html +35 -35
  92. package/dist/docs/reference/extensions.html +35 -35
  93. package/dist/docs/reference/hooks.html +35 -35
  94. package/dist/docs/reference/http-api.html +35 -35
  95. package/dist/docs/reference/instructions.html +35 -35
  96. package/dist/docs/reference/playground.html +35 -35
  97. package/dist/docs/reference/project-layout.html +35 -35
  98. package/dist/docs/reference/prompt.html +35 -35
  99. package/dist/docs/reference/schedules.html +35 -35
  100. package/dist/docs/reference/sessions.html +35 -35
  101. package/dist/docs/reference/skills.html +35 -35
  102. package/dist/docs/reference/subagents.html +35 -35
  103. package/dist/docs/reference/tools.html +35 -35
  104. package/dist/docs/templates/agentic-owners.html +35 -35
  105. package/dist/docs/templates/pr-autofixer.html +35 -35
  106. package/dist/docs/templates/security-reviewer.html +35 -35
  107. package/dist/docs/templates/thermo-quality-review.html +35 -35
  108. package/dist/docs/templates/thermo-review.html +35 -35
  109. package/dist/docs/templates/triage.html +35 -35
  110. package/dist/docs/troubleshooting.html +35 -35
  111. package/dist/files-backends/cursor-hosted.d.ts +11 -17
  112. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  113. package/dist/files-backends/cursor-hosted.js +13 -41
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/internal/artifacts-store.d.ts +11 -0
  117. package/dist/internal/artifacts-store.d.ts.map +1 -1
  118. package/dist/internal/artifacts-store.js +113 -18
  119. package/dist/internal/cursor/cursor-api-transport.d.ts +37 -0
  120. package/dist/internal/cursor/cursor-api-transport.d.ts.map +1 -0
  121. package/dist/internal/cursor/cursor-api-transport.js +44 -0
  122. package/dist/internal/cursor/hosted-store-secrets.d.ts +21 -0
  123. package/dist/internal/cursor/hosted-store-secrets.d.ts.map +1 -1
  124. package/dist/internal/cursor/hosted-store-secrets.js +26 -0
  125. package/dist/internal/cursor/store-api-client.d.ts +82 -0
  126. package/dist/internal/cursor/store-api-client.d.ts.map +1 -0
  127. package/dist/internal/cursor/store-api-client.js +227 -0
  128. package/dist/internal/deploy-manifest.d.ts +8 -0
  129. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  130. package/dist/internal/deploy-manifest.js +7 -1
  131. package/dist/internal/reminder-runner.d.ts.map +1 -1
  132. package/dist/internal/reminder-runner.js +13 -4
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +13 -15
  135. package/dist/internal/store-api-protocol.d.ts +134 -0
  136. package/dist/internal/store-api-protocol.d.ts.map +1 -0
  137. package/dist/internal/store-api-protocol.js +126 -0
  138. package/dist/memory.d.ts +49 -14
  139. package/dist/memory.d.ts.map +1 -1
  140. package/dist/memory.js +193 -74
  141. package/dist/playground/assets/{index-CX6oTKwz.js → index-Cs0MKsv4.js} +30 -30
  142. package/dist/playground/index.html +1 -1
  143. package/dist/reminders.d.ts +1 -1
  144. package/dist/reminders.d.ts.map +1 -1
  145. package/dist/types.d.ts +28 -1
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/guides/slack.md +15 -7
  148. package/package.json +1 -1
  149. package/skills/setup-slack/SKILL.md +1 -1
  150. package/src/channels/slack/channel-watch.ts +57 -8
  151. package/src/channels/slack/slack-channel.ts +4 -3
  152. package/src/channels/slack/types.ts +11 -12
  153. package/src/files-backends/cursor-hosted.ts +31 -68
  154. package/src/index.ts +1 -0
  155. package/src/internal/artifacts-store.ts +131 -25
  156. package/src/internal/cursor/cursor-api-transport.ts +73 -0
  157. package/src/internal/cursor/hosted-store-secrets.ts +35 -0
  158. package/src/internal/cursor/store-api-client.ts +360 -0
  159. package/src/internal/deploy-manifest.ts +15 -0
  160. package/src/internal/reminder-runner.ts +30 -5
  161. package/src/internal/session-engine.ts +17 -22
  162. package/src/internal/store-api-protocol.ts +222 -0
  163. package/src/memory.ts +240 -93
  164. package/src/reminders.ts +1 -0
  165. package/src/types.ts +31 -1
  166. package/dist/docs/assets/chunks/@localSearchIndexroot.DjNBgxTF.js +0 -1
  167. package/dist/docs/assets/chunks/channel.BNF8VK-B.js +0 -1
  168. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.iViHzfrK.js +0 -1
  169. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.iViHzfrK.js +0 -1
  170. package/dist/docs/assets/chunks/clone.CJoR2BBM.js +0 -1
  171. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.nV8Nl-td.js +0 -1
  172. package/dist/docs/assets/chunks/wardley-RL74JXVD.Do4PwzeN.js +0 -162
@@ -57,19 +57,19 @@ import {
57
57
  type FileSinkPutResult,
58
58
  } from "../files.js";
59
59
  import {
60
- AGENT_SERVE_FACTORY_FLUSH_PROTOCOL,
60
+ type CursorApiTransportOptions,
61
+ resolveCursorApiBaseUrl,
62
+ resolveCursorApiFetch,
63
+ resolveCursorApiKey,
64
+ } from "../internal/cursor/cursor-api-transport.js";
65
+ import {
61
66
  resolveDeploymentStoreSourceIdSync,
62
67
  resolveFrameworkStoreSourceIdSync,
63
68
  resolveStoreProtocolSync,
69
+ storeProtocolEnablesFactoryFlush,
64
70
  } from "../internal/cursor/hosted-store-secrets.js";
65
71
  import { createAgentStorePresignedUrlValidatorForBackend } from "./agent-store-presigned-url.js";
66
72
 
67
- /**
68
- * Same default as the hosted storage backend, inlined so this entrypoint does
69
- * not import CLI code.
70
- */
71
- const DEFAULT_CURSOR_EXTERNAL_API_URL = "https://api.cursor.com";
72
-
73
73
  const AGENT_SERVE_FILES_PATH = "/v0/agent-serve-files";
74
74
 
75
75
  /**
@@ -156,18 +156,12 @@ export function isCursorUserStoreFilesAvailable(
156
156
  return key !== undefined && key !== "";
157
157
  }
158
158
 
159
- export interface CursorHostedFilesOptions {
160
- /** Control-plane base URL (default: `CURSOR_API_BASE_URL`, else api.cursor.com). */
161
- baseUrl?: string;
162
- /** Bearer credential (default: `CURSOR_API_KEY`). */
163
- apiKey?: string;
164
- /**
165
- * Lazy credential, re-resolved on every call and preferred over
166
- * {@link apiKey} — hosted freeze snapshots listen unsigned and bind the
167
- * key file after serve start. Kept separate so `apiKey` stays a plain
168
- * string.
169
- */
170
- resolveApiKey?: () => string | undefined;
159
+ /**
160
+ * Base URL / credential / fetch resolution is the shared
161
+ * {@link CursorApiTransportOptions} (`internal/cursor/cursor-api-transport.ts`),
162
+ * so this sink and the domain-lane store API client resolve identically.
163
+ */
164
+ export interface CursorHostedFilesOptions extends CursorApiTransportOptions {
171
165
  /** File store to address (default: `AGENT_SERVE_STORE_SOURCE_ID`). */
172
166
  storeSourceId?: string;
173
167
  /**
@@ -178,13 +172,12 @@ export interface CursorHostedFilesOptions {
178
172
  resolveStoreSourceId?: () => string | undefined;
179
173
  /**
180
174
  * Lazy store protocol pin (default: env, then the task-bound file). When it
181
- * answers {@link AGENT_SERVE_FACTORY_FLUSH_PROTOCOL} and the source id maps
182
- * to a deployment store role, requests ride the factory store API instead
183
- * of `/v0/agent-serve-files`. Any other value keeps `/v0`.
175
+ * enables the factory flush wire (`factory-flush-v1`, or the superset
176
+ * domain-API pin `factory-api-v1`) and the source id maps to a deployment
177
+ * store role, requests ride the factory store API instead of
178
+ * `/v0/agent-serve-files`. Any other value keeps `/v0`.
184
179
  */
185
180
  resolveStoreProtocol?: () => string | undefined;
186
- /** Test seam; defaults to global fetch. */
187
- fetch?: typeof fetch;
188
181
  }
189
182
 
190
183
  /**
@@ -295,7 +288,7 @@ export function cursorHostedFiles(
295
288
  const sourceId = resolveStoreSourceId(options);
296
289
  const protocol =
297
290
  options.resolveStoreProtocol?.() ?? resolveStoreProtocolSync();
298
- if (protocol === AGENT_SERVE_FACTORY_FLUSH_PROTOCOL) {
291
+ if (storeProtocolEnablesFactoryFlush(protocol)) {
299
292
  // The factory API addresses stores by role, so map the pinned source id
300
293
  // back to the role it was pinned from. A source id matching neither (a
301
294
  // local-dev override) has no role there and stays on `/v0`, as does any
@@ -567,7 +560,7 @@ export function cursorHostedFiles(
567
560
  };
568
561
  }
569
562
  try {
570
- const response = await fetchImpl(options)(
563
+ const response = await resolveCursorApiFetch(options)(
571
564
  assertPresignedUrlSafe(options, instruction.url),
572
565
  {
573
566
  method: "PUT",
@@ -818,7 +811,7 @@ export function cursorHostedFiles(
818
811
  if (url === undefined) {
819
812
  return undefined;
820
813
  }
821
- const response = await fetchImpl(options)(
814
+ const response = await resolveCursorApiFetch(options)(
822
815
  assertPresignedUrlSafe(options, url),
823
816
  {
824
817
  method: "GET",
@@ -992,9 +985,9 @@ function assertPresignedUrlSafe(
992
985
  return url.toString();
993
986
  }
994
987
  try {
995
- createAgentStorePresignedUrlValidatorForBackend(resolveBaseUrl(options))(
996
- url
997
- );
988
+ createAgentStorePresignedUrlValidatorForBackend(
989
+ resolveCursorApiBaseUrl(options)
990
+ )(url);
998
991
  } catch (error) {
999
992
  throw new Error(
1000
993
  `cursorHostedFiles: refused presigned URL: ${
@@ -1005,14 +998,6 @@ function assertPresignedUrlSafe(
1005
998
  return url.toString();
1006
999
  }
1007
1000
 
1008
- function fetchImpl(options: CursorHostedFilesOptions): typeof fetch {
1009
- const impl = options.fetch ?? globalThis.fetch;
1010
- if (typeof impl !== "function") {
1011
- throw new Error("cursorHostedFiles requires fetch");
1012
- }
1013
- return impl;
1014
- }
1015
-
1016
1001
  /**
1017
1002
  * The file store this engine's deployment owns. Every request names it, so
1018
1003
  * the server resolves the store row and this process never handles a store id.
@@ -1034,17 +1019,6 @@ function resolveStoreSourceId(options: CursorHostedFilesOptions): string {
1034
1019
  return id;
1035
1020
  }
1036
1021
 
1037
- function resolveApiKey(options: CursorHostedFilesOptions): string {
1038
- const configured = options.resolveApiKey?.() ?? options.apiKey;
1039
- const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
1040
- if (key === undefined || key === "") {
1041
- throw new Error(
1042
- "cursorHostedFiles requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
1043
- );
1044
- }
1045
- return key;
1046
- }
1047
-
1048
1022
  const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
1049
1023
  (process.env.CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS ?? "")
1050
1024
  .split(",")
@@ -1052,19 +1026,6 @@ const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
1052
1026
  .filter(host => host.length > 0)
1053
1027
  );
1054
1028
 
1055
- function resolveBaseUrl(options: CursorHostedFilesOptions): string {
1056
- return trimTrailingSlash(
1057
- options.baseUrl ??
1058
- process.env.CURSOR_API_BASE_URL ??
1059
- DEFAULT_CURSOR_EXTERNAL_API_URL
1060
- );
1061
- }
1062
-
1063
- function trimTrailingSlash(raw: string): string {
1064
- const trimmed = raw.trim();
1065
- return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
1066
- }
1067
-
1068
1029
  /**
1069
1030
  * Public agent-serve-files API call. The pod credential is the whole authorization:
1070
1031
  * the API forwards this caller's identity to the store service, which checks it
@@ -1077,7 +1038,7 @@ async function storePost<T>(
1077
1038
  ): Promise<T> {
1078
1039
  return await postJson<T>(
1079
1040
  options,
1080
- `${resolveBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1041
+ `${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1081
1042
  body
1082
1043
  );
1083
1044
  }
@@ -1088,7 +1049,7 @@ async function storeGet<T>(
1088
1049
  ): Promise<T> {
1089
1050
  return await sendJson<T>(
1090
1051
  options,
1091
- `${resolveBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1052
+ `${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1092
1053
  "GET"
1093
1054
  );
1094
1055
  }
@@ -1096,7 +1057,9 @@ async function storeGet<T>(
1096
1057
  /**
1097
1058
  * Factory store API call — same base URL and pod credential as
1098
1059
  * {@link storePost}, different path family. The server resolves the
1099
- * deployment (and the addressed store role) from the credential.
1060
+ * deployment (and the addressed store role) from the credential. The
1061
+ * domain-call lane one path family deeper is NOT here: it belongs to the
1062
+ * store API client (`../internal/cursor/store-api-client.ts`).
1100
1063
  */
1101
1064
  async function flushPost<T>(
1102
1065
  options: CursorHostedFilesOptions,
@@ -1105,7 +1068,7 @@ async function flushPost<T>(
1105
1068
  ): Promise<T> {
1106
1069
  return await postJson<T>(
1107
1070
  options,
1108
- `${resolveBaseUrl(options)}${AGENTSDK_STORE_PATH}${path}`,
1071
+ `${resolveCursorApiBaseUrl(options)}${AGENTSDK_STORE_PATH}${path}`,
1109
1072
  body
1110
1073
  );
1111
1074
  }
@@ -1124,10 +1087,10 @@ async function sendJson<T>(
1124
1087
  method: "GET" | "POST",
1125
1088
  body?: Record<string, unknown>
1126
1089
  ): Promise<T> {
1127
- const response = await fetchImpl(options)(url, {
1090
+ const response = await resolveCursorApiFetch(options)(url, {
1128
1091
  method,
1129
1092
  headers: {
1130
- Authorization: `Bearer ${resolveApiKey(options)}`,
1093
+ Authorization: `Bearer ${resolveCursorApiKey(options)}`,
1131
1094
  ...(body === undefined ? {} : { "Content-Type": "application/json" }),
1132
1095
  },
1133
1096
  ...(body === undefined ? {} : { body: JSON.stringify(body) }),
package/src/index.ts CHANGED
@@ -166,6 +166,7 @@ export type {
166
166
  ReminderDefinition,
167
167
  ReminderFireContext,
168
168
  ReminderFireResult,
169
+ ReminderFollowupOptions,
169
170
  ReminderHandlerConfig,
170
171
  ReminderHostApi,
171
172
  ReminderInfo,
@@ -31,14 +31,34 @@ import type {
31
31
  ArtifactTagInput,
32
32
  JsonValue,
33
33
  } from "../types.js";
34
+ import { mapBounded } from "./bounded.js";
35
+
36
+ /**
37
+ * Row-body hydration fan-out cap. `list()` fetches one object per row; on
38
+ * the Agent Store each fetch is a control-plane request, and an unbounded
39
+ * `Promise.all` over ~1k rows is a fleet-visible read burst (the 2026-09-18
40
+ * Agent Store read storm was this exact fan-out, run once per insert by the
41
+ * pre-metadata eviction path).
42
+ */
43
+ const ROW_HYDRATION_CONCURRENCY = 8;
44
+
45
+ /** One row's identity + modification metadata, without its body. */
46
+ interface ArtifactRowMeta {
47
+ id: string;
48
+ /** Last-modified epoch ms, when the backing store tracks one. */
49
+ mtimeMs?: number;
50
+ }
34
51
 
35
52
  interface ArtifactRowStore {
36
53
  get(id: string): Promise<ArtifactRecord | undefined>;
37
54
  put(record: ArtifactRecord): Promise<void>;
38
55
  delete(id: string): Promise<void>;
39
56
  list(): Promise<ArtifactRecord[]>;
40
- /** Row count without hydrating bodies — the per-insert cap check. */
41
- count(): Promise<number>;
57
+ /**
58
+ * Row ids + modification metadata without hydrating bodies — the
59
+ * per-insert cap check and (when mtimes are tracked) the eviction order.
60
+ */
61
+ listMeta(): Promise<ArtifactRowMeta[]>;
42
62
  }
43
63
 
44
64
  /** File/blob payload bytes, keyed by artifact id (metadata lives on the row). */
@@ -202,21 +222,54 @@ export class ArtifactsStore {
202
222
  return { kind, data: input.data };
203
223
  }
204
224
 
225
+ /**
226
+ * Trim to the retention cap from listing metadata alone. Past the cap
227
+ * this runs on every new-row insert, so it must not hydrate row bodies:
228
+ * on the Agent Store that is one read per row (~`max` reads per insert,
229
+ * forever, since eviction trims right back to the cap) — the 2026-09-18
230
+ * read storm on the security-reviewer deployment. The store listing
231
+ * carries last-modified metadata, and a row is rewritten on every upsert,
232
+ * so listing mtime orders rows the same way their `updatedAt` does.
233
+ */
205
234
  private async evictPastCap(): Promise<void> {
206
- if ((await this.rows.count()) <= this.max) {
235
+ const metas = await this.rows.listMeta();
236
+ const excess = metas.length - this.max;
237
+ if (excess <= 0) {
207
238
  return;
208
239
  }
209
- const records = await this.rows.list();
210
- if (records.length <= this.max) {
211
- return;
240
+ for (const id of await this.oldestRowIds(metas, excess)) {
241
+ await this.rows.delete(id);
242
+ // Without the row body there is no `content` flag to consult; delete
243
+ // the payload unconditionally — absent payloads delete as a no-op.
244
+ await this.content.delete(id);
212
245
  }
213
- const oldestFirst = records.sort((a, b) => byNewestUpdated(b, a));
214
- for (const record of oldestFirst.slice(0, records.length - this.max)) {
215
- await this.rows.delete(record.id);
216
- if (record.content !== undefined) {
217
- await this.content.delete(record.id);
218
- }
246
+ }
247
+
248
+ /** The `excess` oldest row ids, cheapest available ordering first. */
249
+ private async oldestRowIds(
250
+ metas: ArtifactRowMeta[],
251
+ excess: number
252
+ ): Promise<string[]> {
253
+ if (metas.some(meta => meta.mtimeMs !== undefined)) {
254
+ // A store that tracks mtimes must never regress to per-insert
255
+ // hydration. The rare entry missing one (anomalous on such a store)
256
+ // sorts oldest via the comparator's `?? 0` and gets evicted first —
257
+ // evicting an anomaly beats re-reading every row on every insert.
258
+ return metas
259
+ .sort(byOldestModified)
260
+ .slice(0, excess)
261
+ .map(meta => meta.id);
219
262
  }
263
+ // A store that tracks no modification times at all (the local
264
+ // filesystem store, deliberately) leaves only the rows' own
265
+ // `updatedAt`: hydrate — bounded — and order exactly. Corrupt rows
266
+ // drop out of `list()`, so recompute the excess against the rows that
267
+ // actually parsed.
268
+ const records = await this.rows.list();
269
+ return records
270
+ .sort((a, b) => byNewestUpdated(b, a))
271
+ .slice(0, Math.max(0, records.length - this.max))
272
+ .map(record => record.id);
220
273
  }
221
274
  }
222
275
 
@@ -228,6 +281,18 @@ export function artifactIdForKey(key: string): string {
228
281
  return `art_${createHash("sha256").update(key, "utf8").digest("hex").slice(0, 32)}`;
229
282
  }
230
283
 
284
+ /**
285
+ * Oldest-modified first. Store mtimes can be coarse (S3 rounds to the
286
+ * second), so equal stamps tie-break by id — deterministic, and within a
287
+ * same-stamp cohort any victim is equally past the retention cap.
288
+ */
289
+ function byOldestModified(a: ArtifactRowMeta, b: ArtifactRowMeta): number {
290
+ if (a.mtimeMs !== b.mtimeMs) {
291
+ return (a.mtimeMs ?? 0) - (b.mtimeMs ?? 0);
292
+ }
293
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
294
+ }
295
+
231
296
  function byNewestUpdated(a: ArtifactRecord, b: ArtifactRecord): number {
232
297
  if (a.updatedAt !== b.updatedAt) {
233
298
  return a.updatedAt < b.updatedAt ? 1 : -1;
@@ -311,8 +376,10 @@ function createSinkRowStore(
311
376
  const entries = await sink.list(
312
377
  agentStoreKeys.artifactRowPrefix(agentName)
313
378
  );
314
- const bodies = await Promise.all(
315
- entries.map(entry => sink.get(entry.key))
379
+ const bodies = await mapBounded(
380
+ entries,
381
+ ROW_HYDRATION_CONCURRENCY,
382
+ entry => sink.get(entry.key)
316
383
  );
317
384
  const records: ArtifactRecord[] = [];
318
385
  for (const body of bodies) {
@@ -323,15 +390,42 @@ function createSinkRowStore(
323
390
  }
324
391
  return records;
325
392
  },
326
- async count(): Promise<number> {
327
- const entries = await sink.list(
328
- agentStoreKeys.artifactRowPrefix(agentName)
329
- );
330
- return entries.length;
393
+ async listMeta(): Promise<ArtifactRowMeta[]> {
394
+ const prefix = agentStoreKeys.artifactRowPrefix(agentName);
395
+ const metas: ArtifactRowMeta[] = [];
396
+ for (const entry of await sink.list(prefix)) {
397
+ const id = rowIdFromName(entry.key.slice(prefix.length));
398
+ if (id !== undefined) {
399
+ metas.push({ id, mtimeMs: entry.mtimeMs });
400
+ }
401
+ }
402
+ return metas;
331
403
  },
332
404
  };
333
405
  }
334
406
 
407
+ /**
408
+ * Invert the row naming scheme (`encodeURIComponent(id) + ".json"`).
409
+ * Foreign keys under the prefix — no `.json` suffix, malformed escapes,
410
+ * non-canonical encodings — read as absent rather than throwing. The
411
+ * round-trip check matters for eviction: a name that decodes but does not
412
+ * re-encode to itself (`a+b.json`, `foo bar.json`) would count toward the
413
+ * cap yet be undeletable through `key(id)`, leaving a phantom victim that
414
+ * every subsequent insert re-selects and fails to remove.
415
+ */
416
+ function rowIdFromName(name: string): string | undefined {
417
+ if (!name.endsWith(".json") || name.includes("/")) {
418
+ return undefined;
419
+ }
420
+ const encoded = name.slice(0, -".json".length);
421
+ try {
422
+ const id = decodeURIComponent(encoded);
423
+ return encodeURIComponent(id) === encoded ? id : undefined;
424
+ } catch {
425
+ return undefined;
426
+ }
427
+ }
428
+
335
429
  function createSinkContentStore(
336
430
  sink: FileSink,
337
431
  agentName: string
@@ -410,18 +504,30 @@ function createFilesystemRowStore(stateRoot: string): ArtifactRowStore {
410
504
  }
411
505
  return records;
412
506
  },
413
- async count(): Promise<number> {
507
+ async listMeta(): Promise<ArtifactRowMeta[]> {
508
+ // No mtimes on purpose: local rows are cheap to hydrate, and the
509
+ // rows' own `updatedAt` orders eviction exactly, immune to coarse
510
+ // filesystem timestamp granularity.
511
+ let names: string[];
414
512
  try {
415
- const names = await readdir(dir);
416
- return names.filter(
417
- name => !name.startsWith(".") && name.endsWith(".json")
418
- ).length;
513
+ names = await readdir(dir);
419
514
  } catch (error) {
420
515
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
421
- return 0;
516
+ return [];
422
517
  }
423
518
  throw error;
424
519
  }
520
+ const metas: ArtifactRowMeta[] = [];
521
+ for (const name of names) {
522
+ if (name.startsWith(".")) {
523
+ continue;
524
+ }
525
+ const id = rowIdFromName(name);
526
+ if (id !== undefined) {
527
+ metas.push({ id });
528
+ }
529
+ }
530
+ return metas;
425
531
  },
426
532
  };
427
533
  }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The one resolution of "which Cursor control plane, as whom" for hosted
3
+ * transports. The verb-lane files sink (`files-backends/cursor-hosted.ts`)
4
+ * and the domain-lane store API client (`store-api-client.ts`) both build on
5
+ * these, so the pod credential and base URL resolve identically on every
6
+ * wire — one place to change, no drift between lanes.
7
+ */
8
+
9
+ /**
10
+ * Same default as the hosted storage backend, inlined so these entrypoints
11
+ * do not import CLI code.
12
+ */
13
+ export const DEFAULT_CURSOR_EXTERNAL_API_URL = "https://api.cursor.com";
14
+
15
+ export interface CursorApiTransportOptions {
16
+ /** Control-plane base URL (default: `CURSOR_API_BASE_URL`, else api.cursor.com). */
17
+ baseUrl?: string;
18
+ /** Bearer credential (default: `CURSOR_API_KEY`). */
19
+ apiKey?: string;
20
+ /**
21
+ * Lazy credential, re-resolved on every call and preferred over
22
+ * {@link apiKey} — hosted freeze snapshots listen unsigned and bind the
23
+ * key file after serve start. Kept separate so `apiKey` stays a plain
24
+ * string.
25
+ */
26
+ resolveApiKey?: () => string | undefined;
27
+ /** Test seam; defaults to global fetch. */
28
+ fetch?: typeof fetch;
29
+ }
30
+
31
+ function trimTrailingSlash(raw: string): string {
32
+ const trimmed = raw.trim();
33
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
34
+ }
35
+
36
+ export function resolveCursorApiBaseUrl(
37
+ options: CursorApiTransportOptions
38
+ ): string {
39
+ return trimTrailingSlash(
40
+ options.baseUrl ??
41
+ process.env.CURSOR_API_BASE_URL ??
42
+ DEFAULT_CURSOR_EXTERNAL_API_URL
43
+ );
44
+ }
45
+
46
+ /**
47
+ * The pod credential is the whole authorization on every hosted wire: the
48
+ * API resolves the caller's deployment from it, so there is no second,
49
+ * narrower credential to manage. Fails loudly rather than defaulting —
50
+ * calling the control plane as nobody is worse than not calling.
51
+ */
52
+ export function resolveCursorApiKey(
53
+ options: CursorApiTransportOptions
54
+ ): string {
55
+ const configured = options.resolveApiKey?.() ?? options.apiKey;
56
+ const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
57
+ if (key === undefined || key === "") {
58
+ throw new Error(
59
+ "Cursor hosted transport requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
60
+ );
61
+ }
62
+ return key;
63
+ }
64
+
65
+ export function resolveCursorApiFetch(
66
+ options: CursorApiTransportOptions
67
+ ): typeof fetch {
68
+ const impl = options.fetch ?? globalThis.fetch;
69
+ if (typeof impl !== "function") {
70
+ throw new Error("Cursor hosted transport requires fetch");
71
+ }
72
+ return impl;
73
+ }
@@ -26,6 +26,41 @@ export const DEFAULT_AGENT_SERVE_STORE_PROTOCOL_FILE =
26
26
  */
27
27
  export const AGENT_SERVE_FACTORY_FLUSH_PROTOCOL = "factory-flush-v1";
28
28
 
29
+ /**
30
+ * Pin value additionally enabling the domain-call lane
31
+ * (`POST /internal/agentsdk/store/api/<call>`), where the server owns the
32
+ * storage pattern for framework state — starting with `memory.append`. A
33
+ * strict superset of {@link AGENT_SERVE_FACTORY_FLUSH_PROTOCOL}: verbs keep
34
+ * riding the factory flush wire under this pin too. The same fail-safe
35
+ * applies in both directions — an unknown value keeps today's behavior, and
36
+ * re-pinning to `factory-flush-v1` turns the domain lane back off without a
37
+ * client change.
38
+ */
39
+ export const AGENT_SERVE_FACTORY_API_PROTOCOL = "factory-api-v1";
40
+
41
+ /**
42
+ * Does this pin select the factory flush wire for the store verbs? True for
43
+ * the flush pin and for the domain-API superset pin.
44
+ */
45
+ export function storeProtocolEnablesFactoryFlush(
46
+ protocol: string | undefined
47
+ ): boolean {
48
+ return (
49
+ protocol === AGENT_SERVE_FACTORY_FLUSH_PROTOCOL ||
50
+ protocol === AGENT_SERVE_FACTORY_API_PROTOCOL
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Does this pin enable the domain-call lane (server-owned storage patterns)?
56
+ * Only its exact value does; everything else keeps the client-side patterns.
57
+ */
58
+ export function storeProtocolEnablesStoreApi(
59
+ protocol: string | undefined
60
+ ): boolean {
61
+ return protocol === AGENT_SERVE_FACTORY_API_PROTOCOL;
62
+ }
63
+
29
64
  const STORE_SOURCE_ID_ENV = "AGENT_SERVE_STORE_SOURCE_ID";
30
65
  const FRAMEWORK_STORE_SOURCE_ID_ENV = "AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID";
31
66
  const STORE_SOURCE_ID_FILE_ENV = "AGENT_SERVE_STORE_SOURCE_ID_FILE";