@cursor/july 0.1.114 → 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 (206) 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/inbound.d.ts +7 -0
  5. package/dist/channels/slack/inbound.d.ts.map +1 -1
  6. package/dist/channels/slack/inbound.js +23 -0
  7. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  8. package/dist/channels/slack/slack-channel.js +87 -8
  9. package/dist/channels/slack/types.d.ts +11 -12
  10. package/dist/channels/slack/types.d.ts.map +1 -1
  11. package/dist/docs/404.html +2 -2
  12. package/dist/docs/assets/{app.BqkJwOZ-.js → app.D23Y-7Tp.js} +4 -4
  13. package/dist/docs/assets/chunks/@localSearchIndexroot.CzCCM7N8.js +1 -0
  14. package/dist/docs/assets/chunks/{VPLocalSearchBox.BJAi2KiV.js → VPLocalSearchBox.CWBeTFRZ.js} +1 -1
  15. package/dist/docs/assets/chunks/{arc.BZpXTgvV.js → arc.DSF2O3pm.js} +1 -1
  16. package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.WYI-7F-Y.js → architectureDiagram-Q4EWVU46.J52Wzbkg.js} +1 -1
  17. package/dist/docs/assets/chunks/{baseUniq.CZaUPpg0.js → baseUniq.CQS3LPCt.js} +1 -1
  18. package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.D6UES2pD.js → blockDiagram-DXYQGD6D.Dw339Gr5.js} +1 -1
  19. package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.cwebIe4i.js → c4Diagram-AHTNJAMY.BUdtOaRZ.js} +1 -1
  20. package/dist/docs/assets/chunks/channel.Bfu4df88.js +1 -0
  21. package/dist/docs/assets/chunks/{chunk-4BX2VUAB.fVyFnjxg.js → chunk-4BX2VUAB.CuOrkEqk.js} +1 -1
  22. package/dist/docs/assets/chunks/{chunk-4TB4RGXK.BanufG1c.js → chunk-4TB4RGXK.BJNBcY7U.js} +1 -1
  23. package/dist/docs/assets/chunks/{chunk-55IACEB6.VaSMz5-2.js → chunk-55IACEB6.VJK5LAm_.js} +1 -1
  24. package/dist/docs/assets/chunks/{chunk-EDXVE4YY.CN2diZOM.js → chunk-EDXVE4YY.BYYLihvj.js} +1 -1
  25. package/dist/docs/assets/chunks/{chunk-FMBD7UC4.g4ivypu3.js → chunk-FMBD7UC4.CmoW8BXP.js} +1 -1
  26. package/dist/docs/assets/chunks/{chunk-OYMX7WX6.GZXKn9JJ.js → chunk-OYMX7WX6.DTGY4C-M.js} +1 -1
  27. package/dist/docs/assets/chunks/{chunk-QZHKN3VN.itXxJZCd.js → chunk-QZHKN3VN.Cg5n67vl.js} +1 -1
  28. package/dist/docs/assets/chunks/{chunk-YZCP3GAM.-rw2GfvX.js → chunk-YZCP3GAM.C3GR_ia5.js} +1 -1
  29. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.DdfgtaWs.js +1 -0
  30. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.DdfgtaWs.js +1 -0
  31. package/dist/docs/assets/chunks/clone.rkmfti6d.js +1 -0
  32. package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.CmaI5br0.js → cose-bilkent-S5V4N54A.BTRG8N3b.js} +1 -1
  33. package/dist/docs/assets/chunks/{dagre-KV5264BT.4wY9S4Kt.js → dagre-KV5264BT.Bob_bp_p.js} +1 -1
  34. package/dist/docs/assets/chunks/{diagram-5BDNPKRD.Pc3c0u9W.js → diagram-5BDNPKRD.ggPcs9uO.js} +1 -1
  35. package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.CYrWz-nj.js → diagram-G4DWMVQ6.BP0qyJkp.js} +1 -1
  36. package/dist/docs/assets/chunks/{diagram-MMDJMWI5.Bgj5hukb.js → diagram-MMDJMWI5.B0X24UKr.js} +1 -1
  37. package/dist/docs/assets/chunks/{diagram-TYMM5635.DGMEXalS.js → diagram-TYMM5635.B4rXHFVt.js} +1 -1
  38. package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.GepTV9Im.js → erDiagram-SMLLAGMA._55Rt9oX.js} +1 -1
  39. package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.DVKywg3j.js → flowDiagram-DWJPFMVM.DGP4XvR5.js} +1 -1
  40. package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.C7qt9Mlo.js → ganttDiagram-T4ZO3ILL.BtXtkL4E.js} +1 -1
  41. package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.U30_r82P.js → gitGraphDiagram-UUTBAWPF.B9cPWblK.js} +1 -1
  42. package/dist/docs/assets/chunks/{graph.CyyMyAWv.js → graph.D8HzNexS.js} +1 -1
  43. package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.Dn9ACW3y.js → infoDiagram-42DDH7IO.Bw7CQUpi.js} +1 -1
  44. package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DlIdIGOA.js → ishikawaDiagram-UXIWVN3A.MwkzF6nQ.js} +1 -1
  45. package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.DZj4vy4E.js → journeyDiagram-VCZTEJTY.DIGFF-3C.js} +1 -1
  46. package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.Dl63eMUV.js → kanban-definition-6JOO6SKY.DhYef2BN.js} +1 -1
  47. package/dist/docs/assets/chunks/{layout.BLHZLWPH.js → layout.C0XUxuPi.js} +1 -1
  48. package/dist/docs/assets/chunks/{linear.aXKGKaNw.js → linear.BwNPpZex.js} +1 -1
  49. package/dist/docs/assets/chunks/{min.zWnFcpcc.js → min.CwAQdL7z.js} +1 -1
  50. package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.Qs4MQBea.js → mindmap-definition-QFDTVHPH.pWsSVLsP.js} +1 -1
  51. package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.BmPHgsk7.js → pieDiagram-DEJITSTG.BDJ3FbBy.js} +1 -1
  52. package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.D5MQ3gwA.js → quadrantDiagram-34T5L4WZ.Co80izyB.js} +1 -1
  53. package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.CkdUFrO7.js → requirementDiagram-MS252O5E.JveKw4yx.js} +1 -1
  54. package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.KZrljrAV.js → sankeyDiagram-XADWPNL6.B0A7adPi.js} +1 -1
  55. package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.XMoEW-Lx.js → sequenceDiagram-FGHM5R23.d6JZ5Hre.js} +1 -1
  56. package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.BmTzePLj.js → stateDiagram-FHFEXIEX.DWnL0NQl.js} +1 -1
  57. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.ZEetPk0E.js +1 -0
  58. package/dist/docs/assets/chunks/{theme.BfQzpxsg.js → theme.MJTLx0hh.js} +2 -2
  59. package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.Dug0oamp.js → timeline-definition-GMOUNBTQ.CFS7Ai4c.js} +1 -1
  60. package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.BOTHrEFu.js → vennDiagram-DHZGUBPP.CwSlnjCf.js} +1 -1
  61. package/dist/docs/assets/chunks/wardley-RL74JXVD.3gurI8YA.js +162 -0
  62. package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.CoXKdfi6.js → wardleyDiagram-NUSXRM2D.B_8mvtjh.js} +1 -1
  63. package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.DXoSCjAW.js → xychartDiagram-5P7HB3ND.DtjU5H85.js} +1 -1
  64. package/dist/docs/assets/{guides_slack.md.Bo96y42E.js → guides_slack.md.Bjw2r2gL.js} +5 -5
  65. package/dist/docs/assets/{guides_slack.md.Bo96y42E.lean.js → guides_slack.md.Bjw2r2gL.lean.js} +1 -1
  66. package/dist/docs/assets/reference_cli.md.BvnQM8wd.js +97 -0
  67. package/dist/docs/assets/reference_cli.md.BvnQM8wd.lean.js +1 -0
  68. package/dist/docs/building-with-agents.html +35 -35
  69. package/dist/docs/deployment.html +35 -35
  70. package/dist/docs/evals.html +35 -35
  71. package/dist/docs/guides/agent-to-agent.html +35 -35
  72. package/dist/docs/guides/bitbucket.html +35 -35
  73. package/dist/docs/guides/cloud-agents.html +35 -35
  74. package/dist/docs/guides/convert-automation.html +35 -35
  75. package/dist/docs/guides/github.html +35 -35
  76. package/dist/docs/guides/gitlab.html +35 -35
  77. package/dist/docs/guides/grokbot-agents.html +35 -35
  78. package/dist/docs/guides/hooks.html +35 -35
  79. package/dist/docs/guides/improve.html +35 -35
  80. package/dist/docs/guides/jev.html +35 -35
  81. package/dist/docs/guides/mcp-oauth.html +35 -35
  82. package/dist/docs/guides/opentelemetry.html +35 -35
  83. package/dist/docs/guides/slack.html +39 -39
  84. package/dist/docs/guides/slack.md +16 -1
  85. package/dist/docs/guides/webhooks.html +35 -35
  86. package/dist/docs/hashmap.json +1 -1
  87. package/dist/docs/hillclimbing.html +35 -35
  88. package/dist/docs/index.html +35 -35
  89. package/dist/docs/llms-full.txt +699 -752
  90. package/dist/docs/llms.txt +1 -1
  91. package/dist/docs/quickstart.html +35 -35
  92. package/dist/docs/reference/agent-config.html +35 -35
  93. package/dist/docs/reference/artifacts.html +35 -35
  94. package/dist/docs/reference/channels.html +35 -35
  95. package/dist/docs/reference/cli.html +127 -125
  96. package/dist/docs/reference/cli.md +685 -753
  97. package/dist/docs/reference/connections.html +35 -35
  98. package/dist/docs/reference/evals.html +35 -35
  99. package/dist/docs/reference/extensions.html +35 -35
  100. package/dist/docs/reference/hooks.html +35 -35
  101. package/dist/docs/reference/http-api.html +35 -35
  102. package/dist/docs/reference/instructions.html +35 -35
  103. package/dist/docs/reference/playground.html +35 -35
  104. package/dist/docs/reference/project-layout.html +35 -35
  105. package/dist/docs/reference/prompt.html +35 -35
  106. package/dist/docs/reference/schedules.html +35 -35
  107. package/dist/docs/reference/sessions.html +35 -35
  108. package/dist/docs/reference/skills.html +35 -35
  109. package/dist/docs/reference/subagents.html +35 -35
  110. package/dist/docs/reference/tools.html +35 -35
  111. package/dist/docs/templates/agentic-owners.html +35 -35
  112. package/dist/docs/templates/pr-autofixer.html +35 -35
  113. package/dist/docs/templates/security-reviewer.html +35 -35
  114. package/dist/docs/templates/thermo-quality-review.html +35 -35
  115. package/dist/docs/templates/thermo-review.html +35 -35
  116. package/dist/docs/templates/triage.html +35 -35
  117. package/dist/docs/troubleshooting.html +35 -35
  118. package/dist/files-backends/cursor-hosted.d.ts +11 -17
  119. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  120. package/dist/files-backends/cursor-hosted.js +13 -41
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/internal/artifacts-store.d.ts +11 -0
  124. package/dist/internal/artifacts-store.d.ts.map +1 -1
  125. package/dist/internal/artifacts-store.js +113 -18
  126. package/dist/internal/cli-deploy.d.ts.map +1 -1
  127. package/dist/internal/cli-deploy.js +9 -1
  128. package/dist/internal/cursor/cursor-api-transport.d.ts +37 -0
  129. package/dist/internal/cursor/cursor-api-transport.d.ts.map +1 -0
  130. package/dist/internal/cursor/cursor-api-transport.js +44 -0
  131. package/dist/internal/cursor/hosted-store-secrets.d.ts +21 -0
  132. package/dist/internal/cursor/hosted-store-secrets.d.ts.map +1 -1
  133. package/dist/internal/cursor/hosted-store-secrets.js +26 -0
  134. package/dist/internal/cursor/store-api-client.d.ts +82 -0
  135. package/dist/internal/cursor/store-api-client.d.ts.map +1 -0
  136. package/dist/internal/cursor/store-api-client.js +227 -0
  137. package/dist/internal/deploy-client.d.ts +6 -0
  138. package/dist/internal/deploy-client.d.ts.map +1 -1
  139. package/dist/internal/deploy-client.js +3 -0
  140. package/dist/internal/deploy-manifest.d.ts +8 -0
  141. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  142. package/dist/internal/deploy-manifest.js +7 -1
  143. package/dist/internal/discovery/agent-config.d.ts +3 -1
  144. package/dist/internal/discovery/agent-config.d.ts.map +1 -1
  145. package/dist/internal/discovery/agent-config.js +7 -4
  146. package/dist/internal/framework-storage-selection.d.ts +1 -1
  147. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  148. package/dist/internal/platform-timers.d.ts.map +1 -1
  149. package/dist/internal/platform-timers.js +20 -2
  150. package/dist/internal/reminder-runner.d.ts +79 -1
  151. package/dist/internal/reminder-runner.d.ts.map +1 -1
  152. package/dist/internal/reminder-runner.js +287 -46
  153. package/dist/internal/server.d.ts.map +1 -1
  154. package/dist/internal/server.js +7 -0
  155. package/dist/internal/session-engine.d.ts.map +1 -1
  156. package/dist/internal/session-engine.js +13 -15
  157. package/dist/internal/store-api-protocol.d.ts +134 -0
  158. package/dist/internal/store-api-protocol.d.ts.map +1 -0
  159. package/dist/internal/store-api-protocol.js +126 -0
  160. package/dist/memory.d.ts +49 -10
  161. package/dist/memory.d.ts.map +1 -1
  162. package/dist/memory.js +193 -50
  163. package/dist/playground/assets/{index-CrMWlgUU.js → index-Cs0MKsv4.js} +30 -30
  164. package/dist/playground/assets/index-DLwnR9ys.css +1 -0
  165. package/dist/playground/index.html +2 -2
  166. package/dist/reminders.d.ts +1 -1
  167. package/dist/reminders.d.ts.map +1 -1
  168. package/dist/types.d.ts +44 -10
  169. package/dist/types.d.ts.map +1 -1
  170. package/docs/guides/slack.md +16 -1
  171. package/docs/reference/cli.md +686 -754
  172. package/package.json +1 -1
  173. package/skills/setup-slack/SKILL.md +1 -1
  174. package/src/channels/slack/channel-watch.ts +57 -8
  175. package/src/channels/slack/inbound.ts +24 -0
  176. package/src/channels/slack/slack-channel.ts +127 -4
  177. package/src/channels/slack/types.ts +11 -12
  178. package/src/files-backends/cursor-hosted.ts +31 -68
  179. package/src/index.ts +1 -0
  180. package/src/internal/artifacts-store.ts +131 -25
  181. package/src/internal/cli-deploy.ts +12 -1
  182. package/src/internal/cursor/cursor-api-transport.ts +73 -0
  183. package/src/internal/cursor/hosted-store-secrets.ts +35 -0
  184. package/src/internal/cursor/store-api-client.ts +360 -0
  185. package/src/internal/deploy-client.ts +9 -0
  186. package/src/internal/deploy-manifest.ts +15 -0
  187. package/src/internal/discovery/agent-config.ts +8 -6
  188. package/src/internal/framework-storage-selection.ts +1 -1
  189. package/src/internal/platform-timers.ts +24 -2
  190. package/src/internal/reminder-runner.ts +454 -66
  191. package/src/internal/server.ts +8 -0
  192. package/src/internal/session-engine.ts +17 -22
  193. package/src/internal/store-api-protocol.ts +222 -0
  194. package/src/memory.ts +240 -59
  195. package/src/reminders.ts +1 -0
  196. package/src/types.ts +47 -10
  197. package/dist/docs/assets/chunks/@localSearchIndexroot.BnSgidYE.js +0 -1
  198. package/dist/docs/assets/chunks/channel.DdM5EfNW.js +0 -1
  199. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.CjfGHeg2.js +0 -1
  200. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.CjfGHeg2.js +0 -1
  201. package/dist/docs/assets/chunks/clone.wSOICb_f.js +0 -1
  202. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.Cu5X28zZ.js +0 -1
  203. package/dist/docs/assets/chunks/wardley-RL74JXVD.DXy2i1LS.js +0 -162
  204. package/dist/docs/assets/reference_cli.md.DLWDz9ij.js +0 -95
  205. package/dist/docs/assets/reference_cli.md.DLWDz9ij.lean.js +0 -1
  206. package/dist/playground/assets/index-C61EWMBK.css +0 -1
