@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
@@ -0,0 +1,126 @@
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
+ import { z } from "zod";
25
+ export const STORE_API_PROTOCOL_VERSION = 1;
26
+ /** Path family on the factory surface; one segment per call. */
27
+ export const STORE_API_PATH = "/internal/agentsdk/store/api";
28
+ export const STORE_API_MEMORY_APPEND_CALL = "memory.append";
29
+ /**
30
+ * Semantic cap on one serialized memory record. Text fields are truncated
31
+ * client-side (`memoryHook` default 2000 chars per field), so a well-formed
32
+ * record is a few KiB; 16 KiB is generous headroom, not a target. The cap is
33
+ * part of the wire contract but rides the layers, not the schema: the
34
+ * backend's store controller enforces it as a typed 4xx, and the store API
35
+ * client (`cursor/store-api-client.ts`) pre-checks it before spending a
36
+ * doomed request.
37
+ */
38
+ export const MEMORY_APPEND_MAX_RECORD_BYTES = 16 * 1024;
39
+ /** Serialized size of a record, as counted against the byte cap. */
40
+ export function serializedRecordBytes(record) {
41
+ return new TextEncoder().encode(JSON.stringify(record)).byteLength;
42
+ }
43
+ /**
44
+ * Wire schema for {@link TurnMemoryRecordWire}. Per-field caps are
45
+ * deliberately absent: the client truncates text at authoring time and
46
+ * {@link MEMORY_APPEND_MAX_RECORD_BYTES} bounds the whole record.
47
+ */
48
+ export const turnMemoryRecordWireSchema = z
49
+ .object({
50
+ at: z.string().min(1),
51
+ sessionId: z.string().min(1),
52
+ channelId: z.string().min(1),
53
+ status: z.enum(["completed", "failed"]),
54
+ title: z.string().optional(),
55
+ sdkAgentId: z.string().optional(),
56
+ userMessage: z.string().optional(),
57
+ result: z.string().optional(),
58
+ usage: z.object({}).passthrough().optional(),
59
+ })
60
+ .passthrough();
61
+ /**
62
+ * `memory.append` payload shape. The agent name becomes a store key segment
63
+ * on the server, so it must be exactly one segment. Unknown payload fields
64
+ * are stripped (= ignored), per the evolution rules. The byte cap is
65
+ * semantics, not shape — the backend's store controller owns it.
66
+ */
67
+ export const memoryAppendPayloadSchema = z.object({
68
+ agent: z
69
+ .string()
70
+ .min(1)
71
+ .max(256)
72
+ .refine(name => !name.includes("/") &&
73
+ !name.includes("\\") &&
74
+ name !== "." &&
75
+ name !== "..", { message: "agent must be a single store key segment" }),
76
+ record: turnMemoryRecordWireSchema,
77
+ });
78
+ /** The whole request body. Unknown envelope fields are stripped (= ignored). */
79
+ export const memoryAppendEnvelopeSchema = z.object({
80
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
81
+ call: z.literal(STORE_API_MEMORY_APPEND_CALL),
82
+ payload: memoryAppendPayloadSchema,
83
+ });
84
+ /**
85
+ * Success body. Failures answer the store surface's standard error JSON
86
+ * (`{ code, error }`) with a matching HTTP status.
87
+ */
88
+ export const memoryAppendResponseSchema = z.object({
89
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
90
+ ok: z.literal(true),
91
+ });
92
+ export const STORE_API_MEMORY_READ_CALL = "memory.read";
93
+ /**
94
+ * `memory.read` payload shape — the same single-segment agent name rule as
95
+ * `memory.append`, since the name addresses the same journal key family.
96
+ */
97
+ export const memoryReadPayloadSchema = z.object({
98
+ agent: z
99
+ .string()
100
+ .min(1)
101
+ .max(256)
102
+ .refine(name => !name.includes("/") &&
103
+ !name.includes("\\") &&
104
+ name !== "." &&
105
+ name !== "..", { message: "agent must be a single store key segment" }),
106
+ });
107
+ /** The whole request body. Unknown envelope fields are stripped (= ignored). */
108
+ export const memoryReadEnvelopeSchema = z.object({
109
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
110
+ call: z.literal(STORE_API_MEMORY_READ_CALL),
111
+ payload: memoryReadPayloadSchema,
112
+ });
113
+ /**
114
+ * Success body. Failures answer the store surface's standard error JSON
115
+ * (`{ code, error }`) with a matching HTTP status.
116
+ */
117
+ export const memoryReadResponseSchema = z.object({
118
+ v: z.literal(STORE_API_PROTOCOL_VERSION),
119
+ ok: z.literal(true),
120
+ journal: z
121
+ .object({
122
+ url: z.string().min(1),
123
+ expiresAt: z.string().min(1),
124
+ })
125
+ .nullable(),
126
+ });
package/dist/memory.d.ts CHANGED
@@ -18,11 +18,16 @@
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
- import { type FileSink } from "./files.js";
29
+ import { type CursorHostedFilesOptions } from "./files-backends/cursor-hosted.js";
30
+ import { type StoreApiClient } from "./internal/cursor/store-api-client.js";
26
31
  import type { HookDefinition, TurnUsage } from "./types.js";
