@agentshouse/mdruntime-adapter 0.1.0-cd028205a5103 → 0.1.0-cd23c45caa798

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 (53) hide show
  1. package/AGENTS.md +285 -225
  2. package/dist/actions.d.ts +68 -1
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +196 -193
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +384 -103
  8. package/dist/discovery.d.ts +7 -4
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +46 -13
  11. package/dist/gather.d.ts +2 -1
  12. package/dist/gather.d.ts.map +1 -1
  13. package/dist/gather.js +15 -20
  14. package/dist/handed.d.ts +15 -0
  15. package/dist/handed.d.ts.map +1 -0
  16. package/dist/handed.js +45 -0
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/model.d.ts +1 -1
  21. package/dist/model.d.ts.map +1 -1
  22. package/dist/model.js +6 -2
  23. package/dist/part.d.ts +13 -0
  24. package/dist/part.d.ts.map +1 -0
  25. package/dist/part.js +80 -0
  26. package/dist/read.d.ts +53 -16
  27. package/dist/read.d.ts.map +1 -1
  28. package/dist/read.js +265 -147
  29. package/dist/referrer.d.ts +7 -0
  30. package/dist/referrer.d.ts.map +1 -0
  31. package/dist/referrer.js +33 -0
  32. package/dist/refusal.d.ts +2 -5
  33. package/dist/refusal.d.ts.map +1 -1
  34. package/dist/refusal.js +6 -9
  35. package/dist/site.d.ts +1 -1
  36. package/dist/site.d.ts.map +1 -1
  37. package/dist/site.js +175 -58
  38. package/dist/stream.d.ts +4 -4
  39. package/dist/stream.d.ts.map +1 -1
  40. package/dist/stream.js +340 -42
  41. package/dist/transport.d.ts +64 -10
  42. package/dist/transport.d.ts.map +1 -1
  43. package/dist/transport.js +3 -0
  44. package/dist/version.d.ts +1 -0
  45. package/dist/version.d.ts.map +1 -1
  46. package/dist/version.js +1 -1
  47. package/dist/write.d.ts +14 -9
  48. package/dist/write.d.ts.map +1 -1
  49. package/dist/write.js +156 -37
  50. package/package.json +3 -4
  51. package/dist/links.d.ts +0 -20
  52. package/dist/links.d.ts.map +0 -1
  53. package/dist/links.js +0 -104
package/dist/site.js CHANGED
@@ -1,79 +1,134 @@
1
- import { actionOutcome } from './actions.js';
1
+ import { stepped } from '@agentshouse/mdruntime';
2
+ import { actionOutcome, SITE_SUBMIT } from './actions.js';
2
3
  import { mapDiscovery, parseSiteDiscovery, siteCapabilities, siteTracked, } from './discovery.js';
3
- import { scopeModels, streamedModels, viewModels } from './model.js';
4
- import { advancedScope, bootstrapOf, parseEnumeration, parseSiteBootstrap, refreshFrom, viewOf, } from './read.js';
4
+ import { partModels, streamedModels, viewModels } from './model.js';
5
+ import { advancedScope, bootstrapping, emptyScope, housePart, parseEnumeration, parseSiteParted, refreshFrom, viewOf, wholeView, } from './read.js';
6
+ import { PART_ATTEMPTS, unverifiedPart } from './part.js';
5
7
  import { classifyDoor, classifyRead, discoveryFailure } from './refusal.js';
6
8
  import { houseNotifications } from './stream.js';
7
- import { SITE_DOOR_BOOTSTRAP, SITE_DOOR_DISCOVER, SITE_DOOR_ENUMERATE, SITE_DOOR_SUBMIT, } from './transport.js';
9
+ import { SITE_DOOR_BOOTSTRAP, SITE_DOOR_DISCOVER, SITE_DOOR_ENUMERATE, SITE_DOOR_SUBMIT, SITE_FILE_RECORD, SITE_FILES, } from './transport.js';
10
+ import { houseReferrer } from './referrer.js';
8
11
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
9
12
  const UNCERTAIN = { kind: 'transport-uncertainty' };
13
+ function unfound() {
14
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
15
+ }
10
16
  function siteRoom(tracked) {
11
17
  return {
12
18
  name: tracked.handle,
13
19
  locator: tracked.locator,
14
20
  handle: tracked.handle,
15
21
  roomVersion: roomVersion(tracked),
16
- operations: { read: [''], sync: [''], write: [], delete: [] },
17
- replica: { selection: tracked.locator, admission: 'admitted' },
22
+ operations: { write: [], delete: [] },
23
+ replica: { selection: tracked.locator, admission: 'admitted', offline: 'open' },
18
24
  providerOwnedPaths: [],
19
25
  };
20
26
  }
