@cursor/july 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/AGENTS.md +24 -2
  2. package/README.md +25 -15
  3. package/dist/bin/agent-serve.js +101 -12
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +31 -14
  6. package/dist/channels/github/cursor-account.d.ts +43 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -0
  8. package/dist/channels/github/cursor-account.js +95 -0
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +46 -9
  11. package/dist/channels/github/index.d.ts +2 -2
  12. package/dist/channels/github/index.js +2 -2
  13. package/dist/channels/github/types.d.ts +17 -0
  14. package/dist/channels/github/types.d.ts.map +1 -1
  15. package/dist/channels/slack/slack-channel.d.ts +8 -2
  16. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  17. package/dist/channels/slack/slack-channel.js +8 -0
  18. package/dist/channels/slack/types.d.ts +24 -3
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/channels/slack/types.js +15 -1
  21. package/dist/docs/404.html +2 -2
  22. package/dist/docs/ab.html +8 -8
  23. package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
  24. package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
  25. package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
  26. package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
  27. package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
  28. package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
  29. package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
  30. package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
  31. package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
  32. package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
  33. package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
  34. package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
  35. package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
  36. package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
  37. package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
  38. package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
  39. package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
  40. package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
  41. package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
  42. package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
  43. package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
  44. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
  45. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
  46. package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
  47. package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
  48. package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
  49. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
  50. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
  51. package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
  52. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
  53. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
  54. package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
  55. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
  56. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
  58. package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
  59. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
  60. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
  61. package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
  62. package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
  63. package/dist/docs/building-with-agents.html +4 -4
  64. package/dist/docs/concepts.html +4 -4
  65. package/dist/docs/deployment.html +7 -7
  66. package/dist/docs/evals.html +7 -7
  67. package/dist/docs/guides/agent-to-agent.html +6 -6
  68. package/dist/docs/guides/cloud-runtime.html +5 -5
  69. package/dist/docs/guides/github.html +14 -7
  70. package/dist/docs/guides/human-in-the-loop.html +6 -6
  71. package/dist/docs/guides/slack.html +8 -8
  72. package/dist/docs/guides/webhooks.html +6 -6
  73. package/dist/docs/hashmap.json +1 -1
  74. package/dist/docs/hillclimbing.html +5 -5
  75. package/dist/docs/index.html +7 -7
  76. package/dist/docs/quickstart.html +180 -23
  77. package/dist/docs/reference/agent-config.html +7 -7
  78. package/dist/docs/reference/channels.html +8 -8
  79. package/dist/docs/reference/cli.html +4 -4
  80. package/dist/docs/reference/connections.html +5 -5
  81. package/dist/docs/reference/hooks.html +5 -5
  82. package/dist/docs/reference/http-api.html +6 -6
  83. package/dist/docs/reference/instructions.html +13 -11
  84. package/dist/docs/reference/playground.html +4 -4
  85. package/dist/docs/reference/project-layout.html +4 -4
  86. package/dist/docs/reference/schedules.html +7 -7
  87. package/dist/docs/reference/sessions.html +4 -4
  88. package/dist/docs/reference/skills.html +6 -6
  89. package/dist/docs/reference/subagents.html +6 -6
  90. package/dist/docs/reference/tools.html +7 -7
  91. package/dist/docs/scaffolding-agents.html +5 -5
  92. package/dist/docs/storage.html +41 -0
  93. package/dist/docs/troubleshooting.html +4 -4
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1 -0
  97. package/dist/internal/cli-deploy.d.ts +52 -0
  98. package/dist/internal/cli-deploy.d.ts.map +1 -0
  99. package/dist/internal/cli-deploy.js +731 -0
  100. package/dist/internal/cursor/backend-client.d.ts +10 -1
  101. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  102. package/dist/internal/cursor/backend-client.js +79 -1
  103. package/dist/internal/cursor/github-credentials.d.ts +44 -0
  104. package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
  105. package/dist/internal/cursor/github-credentials.js +195 -0
  106. package/dist/internal/deploy-client.d.ts +176 -0
  107. package/dist/internal/deploy-client.d.ts.map +1 -0
  108. package/dist/internal/deploy-client.js +375 -0
  109. package/dist/internal/discovery.d.ts.map +1 -1
  110. package/dist/internal/discovery.js +73 -5
  111. package/dist/internal/distribution.d.ts.map +1 -1
  112. package/dist/internal/distribution.js +1 -0
  113. package/dist/internal/eval-run-store.d.ts +22 -3
  114. package/dist/internal/eval-run-store.d.ts.map +1 -1
  115. package/dist/internal/eval-run-store.js +37 -19
  116. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
  117. package/dist/internal/handleAgentServeTrigger.js +10 -12
  118. package/dist/internal/host-platforms.d.ts +7 -2
  119. package/dist/internal/host-platforms.d.ts.map +1 -1
  120. package/dist/internal/host-platforms.js +15 -10
  121. package/dist/internal/hosting.d.ts +37 -0
  122. package/dist/internal/hosting.d.ts.map +1 -0
  123. package/dist/internal/hosting.js +67 -0
  124. package/dist/internal/reminder-runner.d.ts +7 -0
  125. package/dist/internal/reminder-runner.d.ts.map +1 -1
  126. package/dist/internal/reminder-runner.js +50 -6
  127. package/dist/internal/reminder-store.d.ts +2 -0
  128. package/dist/internal/reminder-store.d.ts.map +1 -1
  129. package/dist/internal/reminder-store.js +18 -0
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +173 -42
  132. package/dist/internal/session-engine.d.ts +49 -0
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +246 -17
  135. package/dist/internal/storage-coordinator.d.ts +139 -0
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -0
  137. package/dist/internal/storage-coordinator.js +499 -0
  138. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  139. package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
  140. package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
  141. package/dist/playground/index.html +2 -2
  142. package/dist/storage.d.ts +204 -0
  143. package/dist/storage.d.ts.map +1 -0
  144. package/dist/storage.js +153 -0
  145. package/dist/types.d.ts +44 -3
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/README.md +3 -2
  148. package/docs/guides/github.md +43 -8
  149. package/docs/guides/slack.md +1 -1
  150. package/docs/quickstart.md +329 -51
  151. package/docs/reference/instructions.md +8 -6
  152. package/docs/scaffolding-agents.md +1 -1
  153. package/docs/storage.md +98 -0
  154. package/package.json +8 -1
  155. package/skills/github/SKILL.md +9 -1
  156. package/src/bin/agent-serve.ts +139 -0
  157. package/src/channels/github/api.ts +42 -23
  158. package/src/channels/github/cursor-account.ts +165 -0
  159. package/src/channels/github/github-channel.ts +66 -6
  160. package/src/channels/github/index.ts +2 -2
  161. package/src/channels/github/types.ts +19 -0
  162. package/src/channels/slack/slack-channel.ts +17 -3
  163. package/src/channels/slack/types.ts +44 -3
  164. package/src/index.ts +13 -0
  165. package/src/internal/cli-deploy.ts +940 -0
  166. package/src/internal/cursor/backend-client.ts +103 -1
  167. package/src/internal/cursor/github-credentials.ts +248 -0
  168. package/src/internal/deploy-client.ts +591 -0
  169. package/src/internal/discovery.ts +88 -1
  170. package/src/internal/distribution.ts +1 -0
  171. package/src/internal/eval-run-store.ts +48 -19
  172. package/src/internal/handleAgentServeTrigger.ts +10 -12
  173. package/src/internal/host-platforms.ts +28 -11
  174. package/src/internal/hosting.ts +77 -0
  175. package/src/internal/reminder-runner.ts +50 -6
  176. package/src/internal/reminder-store.ts +21 -0
  177. package/src/internal/server.ts +213 -27
  178. package/src/internal/session-engine.ts +285 -7
  179. package/src/internal/storage-coordinator.ts +615 -0
  180. package/src/storage.ts +325 -0
  181. package/src/types.ts +40 -2
  182. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
  183. package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
  184. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
  185. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
  186. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
  187. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
  188. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  189. package/dist/playground/assets/index-1K-hG-7p.css +0 -1
  190. /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
  191. /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
  192. /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
  193. /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
  194. /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
  198. /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
  199. /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