27
32
  /** Name of the shared memory directory under the agent state root. */
28
33
  export declare const MEMORY_DIR_NAME = "memory";
@@ -49,6 +54,16 @@ export interface MemoryBackend {
49
54
  stateRoot: string;
50
55
  agentName: string;
51
56
  }): Promise<void>;
57
+ /**
58
+ * Optional session-start hook: bring the backend's local read path up to
59
+ * date before the session's first turn reads it. A freshness upgrade, not
60
+ * a correctness gate — implementations soft-fail and keep whatever read
61
+ * state already exists.
62
+ */
63
+ prepareSession?(ctx: {
64
+ stateRoot: string;
65
+ agentName: string;
66
+ }): Promise<void>;
52
67
  }
53
68
  export interface FileMemoryBackendOptions {
54
69
  /**
@@ -64,20 +79,40 @@ export interface FileMemoryBackendOptions {
64
79
  */
65
80
  export declare function fileMemoryBackend(options?: FileMemoryBackendOptions): MemoryBackend;
66
81
  export interface AgentStoreMemoryBackendOptions {
67
- /** Sink override (tests). Defaults to the deployment's Agent Store. */
68
- sink?: FileSink;
69
- /** Rotation threshold, same meaning as {@link FileMemoryBackendOptions}. */
82
+ /**
83
+ * Local mirror rotation threshold, same meaning as
84
+ * {@link FileMemoryBackendOptions}. The durable journal's rotation is the
85
+ * server's (`memory.append` controller), not this.
86
+ */
70
87
  maxJournalBytes?: number;
71
- /** Backoff sleep override (tests). */
72
- sleep?: (ms: number) => Promise<void>;
73
- /** Jitter source override (tests). Returns [0, 1). */
74
- random?: () => number;
88
+ /**
89
+ * Cursor-hosted transport overrides (tests): base URL, credential, store
90
+ * source id, protocol pin, fetch. Feeds the default
91
+ * {@link storeApiClient} and the lane-availability check.
92
+ */
93
+ hosted?: CursorHostedFilesOptions;
94
+ /**
95
+ * Domain-lane client override (tests). Defaults to
96
+ * {@link createStoreApiClient} over {@link hosted} — the module that owns
97
+ * the `memory.append` / `memory.read` transport, typed errors, and
98
+ * bounded backoff.
99
+ */
100
+ storeApiClient?: StoreApiClient;
75
101
  }
76
102
  /**
77
103
  * Journal on the deployment's Agent Store — durable across deploys, visible
78
- * on cloud VMs under the store mount. Appends are read-modify-write with
79
- * etag preconditions (retried on a lost race), then mirrored to
80
- * `<stateRoot>/memory/journal.jsonl` for the workspace symlink read path.
104
+ * on cloud VMs under the store mount, written exclusively through the
105
+ * `memory.append` domain call: the server owns the journal's storage
106
+ * pattern (Redis-buffered flush, rotation), so a pathological pattern is
107
+ * fixable in a backend deploy instead of a July release plus a rebake.
108
+ *
109
+ * There is no client-composed fallback. The read-modify-write CAS lane that
110
+ * predated the domain call (and caused the 2026-09-18 Agent Store read
111
+ * storm) was removed in 0.2.1 together with v2 hosting binding the
112
+ * `factory-api-v1` pin unconditionally and refusing artifacts frozen on an
113
+ * older July. A hosted pod without the pin is therefore a misconfiguration
114
+ * (a server predating the pin change, or a local env naming a hosted store
115
+ * without one): records are dropped loudly, never written client-side.
81
116
  */
82
117
  export declare function agentStoreMemoryBackend(options?: AgentStoreMemoryBackendOptions): MemoryBackend;
83
118
  export interface MemoryHookOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM9E,OAAO,KAAK,EAAe,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,sEAAsE;AACtE,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,UAAU,CACR,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAgCf;AAED,MAAM,WAAW,8BAA8B;IAC7C,uEAAuE;IACvE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAsBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,aAAa,CAgFf;AAqBD,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CA0F1E"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAe,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,sEAAsE;AACtE,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,UAAU,CACR,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAgCf;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,aAAa,CAiMf;AAqBD,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CA4G1E"}
