@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
@@ -919,6 +919,14 @@ export async function startServer(
919
919
  }),
920
920
  architecture,
921
921
  controlPlane: reminderControlPlane,
922
+ // Hosted admissions build their channel args with no request
923
+ // principal (`buildChannelHandlerArgs` → `auth: null`), so a fire that
924
+ // opens a session on a fresh pod uses the same one and a session
925
+ // restored from durable storage still matches.
926
+ hostedGuest:
927
+ hostedReminderControlPlane === undefined
928
+ ? undefined
929
+ : { fireAuth: () => null },
922
930
  });
923
931
  const evalRuns = new EvalRunStore(
924
932
  project.rootDir,
@@ -1105,17 +1105,23 @@ export class SessionEngine {
1105
1105
  sendOptions.continuationToken
1106
1106
  );
1107
1107
  }
1108
- if (
1109
- existing === undefined &&
1110
- sendOptions.hostedSessionId !== undefined &&
1111
- sendOptions.mailboxContinuation?.key ===
1112
- hostedSessionResumeKey(sendOptions.hostedSessionId)
1113
- ) {
1114
- // Explicit control-plane resume of an existing session: the CP named
1115
- // the session by its own id and stamped the matching resume key. The
1116
- // session keeps its original continuation key (a PR lane must stay
1117
- // addressable by its webhooks); this is a follow-up turn by id.
1118
- existing = await this.getOrRestoreSession(sendOptions.hostedSessionId);
1108
+ if (existing === undefined && sendOptions.hostedSessionId !== undefined) {
1109
+ const byId = await this.getOrRestoreSession(sendOptions.hostedSessionId);
1110
+ if (byId !== undefined) {
1111
+ // evt: skips the mailbox index; same hostedSessionId is a Temporal
1112
+ // retry. cont:session:<id> is the explicit CP resume (keep the
1113
+ // original key so a PR lane stays addressable by its webhooks).
1114
+ if (
1115
+ byId.continuationKey !== sendOptions.continuationToken &&
1116
+ sendOptions.mailboxContinuation?.key !==
1117
+ hostedSessionResumeKey(sendOptions.hostedSessionId)
1118
+ ) {
1119
+ throw new V2EngineError(
1120
+ `v2: hosted session id ${sendOptions.hostedSessionId} already belongs to another mailbox`
1121
+ );
1122
+ }
1123
+ existing = byId;
1124
+ }
1119
1125
  }
1120
1126
 
1121
1127
  if (existing !== undefined) {
@@ -1223,17 +1229,6 @@ export class SessionEngine {
1223
1229
  throw new HostedEnqueueNotActiveError(sendOptions.hostedSessionId);
1224
1230
  }
1225
1231
 
1226
- if (sendOptions.hostedSessionId !== undefined) {
1227
- const collision = await this.getOrRestoreSession(
1228
- sendOptions.hostedSessionId
1229
- );
1230
- if (collision !== undefined) {
1231
- throw new V2EngineError(
1232
- `v2: hosted session id ${sendOptions.hostedSessionId} already belongs to another mailbox`
1233
- );
1234
- }
1235
- }
1236
-
1237
1232
  const now = new Date().toISOString();
1238
1233
  const channelState = this.initialChannelState(channelId, sendOptions.state);
1239
1234
  const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Domain-call wire contract for the factory store API:
3
+ * `POST /internal/agentsdk/store/api/<call>` carrying a versioned JSON
4
+ * envelope `{ v, call, payload }`. This is the lane where the **server** owns
5
+ * the storage pattern (journal sharding, rotation, compaction), so a
6
+ * pathological pattern is fixable in a backend deploy instead of a July
7
+ * release plus a rebake of every deployment. The verb lane (`/flush`,
8
+ * `/read`, `/list`) keeps carrying bulk presigned file IO.
9
+ *
10
+ * Deliberately duplicated on the backend
11
+ * (`backend/server/src/factory/agent-sdk/agentsdkStoreApiProtocol.ts`): this
12
+ * package is published to npm, so the backend cannot depend on it and it
13
+ * cannot depend on backend packages. A backend drift test compares the two
14
+ * sources — same shape as the hosted-delivery protocol twins — which is why
15
+ * every exported constant here stays a single `export const NAME = <literal>;`
16
+ * line and every schema field stays on its own line.
17
+ *
18
+ * Evolution rules (the compat contract): additive optional fields only; no
19
+ * renames, no retypes, no semantic reuse of an old field; unknown fields are
20
+ * ignored, never rejected; removing a field means deprecating it in place
21
+ * forever. A breaking change means a new `v`, and the server keeps serving
22
+ * every previous `v`.
23
+ */
24
+
25
+ import { z } from "zod";
26
+
27
+ export const STORE_API_PROTOCOL_VERSION = 1;
28
+
29
+ /** Path family on the factory surface; one segment per call. */
30
+ export const STORE_API_PATH = "/internal/agentsdk/store/api";
31
+
32
+ export const STORE_API_MEMORY_APPEND_CALL = "memory.append";
33
+
34
+ /**
35
+ * Semantic cap on one serialized memory record. Text fields are truncated
36
+ * client-side (`memoryHook` default 2000 chars per field), so a well-formed
37
+ * record is a few KiB; 16 KiB is generous headroom, not a target. The cap is
38
+ * part of the wire contract but rides the layers, not the schema: the
39
+ * backend's store controller enforces it as a typed 4xx, and the store API
40
+ * client (`cursor/store-api-client.ts`) pre-checks it before spending a
41
+ * doomed request.
42
+ */
43
+ export const MEMORY_APPEND_MAX_RECORD_BYTES: number = 16 * 1024;
44
+
45
+ /** Serialized size of a record, as counted against the byte cap. */
46
+ export function serializedRecordBytes(record: unknown): number {
47
+ return new TextEncoder().encode(JSON.stringify(record)).byteLength;
48
+ }
49
+
50
+ /**
51
+ * One turn's memory record as it crosses the wire — July's
52
+ * `TurnMemoryRecord` (memory.ts). The extra-key record mirrors the schema's
53
+ * `.passthrough()`: a newer client's additive optional fields are journaled
54
+ * verbatim rather than stripped by an older server.
55
+ */
56
+ export type TurnMemoryRecordWire = {
57
+ at: string;
58
+ sessionId: string;
59
+ channelId: string;
60
+ status: "completed" | "failed";
61
+ title?: string;
62
+ sdkAgentId?: string;
63
+ userMessage?: string;
64
+ result?: string;
65
+ usage?: Record<string, unknown>;
66
+ } & Record<string, unknown>;
67
+
68
+ /**
69
+ * Wire schema for {@link TurnMemoryRecordWire}. Per-field caps are
70
+ * deliberately absent: the client truncates text at authoring time and
71
+ * {@link MEMORY_APPEND_MAX_RECORD_BYTES} bounds the whole record.
72
+ */
73
+ export const turnMemoryRecordWireSchema: z.ZodType<TurnMemoryRecordWire> = z
74
+ .object({
75
+ at: z.string().min(1),
76
+ sessionId: z.string().min(1),
77
+ channelId: z.string().min(1),
78
+ status: z.enum(["completed", "failed"]),
79
+ title: z.string().optional(),
80
+ sdkAgentId: z.string().optional(),
81
+ userMessage: z.string().optional(),
82
+ result: z.string().optional(),
83
+ usage: z.object({}).passthrough().optional(),
84
+ })
85
+ .passthrough();
86
+
87
+ /** `memory.append` validated payload. */
88
+ export type MemoryAppendPayload = {
89
+ agent: string;
90
+ record: TurnMemoryRecordWire;
91
+ };
92
+
93
+ /**
94
+ * `memory.append` payload shape. The agent name becomes a store key segment
95
+ * on the server, so it must be exactly one segment. Unknown payload fields
96
+ * are stripped (= ignored), per the evolution rules. The byte cap is
97
+ * semantics, not shape — the backend's store controller owns it.
98
+ */
99
+ export const memoryAppendPayloadSchema: z.ZodType<MemoryAppendPayload> =
100
+ z.object({
101
+ agent: z
102
+ .string()
103
+ .min(1)
104
+ .max(256)
105
+ .refine(
106
+ name =>
107
+ !name.includes("/") &&
108
+ !name.includes("\\") &&
109
+ name !== "." &&
110
+ name !== "..",
111
+ { message: "agent must be a single store key segment" }
112
+ ),
113
+ record: turnMemoryRecordWireSchema,
114
+ });
115
+
116
+ /** The versioned request body for the `memory.append` call. */
117
+ export type MemoryAppendEnvelope = {
118
+ v: typeof STORE_API_PROTOCOL_VERSION;
119
+ call: typeof STORE_API_MEMORY_APPEND_CALL;
120
+ payload: MemoryAppendPayload;
121
+ };
122
+
123
+ /** The whole request body. Unknown envelope fields are stripped (= ignored). */
124
+ export const memoryAppendEnvelopeSchema: z.ZodType<MemoryAppendEnvelope> =
125
+ z.object({
126
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
127
+ call: z.literal(STORE_API_MEMORY_APPEND_CALL),
128
+ payload: memoryAppendPayloadSchema,
129
+ });
130
+
131
+ /** `memory.append` success body. */
132
+ export type MemoryAppendResponse = {
133
+ v: typeof STORE_API_PROTOCOL_VERSION;
134
+ ok: true;
135
+ };
136
+
137
+ /**
138
+ * Success body. Failures answer the store surface's standard error JSON
139
+ * (`{ code, error }`) with a matching HTTP status.
140
+ */
141
+ export const memoryAppendResponseSchema: z.ZodType<MemoryAppendResponse> =
142
+ z.object({
143
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
144
+ ok: z.literal(true),
145
+ });
146
+
147
+ export const STORE_API_MEMORY_READ_CALL = "memory.read";
148
+
149
+ /** `memory.read` validated payload. */
150
+ export type MemoryReadPayload = {
151
+ agent: string;
152
+ };
153
+
154
+ /**
155
+ * `memory.read` payload shape — the same single-segment agent name rule as
156
+ * `memory.append`, since the name addresses the same journal key family.
157
+ */
158
+ export const memoryReadPayloadSchema: z.ZodType<MemoryReadPayload> = z.object({
159
+ agent: z
160
+ .string()
161
+ .min(1)
162
+ .max(256)
163
+ .refine(
164
+ name =>
165
+ !name.includes("/") &&
166
+ !name.includes("\\") &&
167
+ name !== "." &&
168
+ name !== "..",
169
+ { message: "agent must be a single store key segment" }
170
+ ),
171
+ });
172
+
173
+ /** The versioned request body for the `memory.read` call. */
174
+ export type MemoryReadEnvelope = {
175
+ v: typeof STORE_API_PROTOCOL_VERSION;
176
+ call: typeof STORE_API_MEMORY_READ_CALL;
177
+ payload: MemoryReadPayload;
178
+ };
179
+
180
+ /** The whole request body. Unknown envelope fields are stripped (= ignored). */
181
+ export const memoryReadEnvelopeSchema: z.ZodType<MemoryReadEnvelope> = z.object(
182
+ {
183
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
184
+ call: z.literal(STORE_API_MEMORY_READ_CALL),
185
+ payload: memoryReadPayloadSchema,
186
+ }
187
+ );
188
+
189
+ /**
190
+ * `memory.read` success body. The call is flush-on-read: the server drains
191
+ * the agent's pending append buffer before presigning. Best-effort: when
192
+ * the drain succeeds the journal includes every append that preceded the
193
+ * read; a busy flush lock, a buffer outage, or an oversized backlog
194
+ * degrades to the already-flushed state (callers must not regress their
195
+ * local view on a shorter download). `journal` is null while the agent has
196
+ * no journal at all.
197
+ */
198
+ export type MemoryReadResponse = {
199
+ v: typeof STORE_API_PROTOCOL_VERSION;
200
+ ok: true;
201
+ journal: {
202
+ url: string;
203
+ expiresAt: string;
204
+ } | null;
205
+ };
206
+
207
+ /**
208
+ * Success body. Failures answer the store surface's standard error JSON
209
+ * (`{ code, error }`) with a matching HTTP status.
210
+ */
211
+ export const memoryReadResponseSchema: z.ZodType<MemoryReadResponse> = z.object(
212
+ {
213
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
214
+ ok: z.literal(true),
215
+ journal: z
216
+ .object({
217
+ url: z.string().min(1),
218
+ expiresAt: z.string().min(1),
219
+ })
220
+ .nullable(),
221
+ }
222
+ );
package/src/memory.ts CHANGED
@@ -18,20 +18,29 @@
18
18
  *
19
19
  * On Cursor-managed hosting the default is {@link agentStoreMemoryBackend}:
20
20
  * the journal lives on the deployment's Agent Store (survives deploys,
21
- * visible on cloud VMs under the store mount) and is mirrored to
22
- * `<stateRoot>/memory/journal.jsonl` so the workspace symlink read path
23
- * keeps working for local-runtime turns.
21
+ * visible on cloud VMs under the store mount), written exclusively through
22
+ * the `memory.append` domain call — the server owns the journal pattern —
23
+ * and mirrored to `<stateRoot>/memory/journal.jsonl` (hydrated per session
24
+ * via `memory.read`, appended locally per turn) so the workspace symlink
25
+ * read path keeps working for local-runtime turns. The
26
+ * `factory-api-v1` pin file exists only on hosted pods — plain local dev
27
+ * runs {@link fileMemoryBackend} and never sees the hosted lane.
24
28
  */
