@agentshouse/mdruntime-adapter 0.1.0-cca5d676bc88e → 0.1.0-ccd2ccb1f7214

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