package/dist/memory.js CHANGED
@@ -18,9 +18,13 @@
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
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
30
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -34,9 +38,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
34
38
  import { randomUUID } from "node:crypto";
35
39
  import { appendFile, mkdir, rename, stat, writeFile } from "node:fs/promises";
36
40
  import { join } from "node:path";
37
- import { agentStoreKeys, FileConflictError } from "./files.js";
38
- import { cursorHostedFiles, isCursorHostedFilesAvailable, } from "./files-backends/cursor-hosted.js";
41
+ import { agentStoreKeys } from "./files.js";
42
+ import { isCursorHostedFilesAvailable, } from "./files-backends/cursor-hosted.js";
39
43
  import { defineHook } from "./hooks.js";
44
+ import { createStoreApiClient, isStoreApiLaneEnabled, } from "./internal/cursor/store-api-client.js";
40
45
  /** Name of the shared memory directory under the agent state root. */
41
46
  export const MEMORY_DIR_NAME = "memory";
42
47
  /**
@@ -80,93 +85,189 @@ export function fileMemoryBackend(options = {}) {
80
85
  },
81
86
  };
82
87
  }
83
- /**
84
- * CAS attempts per append. The journal is one deployment-wide key written by
85
- * every engine pod, so under concurrent turn traffic the etag precondition
86
- * loses races routinely; each retry needs headroom to land.
87
- */
88
- const MEMORY_CAS_MAX_ATTEMPTS = 5;
89
- /**
90
- * Jittered exponential backoff between CAS attempts. Retrying immediately is
91
- * a fleet-wide stampede: every losing pod re-GETs the full journal and
92
- * re-PUTs in lockstep, which multiplied reads ~65x on one deployment during
93
- * the 2026-09-18 Agent Store read storm and tripped the store's
94
- * per-service-account read limit (every pod shares the deployment
95
- * credential). Jitter desynchronizes the writers so most retries land on the
96
- * first or second attempt.
97
- */
98
- function memoryCasBackoffMs(attempt, random) {
99
- return 100 * Math.pow(2, attempt) * (0.5 + random());
100
- }
101
88
  /**
102
89
  * Journal on the deployment's Agent Store — durable across deploys, visible
103
- * on cloud VMs under the store mount. Appends are read-modify-write with
104
- * etag preconditions (retried on a lost race), then mirrored to
105
- * `<stateRoot>/memory/journal.jsonl` for the workspace symlink read path.
90
+ * on cloud VMs under the store mount, written exclusively through the
91
+ * `memory.append` domain call: the server owns the journal's storage
92
+ * pattern (Redis-buffered flush, rotation), so a pathological pattern is
93
+ * fixable in a backend deploy instead of a July release plus a rebake.
94
+ *
95
+ * There is no client-composed fallback. The read-modify-write CAS lane that
96
+ * predated the domain call (and caused the 2026-09-18 Agent Store read
97
+ * storm) was removed in 0.2.1 together with v2 hosting binding the
98
+ * `factory-api-v1` pin unconditionally and refusing artifacts frozen on an
99
+ * older July. A hosted pod without the pin is therefore a misconfiguration
100
+ * (a server predating the pin change, or a local env naming a hosted store
101
+ * without one): records are dropped loudly, never written client-side.
106
102
  */