21
27
  function settled(refused) {
22
28
  return refused.kind === 'beyond-horizon' ? UNCERTAIN : refused;
23
29
  }
24
- export function createSiteAdapter(transport) {
30
+ export function createSiteAdapter(transport, snapshot = false) {
25
31
  const notifications = houseNotifications(transport, 'site');
26
32
  const tracked = new Map();
27
- async function enumeration(entry, from) {
33
+ const expired = new Map();
34
+ function published(referrer) {
35
+ const named = houseReferrer(referrer, tracked);
36
+ return named === null || !tracked.has(named.locator) ? null : named;
37
+ }
38
+ function fileRoute(request) {
39
+ const named = published(request.kind === 'path' ? { room: request.locator, path: request.path } : request.referrer);
40
+ if (named === null) {
41
+ return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
42
+ }
43
+ if (request.fileRef === undefined)
44
+ return unfound();
45
+ const { room_ref, path, record } = named.referrer;
46
+ return `${SITE_FILES}${room_ref}/${encodeURIComponent(request.fileRef)}/${path === undefined
47
+ ? `${SITE_FILE_RECORD}/${encodeURIComponent(record)}`
48
+ : encodeURIComponent(path)}`;
49
+ }
50
+ async function enumerated(entry, from, page) {
28
51
  const answer = await transport.door(SITE_DOOR_ENUMERATE, {
29
- positions: [presented(entry.authority, from)],
52
+ positions: [
53
+ page === undefined
54
+ ? presented(entry.authority, from)
55
+ : {
56
+ ...presented(entry.authority, { position: page.after, logEpoch: from.logEpoch }),
57
+ page: { through: page.through, read: page.read },
58
+ },
59
+ ],
30
60
  });
31
61
  const classified = classifyRead(answer.status, answer.body, entry.locator);
32
62
  if (classified !== null)
33
63
  return classified;
34
- return parseEnumeration(answer.body, 'site') ?? UNCERTAIN;
64
+ const found = parseEnumeration(answer.body, 'site');
65
+ if (found === null)
66
+ return UNCERTAIN;
67
+ return viewOf(found, entry) ?? { kind: 'room-not-readable', locator: entry.locator };
35
68
  }
36
- async function caughtUp(entry) {
37
- const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}`);
69
+ async function enumeration(entry, from) {
70
+ const first = await enumerated(entry, from);
71
+ return 'kind' in first ? first : wholeView(first, (page) => enumerated(entry, from, page));
72
+ }
73
+ async function partedRoom(entry) {
74
+ const cut = expired.get(entry.locator);
75
+ const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}${cut === undefined ? '' : `?expired=${cut}`}`);
38
76
  const classified = classifyDoor(answer.status, answer.body, entry.locator);
39
77
  if (classified !== null)
40
- return { failure: classified };
41
- const scope = parseSiteBootstrap(answer.body);
42
- if (scope === null)
43
- return { failure: UNCERTAIN };
44
- if (roomVersion(scope) === roomVersion(entry))
45
- return { scope };
46
- const found = await enumeration(entry, scope);
47
- if ('kind' in found)
48
- return { failure: settled(found) };
49
- const view = viewOf(found, entry);
50
- if (view === undefined)
51
- return { failure: { kind: 'room-not-readable', locator: entry.locator } };
52
- return { scope: advancedScope(scope, view) };
78
+ return classified;
79
+ const room = parseSiteParted(answer.body);
80
+ return room === null || room.anchor !== entry.authority ? UNCERTAIN : room;
81
+ }
82
+ async function fetchedPart(entry, room, listed) {
83
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
84
+ const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}?part=${encodeURIComponent(listed.part)}`);
85
+ const failure = classifyRead(answer.status, answer.body, entry.locator);
86
+ if (failure?.kind === 'beyond-horizon')
87
+ expired.set(entry.locator, room.position);
88
+ if (failure !== null)
89
+ return failure;
90
+ const scope = await housePart(room, answer.body, listed, 'site');
91
+ if (scope !== null)
92
+ return scope;
93
+ }
94
+ return unverifiedPart(entry.locator);
95
+ }
96
+ async function caughtUp(entry, continued) {
97
+ const listed = continued.room.parts[continued.next];
98
+ const scope = listed === undefined
99
+ ? emptyScope(continued.room)
100
+ : await fetchedPart(entry, continued.room, listed);
101
+ if ('kind' in scope)
102
+ return scope;
103
+ if (continued.room.parts.length > 1 || roomVersion(scope) === roomVersion(entry))
104
+ return scope;
105
+ const view = await enumeration(entry, scope);
106
+ if ('kind' in view)
107
+ return settled(view);
108
+ return stepped(advancedScope(scope, view));
53
109
  }
54
110
  async function refreshed(entry, fromRoomVersion) {
55
111
  const from = logPosition(fromRoomVersion);
56
112
  if (from === null)
57
113
  return { kind: 'beyond-horizon' };
58
- const streamed = notifications.advance(entry, from);
114
+ const streamed = await stepped(notifications.advance(entry, from));
59
115
  if (streamed !== null) {
60
116
  const models = await streamedModels(streamed.schemas, entry, 'site');
61
117
  return models === undefined ? streamed.page : { ...streamed.page, models };
62
118
  }
63
- const found = await enumeration(entry, from);
64
- if ('kind' in found)
65
- return found;
66
- const view = viewOf(found, entry);
67
- if (view === undefined)
68
- return { kind: 'room-not-readable', locator: entry.locator };
119
+ const view = await enumeration(entry, from);
120
+ if ('kind' in view)
121
+ return view;
69
122
  notifications.reach(entry.authority, view);
70
- const answer = refreshFrom(view, fromRoomVersion);
123
+ const answer = await refreshFrom(view, fromRoomVersion);
71
124
  if (answer.kind !== 'net-advancement')
72
125
  return answer;
73
126
  const models = await viewModels(view, entry);
74
127
  return models === undefined ? answer : { ...answer, models };
75
128
  }
76
129
  return {
130
+ localSaves: true,
131
+ exhaustiveDiscovery: true,
77
132
  async authorize() {
78
133
  return {};
79
134
  },
@@ -96,14 +151,31 @@ export function createSiteAdapter(transport) {
96
151
  const entry = tracked.get(request.locator);
97
152
  if (entry === undefined)
98
153
  return { kind: 'room-not-readable', locator: request.locator };
99
- if (request.pageToken !== undefined)
100
- return UNCERTAIN;
101
- const held = await caughtUp(entry);
102
- if ('failure' in held)
103
- return held.failure;
104
- notifications.reach(entry.authority, held.scope);
105
- const models = await scopeModels(held.scope, entry);
106
- return { ...bootstrapOf(held.scope), ...(models === undefined ? {} : { models }) };
154
+ let continued;
155
+ if (request.pageToken === undefined) {
156
+ const room = await partedRoom(entry);
157
+ if ('kind' in room)
158
+ return room;
159
+ continued = { room, next: 0, seen: [] };
160
+ }
161
+ else
162
+ continued = JSON.parse(request.pageToken);
163
+ const scope = await caughtUp(entry, continued);
164
+ if ('kind' in scope)
165
+ return scope;
166
+ notifications.reach(entry.authority, scope);
167
+ const next = continued.next + 1;
168
+ const more = next < continued.room.parts.length;
169
+ const seen = [
170
+ ...continued.seen,
171
+ ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
172
+ ];
173
+ const models = await partModels(scope, entry, new Set(seen), !more);
174
+ return {
175
+ ...(await stepped(bootstrapping(scope))),
176
+ ...(models === undefined ? {} : { models }),
177
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
178
+ };
107
179
  },
108
180
  async refresh(_authorization, request) {
109
181
  const entry = tracked.get(request.locator);
@@ -111,14 +183,47 @@ export function createSiteAdapter(transport) {
111
183
  return { kind: 'room-not-readable', locator: request.locator };
112
184
  if (request.pageToken !== undefined)
113
185
  return UNCERTAIN;
186
+ if (snapshot) {
187
+ return request.fromRoomVersion === roomVersion(entry)
188
+ ? { kind: 'still-current' }
189
+ : { kind: 'beyond-horizon' };
190
+ }
114
191
  return refreshed(entry, request.fromRoomVersion);
115
192
  },
116
193
  async mutate() {
117
- return { kind: 'rejected', affected: {}, code: 'write-grant', fields: {} };
194
+ return { kind: 'rejected', affected: {}, code: 'write-grant', fields: {}, retryable: false };
118
195
  },
119
196
  async history() {
120
197
  return { kind: 'no-history' };
121
198
  },
199
+ async binary(_authorization, request) {
200
+ const route = fileRoute(request);
201
+ if (typeof route !== 'string')
202
+ return route;
203
+ const read = await transport.download(route);
204
+ if (read.status === 404)
205
+ return unfound();
206
+ return read.status === 200 && read.blob !== null
207
+ ? { kind: 'binary', blob: read.blob }
208
+ : UNCERTAIN;
209
+ },
210
+ async link(_authorization, request) {
211
+ const route = fileRoute(request);
212
+ if (typeof route !== 'string')
213
+ return route;
214
+ return {
215
+ kind: 'link',
216
+ url: route,
217
+ name: request.path.slice(request.path.lastIndexOf('/') + 1),
218
+ };
219
+ },
220
+ referrerScope(referrer) {
221
+ const named = published(referrer);
222
+ if (named === null)
223
+ return null;
224
+ const { path } = named.referrer;
225
+ return { locator: named.locator, ...(path === undefined ? {} : { path }) };
226
+ },
122
227
  reached(locator, version, target) {
123
228
  return reachedVersion(version, target, tracked.get(locator)?.logEpoch);
124
229
  },
@@ -126,27 +231,39 @@ export function createSiteAdapter(transport) {
126
231
  return followsVersion(version, next);
127
232
  },
128
233
  async action(_authorization, request) {
129
- if (request.name !== 'submit') {
130
- return { kind: 'rejected', code: 'action-unimplemented', fields: { name: request.name } };
234
+ if (request.name !== SITE_SUBMIT) {
235
+ return {
236
+ kind: 'rejected',
237
+ code: 'action-unimplemented',
238
+ fields: { name: request.name },
239
+ retryable: false,
240
+ };
131
241
  }
132
242
  if (!transport.online())
133
243
  return UNCERTAIN;
134
- return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input), 'submit');
244
+ return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input), SITE_SUBMIT);
135
245
  },
136
- roomStream: {
137
- hold(rooms) {
138
- notifications.send(rooms.flatMap((room) => {
139
- const entry = tracked.get(room.locator);
140
- const at = logPosition(room.roomVersion);
141
- if (entry === undefined || at === null)
142
- return [];
143
- notifications.reach(entry.authority, at);
144
- return [presented(entry.authority, at)];
145
- }));
246
+ roomStream: snapshot
247
+ ? undefined
248
+ : {
249
+ hold(rooms) {
250
+ notifications.send(rooms.flatMap((room) => {
251
+ const entry = tracked.get(room.locator);
252
+ const at = logPosition(room.roomVersion);
253
+ if (entry === undefined || at === null)
254
+ return [];
255
+ notifications.reach(entry.authority, at);
256
+ return [presented(entry.authority, at)];
257
+ }));
258
+ },
259
+ reopen: notifications.reopen,
260
+ observe: notifications.observe,
261
+ published(locator, roomVersion) {
262
+ const at = logPosition(roomVersion);
263
+ if (at !== null)
264
+ notifications.published(locator, at);
265
+ },
146
266
  },
147
- reopen: notifications.reopen,
148
- observe: notifications.observe,
149
- },
150
267
  serviceMode: notifications.serviceMode,
151
268
  };
152
269
  }
package/dist/stream.d.ts CHANGED
@@ -1,21 +1,21 @@
1
- import type { ConversationStream, RefreshPage, RoomStreamEvent, ServiceModeSignal } from '@agentshouse/mdruntime';
1
+ import { type ConversationStream, type RefreshPage, type RoomStreamEvent, type ServiceModeSignal, type StreamReceipts } from '@agentshouse/mdruntime';
2
2
  import type { TrackedAuthority } from './discovery.js';
3
3
  import type { HouseDoor, HouseHeldRoom, HouseMode, HouseStreamEntry } from './transport.js';
4
4
  import { type LogPosition } from './version.js';
5
5
  export type HeldRevision = (path: string, revision: string) => boolean;
6
6
  export type StreamedAdvancement = {
7
7
  page: RefreshPage;
8
- entries: readonly HouseStreamEntry[];
9
8
  schemas: ReadonlyMap<string, HouseStreamEntry>;
10
9
  };
11
10
  export type HouseNotifications = {
12
11
  runtime: string;
13
- observe(notify: (event: RoomStreamEvent) => void): () => void;
12
+ observe(notify: (event: RoomStreamEvent) => void, receipts: StreamReceipts): () => void;
14
13
  send(rooms: readonly HouseHeldRoom[]): void;
14
+ published(locator: string, at: LogPosition): void;
15
15
  reopen(): void;
16
16
  track(authorities: readonly TrackedAuthority[]): void;
17
17
  reach(authority: string, at: LogPosition): void;
18
- advance(tracked: TrackedAuthority, from: LogPosition, holds?: HeldRevision): StreamedAdvancement | null;
18
+ advance(tracked: TrackedAuthority, from: LogPosition, holds?: HeldRevision): Generator<number, StreamedAdvancement | null, void>;
19
19
  conversations: ConversationStream;
20
20
  serviceMode: ServiceModeSignal;
21
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAElB,WAAW,EACX,eAAe,EAEf,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAEb,SAAS,EAET,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAgB7D,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACrC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC9D,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5C,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,OAAO,CACL,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,EACjB,KAAK,CAAC,EAAE,YAAY,GACnB,mBAAmB,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,kBAAkB,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAuBF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,kBAAkB,CAyS5F"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EAEvB,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAEb,SAAS,EAET,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AA0C7D,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IACxF,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAClD,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,OAAO,CACL,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,EACjB,KAAK,CAAC,EAAE,YAAY,GACnB,SAAS,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;IACvD,aAAa,EAAE,kBAAkB,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAmFF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,kBAAkB,CA0iB5F"}