25
29
 
26
30
  import { randomUUID } from "node:crypto";
27
31
  import { appendFile, mkdir, rename, stat, writeFile } from "node:fs/promises";
28
32
  import { join } from "node:path";
29
- import { agentStoreKeys, FileConflictError, type FileSink } from "./files.js";
33
+ import { agentStoreKeys } from "./files.js";
30
34
  import {
31
- cursorHostedFiles,
35
+ type CursorHostedFilesOptions,
32
36
  isCursorHostedFilesAvailable,
33
37
  } from "./files-backends/cursor-hosted.js";
34
38
  import { defineHook } from "./hooks.js";
39
+ import {
40
+ createStoreApiClient,
41
+ isStoreApiLaneEnabled,
42
+ type StoreApiClient,
43
+ } from "./internal/cursor/store-api-client.js";
35
44
  import type { HookContext, HookDefinition, TurnUsage } from "./types.js";
36
45
 
37
46
  /** Name of the shared memory directory under the agent state root. */
@@ -61,6 +70,13 @@ export interface MemoryBackend {
61
70
  record: TurnMemoryRecord,
62
71
  ctx: { stateRoot: string; agentName: string }
63
72
  ): Promise<void>;
73
+ /**
74
+ * Optional session-start hook: bring the backend's local read path up to
75
+ * date before the session's first turn reads it. A freshness upgrade, not
76
+ * a correctness gate — implementations soft-fail and keep whatever read
77
+ * state already exists.
78
+ */
79
+ prepareSession?(ctx: { stateRoot: string; agentName: string }): Promise<void>;
64
80
  }