@@ -26,8 +26,8 @@ var __rest = (this && this.__rest) || function (s, e) {
26
26
  }
27
27
  return t;
28
28
  };
29
- import { mkdir, rm } from "node:fs/promises";
30
- import { join } from "node:path";
29
+ import { mkdir, rm, writeFile } from "node:fs/promises";
30
+ import { dirname, join } from "node:path";
31
31
  import { mergeABInstructions, resolveABMaxPlaygroundSessions, resolveABSplit, } from "../ab.js";
32
32
  import { slackThreadPermalink } from "../channels/slack/links.js";
33
33
  import { DIRECT_TOOL_CALL_CHANNEL_ID, isSymbolicConnectionTransport, } from "../types.js";
@@ -45,6 +45,7 @@ import { validateToolInput } from "./json-schema.js";
45
45
  import { McpHost } from "./mcp-host.js";
46
46
  import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
47
47
  import { SessionStore } from "./session-store.js";
48
+ import { StorageCoordinator } from "./storage-coordinator.js";
48
49
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
49
50
  import { buildAgentToolsCatalog, materializeWorkspace, writeWorkspaceFiles, } from "./workspace.js";
50
51
  export const HTTP_CHANNEL_ID = "http";
@@ -106,8 +107,10 @@ export class ApprovalInterruptedError extends Error {
106
107
  }