107
103
  export function agentStoreMemoryBackend(options = {}) {
108
- var _a, _b, _c, _d;
109
- const sink = (_a = options.sink) !== null && _a !== void 0 ? _a : cursorHostedFiles();
104
+ var _a, _b, _c;
105
+ const hosted = (_a = options.hosted) !== null && _a !== void 0 ? _a : {};
110
106
  const maxJournalBytes = (_b = options.maxJournalBytes) !== null && _b !== void 0 ? _b : 5 * 1024 * 1024;
111
- const sleep = (_c = options.sleep) !== null && _c !== void 0 ? _c : ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
112
- const random = (_d = options.random) !== null && _d !== void 0 ? _d : Math.random;
113
- // Appends are chained per journal key (same shape as fileMemoryBackend);
114
- // a failed append must not poison the chain for later turns. The chain
115
- // serializes appends within this pod only — pods race each other on the
116
- // shared key, which is what the CAS retry (with backoff) absorbs.
107
+ // Mirror work is chained per journal key (same shape as
108
+ // fileMemoryBackend): hydration and appends on one agent's mirror never
109
+ // interleave, and a failed step must not poison the chain for later turns.
117
110
  const appendChains = new Map();
118
- const append = (record, ctx) => __awaiter(this, void 0, void 0, function* () {
119
- const key = agentStoreKeys.memoryJournal(ctx.agentName);
120
- const line = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
121
- for (let attempt = 0; attempt < MEMORY_CAS_MAX_ATTEMPTS; attempt += 1) {
122
- const current = yield sink.get(key);
123
- const currentBody = current === undefined
124
- ? new Uint8Array(0)
125
- : current instanceof Uint8Array
126
- ? current
127
- : current.body;
128
- const etag = current === undefined || current instanceof Uint8Array
129
- ? undefined
130
- : current.etag;
131
- let next;
132
- let rotatedStamp;
133
- if (currentBody.byteLength >= maxJournalBytes) {
134
- rotatedStamp = `${Date.now()}-${randomUUID().slice(0, 8)}`;
135
- yield sink.put(agentStoreKeys.memoryJournalRotated(ctx.agentName, rotatedStamp), currentBody, { ifMatch: null });
136
- next = line;
137
- }
138
- else {
139
- next = Buffer.concat([currentBody, line]);
140
- }
141
- try {
142
- yield sink.put(key, next, {
143
- ifMatch: current === undefined ? null : etag,
111
+ // Transport, envelope, typed errors, and bounded backoff all live in the
112
+ // client; this module only decides which lane an append takes.
113
+ const storeApiClient = (_c = options.storeApiClient) !== null && _c !== void 0 ? _c : createStoreApiClient(hosted);
114
+ /**
115
+ * The domain lane still owes local-runtime sessions their read path:
116
+ * cloud-runtime turns read the journal through the store mount, but
117
+ * local-runtime turns on hosted pods (the default `runtime: "local"`)
118
+ * read `<stateRoot>/memory/journal.jsonl` via the workspace symlink, and
119
+ * on hosting the only writer of that file is this backend. So the lane
120
+ * keeps the mirror live without reintroducing the CAS read pattern:
121
+ * hydrate it once from the store when the file is absent (a fresh pod
122
+ * after a deploy), then append each successfully posted record locally,
123
+ * rotating at {@link maxJournalBytes} like {@link fileMemoryBackend}.
124
+ * Server-buffered records not yet flushed when a pod hydrates are the
125
+ * lane's documented staleness window (~30 s / 64 KiB), and other pods'
126
+ * appends surface on the next hydration — advisory memory, same envelope
127
+ * as the server side.
128
+ */
129
+ const hydratedMirrors = new Set();
130
+ let mirrorRotationSeq = 0;
131
+ const maintainLocalMirror = (record, ctx) => __awaiter(this, void 0, void 0, function* () {
132
+ var _a;
133
+ var _b;
134
+ const dir = join(ctx.stateRoot, MEMORY_DIR_NAME);
135
+ const path = join(dir, "journal.jsonl");
136
+ if (!hydratedMirrors.has(path)) {
137
+ // Appends are chained per journal key, so this guard never races
138
+ // itself for one agent. Backstop for a session that appended before
139
+ // any prepareSession hydration ran (e.g. a custom hook ordering).
140
+ hydratedMirrors.add(path);
141
+ const existing = yield stat(path).catch(() => undefined);
142
+ if (existing === undefined) {
143
+ const content = yield storeApiClient.memoryReadContent({
144
+ agent: ctx.agentName,
144
145
  });
145
- }
146
- catch (error) {
147
- if (error instanceof FileConflictError &&
148
- attempt < MEMORY_CAS_MAX_ATTEMPTS - 1) {
149
- yield sleep(memoryCasBackoffMs(attempt, random));
150
- continue;
146
+ if (content !== undefined) {
147
+ yield mirrorJournalLocally(ctx.stateRoot, content);
151
148
  }
152
- throw error;
153
- }
154
- yield mirrorJournalLocally(ctx.stateRoot, next);
155
- if (rotatedStamp !== undefined) {
156
- // Rotated segments sit beside the live journal locally too, so the
157
- // workspace symlink read path keeps pre-rotation history.
158
- yield writeFile(join(ctx.stateRoot, MEMORY_DIR_NAME, `journal-${rotatedStamp}.jsonl`), currentBody);
159
149
  }
150
+ }
151
+ yield mkdir(dir, { recursive: true });
152
+ const size = (_b = (_a = (yield stat(path).catch(() => undefined))) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
153
+ if (size >= maxJournalBytes) {
154
+ mirrorRotationSeq += 1;
155
+ yield rename(path, join(dir, `journal-${Date.now()}-${mirrorRotationSeq}.jsonl`));
156
+ }
157
+ yield appendFile(path, `${JSON.stringify(record)}\n`, "utf8");
158
+ });
159
+ /**
160
+ * One `memory.append` domain call; the server appends to the server-owned
161
+ * journal. On success the record is also appended to the local mirror so
162
+ * the workspace symlink read path stays live (see
163
+ * {@link maintainLocalMirror}).
164
+ *
165
+ * Failure policy: log and give up for this record. Memory is advisory and
166
+ * the hook runner treats an append failure as non-fatal to the turn;
167
+ * falling back to the CAS path per-record would reintroduce the write
168
+ * storm under exactly the server brownout that makes this call fail. A
169
+ * failed POST also skips the mirror — the mirror must never show a record
170
+ * the durable journal will not have.
171
+ */
172
+ const appendViaStoreApi = (record, ctx) => __awaiter(this, void 0, void 0, function* () {
173
+ try {
174
+ yield storeApiClient.memoryAppend({ agent: ctx.agentName, record });
175
+ }
176
+ catch (error) {
177
+ console.warn(`[agent-serve] memory.append dropped one record for "${ctx.agentName}": ${error instanceof Error ? error.message : String(error)}`);
160
178
  return;
161
179
  }
180
+ try {
181
+ yield maintainLocalMirror(record, ctx);
182
+ }
183
+ catch (error) {
184
+ console.warn(`[agent-serve] memory mirror update failed for "${ctx.agentName}" (journal record is durable): ${error instanceof Error ? error.message : String(error)}`);
185
+ }
186
+ });
187
+ /**
188
+ * Session-start hydration: one `memory.read` (flush-on-read server-side,
189
+ * so the pending buffer — including a burst another pod appended — lands
190
+ * first) and the mirror is rewritten to global state; the session then
191
+ * reads everything appended before it started. Soft-fail: a failed
192
+ * hydration keeps the existing mirror.
193
+ */
194
+ const hydrateMirror = (ctx) => __awaiter(this, void 0, void 0, function* () {
195
+ var _a;
196
+ var _b;
197
+ const path = join(ctx.stateRoot, MEMORY_DIR_NAME, "journal.jsonl");
198
+ try {
199
+ const content = yield storeApiClient.memoryReadContent({
200
+ agent: ctx.agentName,
201
+ });
202
+ if (content !== undefined) {
203
+ // Never regress the read path: every mirrored record was
204
+ // server-acked before it was written locally, so a download SHORTER
205
+ // than the mirror means the server's flush-on-read was degraded (a
206
+ // busy lock, a Redis outage — the acked tail is still buffered) or
207
+ // the journal rotated. Keeping the richer mirror loses nothing;
208
+ // rewriting would hide records the agent has already seen.
209
+ const mirroredBytes = (_b = (_a = (yield stat(path).catch(() => undefined))) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
210
+ if (content.byteLength >= mirroredBytes) {
211
+ yield mirrorJournalLocally(ctx.stateRoot, content);
212
+ }
213
+ }
214
+ // Either way the read answered: skip the append path's absent-file
215
+ // hydration for the rest of this process.
216
+ hydratedMirrors.add(path);
217
+ }
218
+ catch (error) {
219
+ console.warn(`[agent-serve] memory hydration failed for "${ctx.agentName}" (keeping the existing mirror): ${error instanceof Error ? error.message : String(error)}`);
220
+ }
162
221
  });
222
+ /**
223
+ * Hosted pods carry the pin and a provisioned store by construction (v2
224
+ * hosting binds `factory-api-v1` unconditionally and refuses pre-0.2.1
225
+ * artifacts). Reaching this without them means a misconfigured
226
+ * environment; there is no client-side write to fall back to.
227
+ */
228
+ const refuseMisconfiguredLane = (agentName, verb) => {
229
+ console.error(`[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.`);
230
+ };
163
231
  return {
232
+ prepareSession(ctx) {
233
+ return __awaiter(this, void 0, void 0, function* () {
234
+ var _a;
235
+ // Chained on the same per-journal key as appends, so hydration never
236
+ // interleaves with an append's mirror write.
237
+ const key = agentStoreKeys.memoryJournal(ctx.agentName);
238
+ const prior = (_a = appendChains.get(key)) !== null && _a !== void 0 ? _a : Promise.resolve();
239
+ const next = prior
240
+ .catch(() => { })
241
+ .then(() => __awaiter(this, void 0, void 0, function* () {
242
+ if (!isStoreApiLaneEnabled(hosted)) {
243
+ refuseMisconfiguredLane(ctx.agentName, "hydration");
244
+ return;
245
+ }
246
+ yield hydrateMirror(ctx);
247
+ }));
248
+ appendChains.set(key, next);
249
+ yield next;
250
+ });
251
+ },
164
252
  appendTurn(record, ctx) {
165
253
  return __awaiter(this, void 0, void 0, function* () {
166
254
  var _a;
255
+ // Soft-fail by construction (appendViaStoreApi never throws), but
256
+ // still chained per journal key: the local mirror's hydrate-then-
257
+ // append must not interleave with itself. Lane availability is
258
+ // re-resolved lazily per append, so a pin bound after serve start
259
+ // takes effect without a restart.
167
260
  const key = agentStoreKeys.memoryJournal(ctx.agentName);
168
261
  const prior = (_a = appendChains.get(key)) !== null && _a !== void 0 ? _a : Promise.resolve();
169
- const next = prior.catch(() => { }).then(() => append(record, ctx));
262
+ const next = prior
263
+ .catch(() => { })
264
+ .then(() => __awaiter(this, void 0, void 0, function* () {
265
+ if (!isStoreApiLaneEnabled(hosted)) {
266
+ refuseMisconfiguredLane(ctx.agentName, "append");
267
+ return;
268
+ }
269
+ yield appendViaStoreApi(record, ctx);
270
+ }));
170
271
  appendChains.set(key, next);
171
272
  yield next;
172
273
  });
@@ -254,6 +355,24 @@ export function memoryHook(options = {}) {
254
355
  };
255
356
  return defineHook({
256
357
  events: {
358
+ "session.started"(_event, ctx) {
359
+ return __awaiter(this, void 0, void 0, function* () {
360
+ var _a;
361
+ // Bring the mirror (the workspace symlink read path) up to date
362
+ // before the session's first turn reads it. Backends without a
363
+ // prepareSession (plain files) have nothing to freshen. Soft-fail:
364
+ // hydration is a freshness upgrade, never a turn blocker.
365
+ try {
366
+ yield (_a = backend.prepareSession) === null || _a === void 0 ? void 0 : _a.call(backend, {
367
+ stateRoot: ctx.stateRoot,
368
+ agentName: ctx.agent.name,
369
+ });
370
+ }
371
+ catch (error) {
372
+ console.warn(`[agent-serve] memory hydration at session start failed: ${error instanceof Error ? error.message : String(error)}`);
373
+ }
374
+ });
375
+ },
257
376
  "message.received"(event, ctx) {
258
377
  return __awaiter(this, void 0, void 0, function* () {
259
378
  pendingMessages.set(pendingKey(ctx, event.turnId), truncate(event.data.text, maxTextLength));