65
81
 
66
82
  export interface FileMemoryBackendOptions {
@@ -113,86 +129,233 @@ export function fileMemoryBackend(
113
129
  }
114
130
 
115
131
  export interface AgentStoreMemoryBackendOptions {
116
- /** Sink override (tests). Defaults to the deployment's Agent Store. */
117
- sink?: FileSink;
118
- /** Rotation threshold, same meaning as {@link FileMemoryBackendOptions}. */
132
+ /**
133
+ * Local mirror rotation threshold, same meaning as
134
+ * {@link FileMemoryBackendOptions}. The durable journal's rotation is the
135
+ * server's (`memory.append` controller), not this.
136
+ */
119
137
  maxJournalBytes?: number;
138
+ /**
139
+ * Cursor-hosted transport overrides (tests): base URL, credential, store
140
+ * source id, protocol pin, fetch. Feeds the default
141
+ * {@link storeApiClient} and the lane-availability check.
142
+ */
143
+ hosted?: CursorHostedFilesOptions;
144
+ /**
145
+ * Domain-lane client override (tests). Defaults to
146
+ * {@link createStoreApiClient} over {@link hosted} — the module that owns
147
+ * the `memory.append` / `memory.read` transport, typed errors, and
148
+ * bounded backoff.
149
+ */
150
+ storeApiClient?: StoreApiClient;
120
151
  }
121
152
 
122
153
  /**
123
154
  * Journal on the deployment's Agent Store — durable across deploys, visible
124
- * on cloud VMs under the store mount. Appends are read-modify-write with
125
- * etag preconditions (retried on a lost race), then mirrored to
126
- * `<stateRoot>/memory/journal.jsonl` for the workspace symlink read path.
155
+ * on cloud VMs under the store mount, written exclusively through the
156
+ * `memory.append` domain call: the server owns the journal's storage
157
+ * pattern (Redis-buffered flush, rotation), so a pathological pattern is
158
+ * fixable in a backend deploy instead of a July release plus a rebake.
159
+ *
160
+ * There is no client-composed fallback. The read-modify-write CAS lane that
161
+ * predated the domain call (and caused the 2026-09-18 Agent Store read
162
+ * storm) was removed in 0.2.1 together with v2 hosting binding the
163
+ * `factory-api-v1` pin unconditionally and refusing artifacts frozen on an
164
+ * older July. A hosted pod without the pin is therefore a misconfiguration
165
+ * (a server predating the pin change, or a local env naming a hosted store
166
+ * without one): records are dropped loudly, never written client-side.
127
167
  */
128
168
  export function agentStoreMemoryBackend(
129
169
  options: AgentStoreMemoryBackendOptions = {}
130
170
  ): MemoryBackend {
131
- const sink = options.sink ?? cursorHostedFiles();
171
+ const hosted = options.hosted ?? {};
132
172
  const maxJournalBytes = options.maxJournalBytes ?? 5 * 1024 * 1024;
133
- // Appends are chained per journal key (same shape as fileMemoryBackend);
134
- // a failed append must not poison the chain for later turns.
173
+ // Mirror work is chained per journal key (same shape as
174
+ // fileMemoryBackend): hydration and appends on one agent's mirror never
175
+ // interleave, and a failed step must not poison the chain for later turns.
135
176
  const appendChains = new Map<string, Promise<void>>();
136
177
 
137
- const append = async (
178
+ // Transport, envelope, typed errors, and bounded backoff all live in the
179
+ // client; this module only decides which lane an append takes.
180
+ const storeApiClient = options.storeApiClient ?? createStoreApiClient(hosted);
181
+
182
+ /**
183
+ * The domain lane still owes local-runtime sessions their read path:
184
+ * cloud-runtime turns read the journal through the store mount, but
185
+ * local-runtime turns on hosted pods (the default `runtime: "local"`)
186
+ * read `<stateRoot>/memory/journal.jsonl` via the workspace symlink, and
187
+ * on hosting the only writer of that file is this backend. So the lane
188
+ * keeps the mirror live without reintroducing the CAS read pattern:
189
+ * hydrate it once from the store when the file is absent (a fresh pod
190
+ * after a deploy), then append each successfully posted record locally,
191
+ * rotating at {@link maxJournalBytes} like {@link fileMemoryBackend}.
192
+ * Server-buffered records not yet flushed when a pod hydrates are the
193
+ * lane's documented staleness window (~30 s / 64 KiB), and other pods'
194
+ * appends surface on the next hydration — advisory memory, same envelope
195
+ * as the server side.
196
+ */
197
+ const hydratedMirrors = new Set<string>();
198
+ let mirrorRotationSeq = 0;
199
+ const maintainLocalMirror = async (
138
200
  record: TurnMemoryRecord,
139
201
  ctx: { stateRoot: string; agentName: string }
140
202
  ): Promise<void> => {
141
- const key = agentStoreKeys.memoryJournal(ctx.agentName);
142
- const line = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
143
- for (let attempt = 0; attempt < 3; attempt += 1) {
144
- const current = await sink.get(key);
145
- const currentBody =
146
- current === undefined
147
- ? new Uint8Array(0)
148
- : current instanceof Uint8Array
149
- ? current
150
- : current.body;
151
- const etag =
152
- current === undefined || current instanceof Uint8Array
153
- ? undefined
154
- : current.etag;
155
- let next: Uint8Array;
156
- let rotatedStamp: string | undefined;
157
- if (currentBody.byteLength >= maxJournalBytes) {
158
- rotatedStamp = `${Date.now()}-${randomUUID().slice(0, 8)}`;
159
- await sink.put(
160
- agentStoreKeys.memoryJournalRotated(ctx.agentName, rotatedStamp),
161
- currentBody,
162
- { ifMatch: null }
163
- );
164
- next = line;
165
- } else {
166
- next = Buffer.concat([currentBody, line]);
167
- }
168
- try {
169
- await sink.put(key, next, {
170
- ifMatch: current === undefined ? null : etag,
203
+ const dir = join(ctx.stateRoot, MEMORY_DIR_NAME);
204
+ const path = join(dir, "journal.jsonl");
205
+ if (!hydratedMirrors.has(path)) {
206
+ // Appends are chained per journal key, so this guard never races
207
+ // itself for one agent. Backstop for a session that appended before
208
+ // any prepareSession hydration ran (e.g. a custom hook ordering).
209
+ hydratedMirrors.add(path);
210
+ const existing = await stat(path).catch(() => undefined);
211
+ if (existing === undefined) {
212
+ const content = await storeApiClient.memoryReadContent({
213
+ agent: ctx.agentName,
171
214
  });
172
- } catch (error) {
173
- if (error instanceof FileConflictError && attempt < 2) {
174
- continue;
215
+ if (content !== undefined) {
216
+ await mirrorJournalLocally(ctx.stateRoot, content);
175
217
  }
176
- throw error;
177
- }
178
- await mirrorJournalLocally(ctx.stateRoot, next);
179
- if (rotatedStamp !== undefined) {
180
- // Rotated segments sit beside the live journal locally too, so the
181
- // workspace symlink read path keeps pre-rotation history.
182
- await writeFile(
183
- join(ctx.stateRoot, MEMORY_DIR_NAME, `journal-${rotatedStamp}.jsonl`),
184
- currentBody
185
- );
186
218
  }
219
+ }
220
+ await mkdir(dir, { recursive: true });
221
+ const size = (await stat(path).catch(() => undefined))?.size ?? 0;
222
+ if (size >= maxJournalBytes) {
223
+ mirrorRotationSeq += 1;
224
+ await rename(
225
+ path,
226
+ join(dir, `journal-${Date.now()}-${mirrorRotationSeq}.jsonl`)
227
+ );
228
+ }
229
+ await appendFile(path, `${JSON.stringify(record)}\n`, "utf8");
230
+ };
231
+
232
+ /**
233
+ * One `memory.append` domain call; the server appends to the server-owned
234
+ * journal. On success the record is also appended to the local mirror so
235
+ * the workspace symlink read path stays live (see
236
+ * {@link maintainLocalMirror}).
237
+ *
238
+ * Failure policy: log and give up for this record. Memory is advisory and
239
+ * the hook runner treats an append failure as non-fatal to the turn;
240
+ * falling back to the CAS path per-record would reintroduce the write
241
+ * storm under exactly the server brownout that makes this call fail. A
242
+ * failed POST also skips the mirror — the mirror must never show a record
243
+ * the durable journal will not have.
244
+ */
245
+ const appendViaStoreApi = async (
246
+ record: TurnMemoryRecord,
247
+ ctx: { stateRoot: string; agentName: string }
248
+ ): Promise<void> => {
249
+ try {
250
+ await storeApiClient.memoryAppend({ agent: ctx.agentName, record });
251
+ } catch (error) {
252
+ console.warn(
253
+ `[agent-serve] memory.append dropped one record for "${ctx.agentName}": ${
254
+ error instanceof Error ? error.message : String(error)
255
+ }`
256
+ );
187
257
  return;
188
258
  }
259
+ try {
260
+ await maintainLocalMirror(record, ctx);
261
+ } catch (error) {
262
+ console.warn(
263
+ `[agent-serve] memory mirror update failed for "${ctx.agentName}" (journal record is durable): ${
264
+ error instanceof Error ? error.message : String(error)
265
+ }`
266
+ );
267
+ }
268
+ };
269
+
270
+ /**
271
+ * Session-start hydration: one `memory.read` (flush-on-read server-side,
272
+ * so the pending buffer — including a burst another pod appended — lands
273
+ * first) and the mirror is rewritten to global state; the session then
274
+ * reads everything appended before it started. Soft-fail: a failed
275
+ * hydration keeps the existing mirror.
276
+ */
277
+ const hydrateMirror = async (ctx: {
278
+ stateRoot: string;
279
+ agentName: string;
280
+ }): Promise<void> => {
281
+ const path = join(ctx.stateRoot, MEMORY_DIR_NAME, "journal.jsonl");
282
+ try {
283
+ const content = await storeApiClient.memoryReadContent({
284
+ agent: ctx.agentName,
285
+ });
286
+ if (content !== undefined) {
287
+ // Never regress the read path: every mirrored record was
288
+ // server-acked before it was written locally, so a download SHORTER
289
+ // than the mirror means the server's flush-on-read was degraded (a
290
+ // busy lock, a Redis outage — the acked tail is still buffered) or
291
+ // the journal rotated. Keeping the richer mirror loses nothing;
292
+ // rewriting would hide records the agent has already seen.
293
+ const mirroredBytes =
294
+ (await stat(path).catch(() => undefined))?.size ?? 0;
295
+ if (content.byteLength >= mirroredBytes) {
296
+ await mirrorJournalLocally(ctx.stateRoot, content);
297
+ }
298
+ }
299
+ // Either way the read answered: skip the append path's absent-file
300
+ // hydration for the rest of this process.
301
+ hydratedMirrors.add(path);
302
+ } catch (error) {
303
+ console.warn(
304
+ `[agent-serve] memory hydration failed for "${ctx.agentName}" (keeping the existing mirror): ${
305
+ error instanceof Error ? error.message : String(error)
306
+ }`
307
+ );
308
+ }
309
+ };
310
+
311
+ /**
312
+ * Hosted pods carry the pin and a provisioned store by construction (v2
313
+ * hosting binds `factory-api-v1` unconditionally and refuses pre-0.2.1
314
+ * artifacts). Reaching this without them means a misconfigured
315
+ * environment; there is no client-side write to fall back to.
316
+ */
317
+ const refuseMisconfiguredLane = (agentName: string, verb: string): void => {
318
+ console.error(
319
+ `[agent-serve] memory ${verb} for "${agentName}" dropped: the store API lane is unavailable (missing/foreign AGENT_SERVE_STORE_PROTOCOL pin or no provisioned store). This July has no client-side journal fallback; fix the hosting pin.`
320
+ );
189
321
  };
190
322
 
191
323
  return {
324
+ async prepareSession(ctx) {
325
+ // Chained on the same per-journal key as appends, so hydration never
326
+ // interleaves with an append's mirror write.
327
+ const key = agentStoreKeys.memoryJournal(ctx.agentName);
328
+ const prior = appendChains.get(key) ?? Promise.resolve();
329
+ const next = prior
330
+ .catch(() => {})
331
+ .then(async () => {
332
+ if (!isStoreApiLaneEnabled(hosted)) {
333
+ refuseMisconfiguredLane(ctx.agentName, "hydration");
334
+ return;
335
+ }
336
+ await hydrateMirror(ctx);
337
+ });
338
+ appendChains.set(key, next);
339
+ await next;
340
+ },
341
+
192
342
  async appendTurn(record, ctx) {
343
+ // Soft-fail by construction (appendViaStoreApi never throws), but
344
+ // still chained per journal key: the local mirror's hydrate-then-
345
+ // append must not interleave with itself. Lane availability is
346
+ // re-resolved lazily per append, so a pin bound after serve start
347
+ // takes effect without a restart.
193
348
  const key = agentStoreKeys.memoryJournal(ctx.agentName);
194
349
  const prior = appendChains.get(key) ?? Promise.resolve();
195
- const next = prior.catch(() => {}).then(() => append(record, ctx));
350
+ const next = prior
351
+ .catch(() => {})
352
+ .then(async () => {
353
+ if (!isStoreApiLaneEnabled(hosted)) {
354
+ refuseMisconfiguredLane(ctx.agentName, "append");
355
+ return;
356
+ }
357
+ await appendViaStoreApi(record, ctx);
358
+ });
196
359
  appendChains.set(key, next);
197
360
  await next;
198
361
  },
@@ -300,6 +463,24 @@ export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
300
463
 
301
464
  return defineHook({
302
465
  events: {
466
+ async "session.started"(_event, ctx) {
467
+ // Bring the mirror (the workspace symlink read path) up to date
468
+ // before the session's first turn reads it. Backends without a
469
+ // prepareSession (plain files) have nothing to freshen. Soft-fail:
470
+ // hydration is a freshness upgrade, never a turn blocker.
471
+ try {
472
+ await backend.prepareSession?.({
473
+ stateRoot: ctx.stateRoot,
474
+ agentName: ctx.agent.name,
475
+ });
476
+ } catch (error) {
477
+ console.warn(
478
+ `[agent-serve] memory hydration at session start failed: ${
479
+ error instanceof Error ? error.message : String(error)
480
+ }`
481
+ );
482
+ }
483
+ },
303
484
  async "message.received"(event, ctx) {
304
485
  pendingMessages.set(
305
486
  pendingKey(ctx, event.turnId),
package/src/reminders.ts CHANGED
@@ -42,6 +42,7 @@ export type {
42
42
  ReminderDefinition,
43
43
  ReminderFireContext,
44
44
  ReminderFireResult,
45
+ ReminderFollowupOptions,
45
46
  ReminderHandlerConfig,
46
47
  ReminderHostApi,
47
48
  ReminderInfo,