107
108
  export class SessionEngine {
108
109
  constructor(options) {
109
- var _a;
110
- var _b;
110
+ var _a, _b;
111
+ var _c, _d, _e;
112
+ /** In-flight lazy restores, deduped per channel + continuation key. */
113
+ this.restoreInFlight = new Map();
111
114
  this.channelsById = new Map();
112
115
  /** Sessions with a turn being admitted (before the run promise exists). */
113
116
  this.pendingTurns = new Set();
@@ -126,17 +129,29 @@ export class SessionEngine {
126
129
  this.project = options.project;
127
130
  this.stateRoot = options.stateRoot;
128
131
  this.runner = options.runner;
129
- this.logger = (_b = options.logger) !== null && _b !== void 0 ? _b : (() => { });
132
+ this.logger = (_c = options.logger) !== null && _c !== void 0 ? _c : (() => { });
130
133
  this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
131
134
  this.logs = new EventLogStore((sessionId) => this.sessions.eventFilePath(sessionId), (sessionId, error) => this.logger(`[agent-serve] failed to persist events for ${sessionId}: ${describeError(error)}`));
132
- this.abCollector = new ABCollector(options.project.abs, this.logger, (sessionId) => __awaiter(this, void 0, void 0, function* () { return (yield this.logs.get(sessionId)).snapshot(0); }), Object.assign({ projectRoot: options.project.rootDir }, (((_a = options.project.abConfig) === null || _a === void 0 ? void 0 : _a.persistSamples) === undefined
133
- ? {}
134
- : { persistSamples: options.project.abConfig.persistSamples })));
135
+ this.storage =
136
+ options.project.storage === undefined
137
+ ? undefined
138
+ : new StorageCoordinator({
139
+ definition: options.project.storage,
140
+ agentName: options.project.name,
141
+ projectRoot: options.project.rootDir,
142
+ logger: this.logger,
143
+ });
144
+ // Domain-specific hooks win; the defineStorage sink is the fallback.
145
+ const persistSamples = (_d = (_a = options.project.abConfig) === null || _a === void 0 ? void 0 : _a.persistSamples) !== null && _d !== void 0 ? _d : (_b = this.storage) === null || _b === void 0 ? void 0 : _b.asABSamplePersistence();
146
+ this.abCollector = new ABCollector(options.project.abs, this.logger, (sessionId) => __awaiter(this, void 0, void 0, function* () { return (yield this.logs.get(sessionId)).snapshot(0); }), {
147
+ projectRoot: options.project.rootDir,
148
+ persistSamples,
149
+ });
135
150
  // Peer and Cursor-account transports are symbolic until the serve host
136
151
  // resolves them; the host-side MCP registry starts with the concrete
137
152
  // connections and gains the rest via setResolvedConnections.
138
153
  this.mcpHost = new McpHost(options.project.agent.connections.filter((connection) => !isSymbolicConnectionTransport(connection.transport)));
139
- this.platforms = createHostPlatforms();
154
+ this.platforms = (_e = options.platforms) !== null && _e !== void 0 ? _e : createHostPlatforms();
140
155
  for (const channel of options.project.channels) {
141
156
  this.channelsById.set(channel.id, channel.definition);
142
157
  }
@@ -181,11 +196,17 @@ export class SessionEngine {
181
196
  */
182
197
  send(channelId_1, message_1) {
183
198
  return __awaiter(this, arguments, void 0, function* (channelId, message, options = {}) {
184
- var _a, _b, _c;
185
- const auth = (_a = options.auth) !== null && _a !== void 0 ? _a : null;
186
- const existing = options.continuationToken === undefined
199
+ var _a;
200
+ var _b, _c, _d;
201
+ const auth = (_b = options.auth) !== null && _b !== void 0 ? _b : null;
202
+ let existing = options.continuationToken === undefined
187
203
  ? undefined
188
204
  : yield this.sessions.findByContinuation(channelId, options.continuationToken);
205
+ if (existing === undefined && options.continuationToken !== undefined) {
206
+ // Local miss: the session may live only in the storage sink (it
207
+ // fell outside the startup restore window, or restore is lazy-only).
208
+ existing = yield this.restoreSessionByContinuation(channelId, options.continuationToken);
209
+ }
189
210
  if (existing !== undefined) {
190
211
  if (!samePrincipal(existing.auth, auth)) {
191
212
  throw new SessionAuthError(existing.sessionId);
@@ -209,9 +230,9 @@ export class SessionEngine {
209
230
  let record = {
210
231
  sessionId: newSessionId(),
211
232
  channelId,
212
- mode: (_b = options.mode) !== null && _b !== void 0 ? _b : "chat",
233
+ mode: (_c = options.mode) !== null && _c !== void 0 ? _c : "chat",
213
234
  purpose,
214
- continuationKey: (_c = options.continuationToken) !== null && _c !== void 0 ? _c : newContinuationKey(),
235
+ continuationKey: (_d = options.continuationToken) !== null && _d !== void 0 ? _d : newContinuationKey(),
215
236
  auth,
216
237
  title: options.title,
217
238
  channelState,
@@ -224,6 +245,7 @@ export class SessionEngine {
224
245
  updatedAt: now,
225
246
  };
226
247
  yield this.sessions.save(record);
248
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(record);
227
249
  yield this.appendEvent(record.sessionId, {
228
250
  type: "session.started",
229
251
  data: { channelId },
@@ -996,11 +1018,22 @@ export class SessionEngine {
996
1018
  }
997
1019
  queueDispatch(sessionId, event) {
998
1020
  var _a;
999
- const chain = (_a = this.dispatchChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
1021
+ var _b;
1022
+ // Single funnel for both append paths (appendEvent + the held-log emit),
1023
+ // so the storage sink sees every durable event exactly once.
1024
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.event(event);
1025
+ const chain = (_b = this.dispatchChains.get(sessionId)) !== null && _b !== void 0 ? _b : Promise.resolve();
1000
1026
  const next = chain
1001
1027
  .then(() => this.dispatchEvent(sessionId, event))
1002
1028
  .catch((error) => {
1003
1029
  this.logger(`[agent-serve] event dispatch failed for ${sessionId}: ${describeError(error)}`);
1030
+ })
1031
+ .then(() => {
1032
+ var _a;
1033
+ // Turn-end flush happens here — after the boundary event's handlers
1034
+ // and their record updates settled — so the persisted record carries
1035
+ // the turn's final channel state and continuation token.
1036
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.eventDispatched(event);
1004
1037
  });
1005
1038
  this.dispatchChains.set(sessionId, next);
1006
1039
  }
@@ -1127,10 +1160,29 @@ export class SessionEngine {
1127
1160
  */
1128
1161
  abSnapshot(caller, options) {
1129
1162
  return __awaiter(this, void 0, void 0, function* () {
1163
+ var _a;
1164
+ var _b;
1130
1165
  const sessions = yield this.listSessions(caller, options);
1131
1166
  const abConfig = this.project.abConfig;
1132
1167
  const maxPlaygroundSessions = resolveABMaxPlaygroundSessions(abConfig === null || abConfig === void 0 ? void 0 : abConfig.maxPlaygroundSessions);
1133
1168
  const foldedSessions = sessions.slice(0, maxPlaygroundSessions);
1169
+ // Backfill for a replacement host with nothing local to fold (e.g.
1170
+ // lazy-only restore before any session resumed): serve the persisted
1171
+ // aggregate instead of an empty surface. Gated on the host having no
1172
+ // sessions AT ALL — the persisted snapshot spans every principal, so a
1173
+ // caller who merely owns no sessions must not see it. Loaded once per
1174
+ // process (playground polls this route ~every 4s); skip the persist
1175
+ // side effect below — re-saving a restored snapshot would echo stale
1176
+ // data back into the store.
1177
+ if (foldedSessions.length === 0 &&
1178
+ this.storage !== undefined &&
1179
+ (yield this.sessions.list()).length === 0) {
1180
+ (_b = this.restoredAbSnapshot) !== null && _b !== void 0 ? _b : (this.restoredAbSnapshot = this.storage.getLatestAbSnapshot());
1181
+ const restoredSnapshot = yield this.restoredAbSnapshot;
1182
+ if (restoredSnapshot !== undefined) {
1183
+ return restoredSnapshot;
1184
+ }
1185
+ }
1134
1186
  const snapshot = yield buildABSnapshot({
1135
1187
  experiments: this.project.abs,
1136
1188
  agentName: this.project.name,
@@ -1158,6 +1210,10 @@ export class SessionEngine {
1158
1210
  this.logger(`[agent-serve] ab persistSnapshots.save threw: ${describeError(error)}`);
1159
1211
  }
1160
1212
  }
1213
+ else {
1214
+ // defineStorage fallback (throttled inside the coordinator).
1215
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.abSnapshot(snapshot);
1216
+ }
1161
1217
  return snapshot;
1162
1218
  });
1163
1219
  }
@@ -1275,12 +1331,14 @@ export class SessionEngine {
1275
1331
  var _a;
1276
1332
  const chain = (_a = this.updateChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
1277
1333
  const next = chain.then(() => __awaiter(this, void 0, void 0, function* () {
1334
+ var _a;
1278
1335
  const current = yield this.sessions.get(sessionId);
1279
1336
  if (current === undefined) {
1280
1337
  throw new Error(`Session ${sessionId} not found`);
1281
1338
  }
1282
1339
  const updated = Object.assign(Object.assign({}, updater(current)), { updatedAt: new Date().toISOString() });
1283
1340
  yield this.sessions.save(updated);
1341
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(updated);
1284
1342
  return updated;
1285
1343
  }));
1286
1344
  this.updateChains.set(sessionId, next.catch(() => { }));
@@ -1289,6 +1347,167 @@ export class SessionEngine {
1289
1347
  // ==========================================================================
1290
1348
  // Lifecycle
1291
1349
  // ==========================================================================
1350
+ /**
1351
+ * Restore sessions from the authored storage sink. Called once at
1352
+ * serve start, before channels accept traffic: any persisted session
1353
+ * missing from the local `--state-root` is materialized there — events
1354
+ * as `events.ndjson`, record as `session.json` — after which every
1355
+ * existing read path (continuation lookup, event replay/streams, A/B
1356
+ * fold hydration) works unchanged. Local state always wins over the
1357
+ * sink, so restarts on the same host are no-ops.
1358
+ *
1359
+ * Failures are logged and skipped; a broken store (including rows that
1360
+ * violate the SessionRecord shape) never blocks serve start.
1361
+ */
1362
+ restoreStoredSessions() {
1363
+ return __awaiter(this, void 0, void 0, function* () {
1364
+ const storage = this.storage;
1365
+ if (storage === undefined || !storage.canBulkRestore) {
1366
+ return;
1367
+ }
1368
+ const policy = storage.policy.restore;
1369
+ if (policy === "off") {
1370
+ return;
1371
+ }
1372
+ try {
1373
+ yield this.runBulkRestore(storage, policy);
1374
+ }
1375
+ catch (error) {
1376
+ this.logger(`[agent-serve] session restore failed: ${describeError(error)}`);
1377
+ }
1378
+ });
1379
+ }
1380
+ runBulkRestore(storage, policy) {
1381
+ return __awaiter(this, void 0, void 0, function* () {
1382
+ const loaded = yield storage.listSessions();
1383
+ // Guardrails on whatever the sink returned: drop invalid rows and
1384
+ // sessions older than the age window, then restore newest-first under
1385
+ // the count cap. Anything filtered out here stays reachable through
1386
+ // the lazy continuation-lookup path.
1387
+ const cutoff = Date.now() - policy.maxAgeMs;
1388
+ const candidates = loaded
1389
+ .map((record) => ({
1390
+ record,
1391
+ // Numeric timestamp for both the age filter and the sort; a
1392
+ // non-string updatedAt (e.g. a raw DB Date from an untyped sink)
1393
+ // parses to NaN and is dropped instead of crashing serve start.
1394
+ updatedAt: typeof (record === null || record === void 0 ? void 0 : record.updatedAt) === "string"
1395
+ ? Date.parse(record.updatedAt)
1396
+ : Number.NaN,
1397
+ }))
1398
+ .filter(({ record, updatedAt }) => typeof (record === null || record === void 0 ? void 0 : record.sessionId) === "string" &&
1399
+ record.sessionId !== "" &&
1400
+ updatedAt >= cutoff)
1401
+ .sort((a, b) => b.updatedAt - a.updatedAt)
1402
+ .slice(0, policy.maxSessions)
1403
+ .map(({ record }) => record);
1404
+ let restored = 0;
1405
+ let budget = policy.maxTotalBytes;
1406
+ // Sink round trips dominate restore latency, so event streams are
1407
+ // fetched with bounded parallelism; writes stay sequential, newest
1408
+ // first, so the byte budget is enforced deterministically.
1409
+ outer: for (let i = 0; i < candidates.length; i += RESTORE_FETCH_CONCURRENCY) {
1410
+ const chunk = candidates.slice(i, i + RESTORE_FETCH_CONCURRENCY);
1411
+ const fetched = yield Promise.all(chunk.map((record) => __awaiter(this, void 0, void 0, function* () {
1412
+ try {
1413
+ // Local state always wins over the sink.
1414
+ if ((yield this.sessions.get(record.sessionId)) !== undefined) {
1415
+ return undefined;
1416
+ }
1417
+ const events = yield storage.listSessionEvents(record.sessionId);
1418
+ return { record, events };
1419
+ }
1420
+ catch (error) {
1421
+ this.logger(`[agent-serve] failed to restore session ${record.sessionId}: ${describeError(error)}`);
1422
+ return undefined;
1423
+ }
1424
+ })));
1425
+ for (const item of fetched) {
1426
+ if (item === undefined) {
1427
+ continue;
1428
+ }
1429
+ const ndjson = eventsNdjson(item.events);
1430
+ // Budget is checked BEFORE anything is written: one oversized
1431
+ // session cannot blow past maxTotalBytes on disk. Skipped sessions
1432
+ // stay reachable through the lazy path.
1433
+ const bytes = Buffer.byteLength(JSON.stringify(item.record), "utf8") +
1434
+ (ndjson === undefined ? 0 : Buffer.byteLength(ndjson, "utf8"));
1435
+ if (bytes > budget) {
1436
+ this.logger(`[agent-serve] session restore stopped at the ${policy.maxTotalBytes}-byte budget after ${restored} session(s); remaining sessions restore lazily on resume`);
1437
+ break outer;
1438
+ }
1439
+ try {
1440
+ yield this.materializeSession(item.record, ndjson);
1441
+ budget -= bytes;
1442
+ restored += 1;
1443
+ }
1444
+ catch (error) {
1445
+ this.logger(`[agent-serve] failed to restore session ${item.record.sessionId}: ${describeError(error)}`);
1446
+ }
1447
+ }
1448
+ }
1449
+ if (restored > 0) {
1450
+ this.logger(`[agent-serve] restored ${restored} session(s) from storage`);
1451
+ }
1452
+ });
1453
+ }
1454
+ /**
1455
+ * Lazy restore: a continuation lookup missed locally, so ask the sink
1456
+ * for that one session and materialize it before resuming. Disk grows
1457
+ * with sessions that are actually resumed, not with history.
1458
+ *
1459
+ * Concurrent misses for the same key (webhook redeliveries, retries)
1460
+ * share one in-flight restore — a second materialization racing the
1461
+ * first resumed turn could truncate events the turn already appended.
1462
+ * Sink errors propagate and fail the send (see
1463
+ * {@link StorageCoordinator.getSessionByContinuation}).
1464
+ */
1465
+ restoreSessionByContinuation(channelId, continuationKey) {
1466
+ const storage = this.storage;
1467
+ if (storage === undefined) {
1468
+ return Promise.resolve(undefined);
1469
+ }
1470
+ const key = `${channelId}\u0000${continuationKey}`;
1471
+ let pending = this.restoreInFlight.get(key);
1472
+ if (pending === undefined) {
1473
+ pending = this.loadAndMaterializeByContinuation(storage, channelId, continuationKey).finally(() => this.restoreInFlight.delete(key));
1474
+ this.restoreInFlight.set(key, pending);
1475
+ }
1476
+ return pending;
1477
+ }
1478
+ loadAndMaterializeByContinuation(storage, channelId, continuationKey) {
1479
+ return __awaiter(this, void 0, void 0, function* () {
1480
+ const record = yield storage.getSessionByContinuation(channelId, continuationKey);
1481
+ if (record === undefined) {
1482
+ // Definitive miss: the caller starts a fresh session under this token.
1483
+ return undefined;
1484
+ }
1485
+ const local = yield this.sessions.get(record.sessionId);
1486
+ if (local !== undefined) {
1487
+ return local;
1488
+ }
1489
+ const events = yield storage.listSessionEvents(record.sessionId);
1490
+ yield this.materializeSession(record, eventsNdjson(events));
1491
+ this.logger(`[agent-serve] lazily restored session ${record.sessionId} from storage`);
1492
+ return record;
1493
+ });
1494
+ }
1495
+ /**
1496
+ * Write one restored session to the local state root. Events land on
1497
+ * disk before the record: a session becomes visible (and addressable via
1498
+ * its continuation key) only with its full stream in place, and the
1499
+ * lazily opened event log hydrates from this file.
1500
+ */
1501
+ materializeSession(record, eventsNdjson) {
1502
+ return __awaiter(this, void 0, void 0, function* () {
1503
+ if (eventsNdjson !== undefined) {
1504
+ const filePath = this.sessions.eventFilePath(record.sessionId);
1505
+ yield mkdir(dirname(filePath), { recursive: true });
1506
+ yield writeFile(filePath, eventsNdjson, "utf8");
1507
+ }
1508
+ yield this.sessions.save(record);
1509
+ });
1510
+ }
1292
1511
  trackBackground(promise) {
1293
1512
  this.backgroundWork.add(promise);
1294
1513
  void promise
@@ -1300,18 +1519,28 @@ export class SessionEngine {
1300
1519
  /** Drain running turns, dispatches, and pending writes, then dispose. */
1301
1520
  close() {
1302
1521
  return __awaiter(this, void 0, void 0, function* () {
1303
- var _a, _b;
1522
+ var _a, _b, _c;
1304
1523
  this.approvals.cancelAll("deny");
1305
1524
  yield Promise.allSettled([...this.activeTurns.values()]);
1306
1525
  yield Promise.allSettled([...this.backgroundWork]);
1307
1526
  yield Promise.allSettled([...this.dispatchChains.values()]);
1308
1527
  yield Promise.allSettled([...this.updateChains.values()]);
1309
1528
  yield this.logs.flushAll();
1529
+ yield (_a = this.storage) === null || _a === void 0 ? void 0 : _a.close();
1310
1530
  yield this.mcpHost.close();
1311
- yield (_b = (_a = this.runner).dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
1531
+ yield (_c = (_b = this.runner).dispose) === null || _c === void 0 ? void 0 : _c.call(_b);
1312
1532
  });
1313
1533
  }
1314
1534
  }
1535
+ /** Parallel event-stream loads during the startup bulk restore. */
1536
+ const RESTORE_FETCH_CONCURRENCY = 8;
1537
+ /** NDJSON payload for a restored event stream; undefined when empty. */
1538
+ function eventsNdjson(events) {
1539
+ if (events.length === 0) {
1540
+ return undefined;
1541
+ }
1542
+ return `${events.map((event) => JSON.stringify(event)).join("\n")}\n`;
1543
+ }
1315
1544
  function subagentPrompt(subagent) {
1316
1545
  var _a;
1317
1546
  if (subagent.instructions !== undefined && subagent.instructions !== "") {
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Runtime for `defineStorage` (`agent/storage.ts`).
3
+ *
4
+ * The coordinator is the single funnel between the engine's hot paths and
5
+ * the author's key-value sink. The sink is four functions (`put` / `get` /
6
+ * `delete` / `list`); the coordinator owns everything else: it mints every
7
+ * key from the versioned scheme ({@link storageKeys}), coalesces
8
+ * session records and batches event chunks per turn (or debounce window),
9
+ * serializes delivery on one bounded queue per agent, and isolates
10
+ * failures — a throwing sink is logged and its write dropped; storage
11
+ * must never stall or fail a turn.
12
+ *
13
+ * Scaling shape: sessions, reminders, evals, and A/Bs all reduce to keyed
14
+ * puts on the same queue, so one author-owned sink covers every domain,
15
+ * and new domains are new key prefixes — not new config surface.
16
+ */
17
+ import type { ABSamplePersistence } from "../ab.js";
18
+ import type { EvalRunPersistence } from "../evals.js";
19
+ import { type ResolvedStoragePolicy, type StorageContext, type StorageDefinition } from "../storage.js";
20
+ import type { SessionEvent, SessionRecord } from "../types.js";
21
+ import type { ABSnapshot } from "./ab-snapshot.js";
22
+ import { type ReminderRecord } from "./reminder-store.js";
23
+ export interface StorageCoordinatorOptions {
24
+ definition: StorageDefinition;
25
+ agentName: string;
26
+ projectRoot: string;
27
+ logger: (line: string) => void;
28
+ }
29
+ export declare class StorageCoordinator {
30
+ readonly policy: ResolvedStoragePolicy;
31
+ private readonly definition;
32
+ private readonly agentName;
33
+ private readonly projectRoot;
34
+ private readonly logger;
35
+ /** Per-session pending state (events + coalesced record). */
36
+ private readonly buffers;
37
+ /**
38
+ * Last continuation key written to the sink per session, so a token
39
+ * change emits a delete for the stale index entry alongside the new put.
40
+ */
41
+ private readonly continuationIndex;
42
+ /** Single delivery chain: writes reach the sink serialized, in order. */
43
+ private queue;
44
+ /** Ops on the queue not yet delivered (bounded by MAX_PENDING_OPS). */
45
+ private pending;
46
+ /** Ops dropped because the queue was full. */
47
+ private dropped;
48
+ private lastAbSnapshotAt;
49
+ private closed;
50
+ constructor(options: StorageCoordinatorOptions);
51
+ /** Durable session-record update (engine calls after every store save). */
52
+ sessionRecord(record: SessionRecord): void;
53
+ /** One appended session event (engine calls from its dispatch funnel). */
54
+ event(event: SessionEvent): void;
55
+ /**
56
+ * A boundary event's dispatch settled: channel/hook handlers ran and
57
+ * their record updates (channel state, continuation tokens) are
58
+ * buffered. This — not the append — is the turn-end flush point, so the
59
+ * persisted session record includes the boundary event's own handler
60
+ * mutations. Debounced batching intentionally ignores boundaries.
61
+ */
62
+ eventDispatched(event: SessionEvent): void;
63
+ /** Flush everything buffered for one session (ordered: events, record). */
64
+ flushSession(sessionId: string, reason?: StorageContext["reason"]): void;
65
+ /**
66
+ * Index maintenance for `continuation/{channelId}/{token}` → sessionId:
67
+ * put the current token, delete the previous one when it changed.
68
+ */
69
+ private continuationOps;
70
+ /** Whether the sink can serve the startup bulk restore (`list`). */
71
+ get canBulkRestore(): boolean;
72
+ /**
73
+ * Saved session records for this agent (startup restore). Errors are
74
+ * logged and read as "nothing saved" — a broken store must never block
75
+ * serve start.
76
+ */
77
+ listSessions(): Promise<SessionRecord[]>;
78
+ /**
79
+ * Saved event stream for one session: chunk values concatenated in key
80
+ * order (chunks are keyed by their first event's index, zero-padded, so
81
+ * ascending key order is append order). Errors read as an empty stream.
82
+ */
83
+ listSessionEvents(sessionId: string): Promise<SessionEvent[]>;
84
+ /**
85
+ * One saved session by channel continuation key (lazy restore). Unlike
86
+ * the startup reads, sink errors **propagate**: a failing store must
87
+ * fail the follow-up (which the caller can retry) rather than read as
88
+ * "unknown token" — that would mint a new session under the same
89
+ * continuation key and permanently shadow the real one on this host.
90
+ * A definitive miss (no index entry, or a write-only sink without
91
+ * `get`) resolves undefined.
92
+ */
93
+ getSessionByContinuation(channelId: string, continuationKey: string): Promise<SessionRecord | undefined>;
94
+ /**
95
+ * Mirror one reminder record (create / fire / cancel / disarm). Local
96
+ * disk under `--state-root/reminders` remains the live source of truth;
97
+ * this is the durable copy for host replacement.
98
+ */
99
+ reminder(record: ReminderRecord): void;
100
+ /**
101
+ * Saved reminder records for this agent (startup hydrate into local
102
+ * disk). Errors read as empty — same posture as session bulk restore.
103
+ */
104
+ listReminders(): Promise<ReminderRecord[]>;
105
+ /**
106
+ * Adapter for {@link EvalRunPersistence} so the playground eval store
107
+ * can fall back to this sink when `evals.config.ts` sets no
108
+ * `persistRuns`.
109
+ */
110
+ asEvalRunPersistence(): EvalRunPersistence;
111
+ /**
112
+ * Adapter for {@link ABSamplePersistence} so the AB collector can fall
113
+ * back to this sink when `ab.config.ts` sets no `persistSamples`.
114
+ */
115
+ asABSamplePersistence(): ABSamplePersistence;
116
+ /**
117
+ * Refresh the persisted aggregate A/B snapshot, throttled to once per
118
+ * {@link AB_SNAPSHOT_MIN_INTERVAL_MS} (the playground recomputes the
119
+ * fold on every `GET /v1/abs` poll).
120
+ */
121
+ abSnapshot(snapshot: ABSnapshot): void;
122
+ /** Latest persisted aggregate A/B snapshot; errors read as absent. */
123
+ getLatestAbSnapshot(): Promise<ABSnapshot | undefined>;
124
+ /**
125
+ * Flush all buffers and drain the queue, giving the sink at most
126
+ * {@link CLOSE_DRAIN_TIMEOUT_MS} — a hung sink must not stall shutdown
127
+ * past the pod grace period. Called from engine close.
128
+ */
129
+ close(): Promise<void>;
130
+ /** Pending deliveries (exposed for tests and drain instrumentation). */
131
+ whenIdle(): Promise<void>;
132
+ private buffer;
133
+ private armDebounce;
134
+ /** `list` wrapper for the startup reads: missing hook or throw ⇒ empty. */
135
+ private tryList;
136
+ private enqueue;
137
+ private context;
138
+ }
139
+ //# sourceMappingURL=storage-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-coordinator.d.ts","sourceRoot":"","sources":["../../src/internal/storage-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAkB,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,aAAa,CAAC;AACvE,OAAO,EACL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEvB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAa,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAwC5E,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AASD,qBAAa,kBAAkB;IAC7B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IAEvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoC;IACtE,yEAAyE;IACzE,OAAO,CAAC,KAAK,CAAoC;IACjD,uEAAuE;IACvE,OAAO,CAAC,OAAO,CAAK;IACpB,8CAA8C;IAC9C,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,OAAO,EAAE,yBAAyB,EAM7C;IAMD,2EAA2E;IAC3E,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAMzC;IAED,0EAA0E;IAC1E,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAoB/B;IAED;;;;;;OAMG;IACH,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CASzC;IAED,2EAA2E;IAC3E,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,cAAc,CAAC,QAAQ,CAAY,GAC1C,IAAI,CA6BN;IAED;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgCvB,oEAAoE;IACpE,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAO7C;IAED;;;;OAIG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAWlE;IAED;;;;;;;;OAQG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgDpC;IAMD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAcrC;IAED;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAO/C;IAMD;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAkCzC;IAMD;;;OAGG;IACH,qBAAqB,IAAI,mBAAmB,CAmB3C;IAED;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAmBrC;IAED,sEAAsE;IAChE,mBAAmB,IAAI,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAiB3D;IAMD;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB3B;IAED,wEAAwE;IACxE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAExB;IAED,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,WAAW;IAgBnB,2EAA2E;YAC7D,OAAO;IAkBrB,OAAO,CAAC,OAAO;IAkCf,OAAO,CAAC,OAAO;CAGhB"}