@@ -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
  }
@@ -1658,11 +1658,18 @@ export async function cmdStop(
1658
1658
  }
1659
1659
  const cancellation =
1660
1660
  "cancellation" in accepted ? accepted.cancellation : undefined;
1661
+ // The backend escalates a publisher's stop of a multi-tenant app to the
1662
+ // application-level suspension; say so, and how to undo it.
1663
+ const stoppedGlobally = "scope" in accepted && accepted.scope === "global";
1661
1664
  if (ctx.noWait) {
1662
1665
  if (ctx.json) {
1663
1666
  ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
1664
1667
  } else {
1665
- ctx.out(`Stop accepted for ${slug} (status: ${accepted.status}).\n`);
1668
+ ctx.out(
1669
+ stoppedGlobally
1670
+ ? `Stop accepted for ${slug} across all tenants (status: ${accepted.status}).\n`
1671
+ : `Stop accepted for ${slug} (status: ${accepted.status}).\n`
1672
+ );
1666
1673
  }
1667
1674
  return cancellation?.failed === 0 || cancellation === undefined ? 0 : 1;
1668
1675
  }
@@ -1681,6 +1688,10 @@ export async function cmdStop(
1681
1688
  }
1682
1689
  if (ctx.json) {
1683
1690
  ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
1691
+ } else if (stoppedGlobally) {
1692
+ ctx.out(
1693
+ `Stopped ${slug} across all tenants (team ${ctx.teamId}); resume with \`${CLI} start ${slug} --global\`.\n`
1694
+ );
1684
1695
  } else {
1685
1696
  ctx.out(`Stopped ${slug} (team ${ctx.teamId}).\n`);
1686
1697
  }
@@ -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";