@adonis-agora/agent 0.45.0 → 0.47.0

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 (95) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/assets/spa/assets/{index-7XqfoeQH.js → index-Be6OClIm.js} +1 -1
  3. package/dist/assets/spa/index.html +1 -1
  4. package/dist/providers/agent_provider.d.ts +1 -1
  5. package/dist/providers/agent_provider.d.ts.map +1 -1
  6. package/dist/providers/agent_provider.js +112 -33
  7. package/dist/providers/agent_provider.js.map +1 -1
  8. package/dist/src/agent-service.d.ts +27 -8
  9. package/dist/src/agent-service.d.ts.map +1 -1
  10. package/dist/src/agent-service.js +66 -7
  11. package/dist/src/agent-service.js.map +1 -1
  12. package/dist/src/ai-sdk/ai-sdk-model.d.ts +30 -13
  13. package/dist/src/ai-sdk/ai-sdk-model.d.ts.map +1 -1
  14. package/dist/src/ai-sdk/ai-sdk-model.js +160 -89
  15. package/dist/src/ai-sdk/ai-sdk-model.js.map +1 -1
  16. package/dist/src/ai-sdk/index.d.ts +1 -1
  17. package/dist/src/ai-sdk/index.d.ts.map +1 -1
  18. package/dist/src/ai-sdk/index.js +1 -1
  19. package/dist/src/ai-sdk/index.js.map +1 -1
  20. package/dist/src/ai-tool-ref.d.ts +23 -8
  21. package/dist/src/ai-tool-ref.d.ts.map +1 -1
  22. package/dist/src/ai-tool-ref.js +21 -4
  23. package/dist/src/ai-tool-ref.js.map +1 -1
  24. package/dist/src/anonymous-actor-resolver.d.ts +38 -0
  25. package/dist/src/anonymous-actor-resolver.d.ts.map +1 -0
  26. package/dist/src/anonymous-actor-resolver.js +76 -0
  27. package/dist/src/anonymous-actor-resolver.js.map +1 -0
  28. package/dist/src/authorizer.d.ts +4 -3
  29. package/dist/src/authorizer.d.ts.map +1 -1
  30. package/dist/src/authorizer.js +5 -4
  31. package/dist/src/authorizer.js.map +1 -1
  32. package/dist/src/define_config.d.ts +31 -32
  33. package/dist/src/define_config.d.ts.map +1 -1
  34. package/dist/src/define_config.js +2 -2
  35. package/dist/src/define_config.js.map +1 -1
  36. package/dist/src/durable/agent-run-workflow.d.ts.map +1 -1
  37. package/dist/src/durable/agent-run-workflow.js +8 -1
  38. package/dist/src/durable/agent-run-workflow.js.map +1 -1
  39. package/dist/src/durable/durable-agent-runner.d.ts.map +1 -1
  40. package/dist/src/durable/durable-agent-runner.js +3 -0
  41. package/dist/src/durable/durable-agent-runner.js.map +1 -1
  42. package/dist/src/index.d.ts +6 -6
  43. package/dist/src/index.d.ts.map +1 -1
  44. package/dist/src/index.js +5 -5
  45. package/dist/src/index.js.map +1 -1
  46. package/dist/src/media/index.d.ts +7 -0
  47. package/dist/src/media/index.d.ts.map +1 -0
  48. package/dist/src/media/index.js +7 -0
  49. package/dist/src/media/index.js.map +1 -0
  50. package/dist/src/media/media-attachment-staging.d.ts +115 -0
  51. package/dist/src/media/media-attachment-staging.d.ts.map +1 -0
  52. package/dist/src/media/media-attachment-staging.js +145 -0
  53. package/dist/src/media/media-attachment-staging.js.map +1 -0
  54. package/dist/src/runners/inline-agent-runner.d.ts +3 -1
  55. package/dist/src/runners/inline-agent-runner.d.ts.map +1 -1
  56. package/dist/src/runners/inline-agent-runner.js +17 -1
  57. package/dist/src/runners/inline-agent-runner.js.map +1 -1
  58. package/dist/src/spi/agent-store.d.ts +8 -0
  59. package/dist/src/spi/agent-store.d.ts.map +1 -1
  60. package/dist/src/spi/agent-store.js +8 -1
  61. package/dist/src/spi/agent-store.js.map +1 -1
  62. package/dist/src/spi/attachment-staging.d.ts +30 -12
  63. package/dist/src/spi/attachment-staging.d.ts.map +1 -1
  64. package/dist/src/stores/factory.d.ts +18 -65
  65. package/dist/src/stores/factory.d.ts.map +1 -1
  66. package/dist/src/stores/factory.js +24 -56
  67. package/dist/src/stores/factory.js.map +1 -1
  68. package/dist/src/stores/lucid.d.ts +1 -0
  69. package/dist/src/stores/lucid.d.ts.map +1 -1
  70. package/dist/src/stores/lucid.js +9 -2
  71. package/dist/src/stores/lucid.js.map +1 -1
  72. package/dist/src/testing/in-memory-attachment-staging.d.ts +7 -1
  73. package/dist/src/testing/in-memory-attachment-staging.d.ts.map +1 -1
  74. package/dist/src/testing/in-memory-attachment-staging.js +22 -2
  75. package/dist/src/testing/in-memory-attachment-staging.js.map +1 -1
  76. package/dist/src/testing/in-memory-store.d.ts +1 -0
  77. package/dist/src/testing/in-memory-store.d.ts.map +1 -1
  78. package/dist/src/testing/in-memory-store.js +7 -1
  79. package/dist/src/testing/in-memory-store.js.map +1 -1
  80. package/dist/src/tool-registry.d.ts +5 -1
  81. package/dist/src/tool-registry.d.ts.map +1 -1
  82. package/dist/src/tool-registry.js +8 -2
  83. package/dist/src/tool-registry.js.map +1 -1
  84. package/dist/src/types.d.ts +43 -8
  85. package/dist/src/types.d.ts.map +1 -1
  86. package/dist/stubs/config/agent.stub +12 -13
  87. package/package.json +10 -1
  88. package/skills/agent-governance/SKILL.md +4 -3
  89. package/skills/agent-personas-agents/SKILL.md +3 -3
  90. package/skills/agent-setup/SKILL.md +39 -69
  91. package/skills/agent-tools/SKILL.md +11 -10
  92. package/dist/src/stores/ledger-quota.d.ts +0 -23
  93. package/dist/src/stores/ledger-quota.d.ts.map +0 -1
  94. package/dist/src/stores/ledger-quota.js +0 -32
  95. package/dist/src/stores/ledger-quota.js.map +0 -1
@@ -3,7 +3,7 @@ name: agent-setup
3
3
  description: >-
4
4
  Set up @adonis-agora/agent in an AdonisJS app: node ace configure @adonis-agora/agent,
5
5
  defineConfig in config/agent.ts (model via aiSdkModel from @adonis-agora/agent/ai-sdk,
6
- stores.lucid()/stores.memory(), quotas.ledger/quotas.memory, pricingStores +
6
+ stores.lucid()/stores.memory(), quota: { limits }, pricingStores +
7
7
  seedModelPrices + estimateCost, tokenSinks.redis multi-replica SSE sink,
8
8
  AuthActorResolver identity seam), auto-created agent tables vs the published migration,
9
9
  the cost fold (null vs $0.00), and route mounting under config path. Use for "set up
@@ -63,7 +63,8 @@ export default defineConfig({
63
63
  lucid: stores.lucid(), // persists to SQL via @adonisjs/lucid
64
64
  },
65
65
 
66
- // Identity seam — fail-closed. Without a resolver every request throws → 401.
66
+ // Identity. Omitted → endpoints PUBLIC, one anonymous actor per browser (HttpOnly cookie).
67
+ // This line requires login instead (ctx.auth.user; 401 without).
67
68
  actorResolver: new AuthActorResolver(),
68
69
 
69
70
  defaultAgent: {
@@ -89,24 +90,22 @@ Source: `packages/adonis/docs/getting-started.mdx`,
89
90
 
90
91
  ## Core patterns
91
92
 
92
- ### Pattern 1 — enforce a daily spend budget with `quotas.ledger`
93
+ ### Pattern 1 — set a budget with `quota: { limits }`
93
94
 
94
- Quotas are **opt-in**: omitting `quota` disables budgeting entirely (fail-open).
95
- Configuring one makes the gate fail-closed — `check()` runs before the first model call
96
- and over-budget turns throw `QuotaExceededError` before any tokens are spent.
95
+ Budgets are **opt-in**: omitting `quota` means usage is reported (`GET /agent/quota`) but
96
+ never enforced. With limits, a send while a window is exhausted is refused with `429`
97
+ `{ code: 'quota_exceeded', period }` before the turn starts — no tokens spent.
97
98
 
98
99
  ```ts
99
- import { defineConfig, quotas } from '@adonis-agora/agent'
100
-
101
100
  export default defineConfig({
102
101
  // ...
103
- quota: quotas.ledger({ limitTokens: 1_000_000 }), // enforced off the persisted usage ledger
102
+ quota: { limits: { day: { tokens: 200_000 }, month: { usd: 20 } } },
104
103
  })
105
104
  ```
106
105
 
107
- `quotas.memory({ limitTokens })` is the single-process variant for tests. The `day`
108
- (`YYYY-MM-DD`, UTC) is stamped once by the runner, so day-bucketing stays deterministic
109
- under durable replay.
106
+ Spend (`usd`) is the provider-reported cost the ledger recorded. A budget of your own is a
107
+ `QuotaProvider` (`report({ actor })`) passed as `quota`. `quotas.ledger` / `quotas.memory`,
108
+ `LedgerQuotaStore` and `GET /agent/quota/today` were removed in 0.46.
110
109
 
111
110
  Source: `packages/adonis/docs/governance/quota-and-cost.mdx`.
112
111
 
@@ -181,15 +180,17 @@ Source: `packages/adonis/src/stores/factory.ts` (`tokenSinks`),
181
180
 
182
181
  ## Common mistakes
183
182
 
184
- ### HIGH — omitting `actorResolver` and shipping 401s everywhere
183
+ ### HIGH — omitting `actorResolver` in an app that has logins
185
184
 
186
185
  ```ts
187
- // Wrong — UnconfiguredActorResolver THROWS on every request; all routes answer 401.
186
+ // Wrong for an app with users — no resolver means the endpoints are PUBLIC: every browser
187
+ // is its own anonymous actor (anon:<sha256 of an HttpOnly cookie>), and your users'
188
+ // threads are not tied to their accounts.
188
189
  export default defineConfig({ model: () => aiSdkModel(openai('gpt-4o-mini')) })
189
190
  ```
190
191
 
191
192
  ```ts
192
- // Correct — read the authenticated principal; no identity is ever fabricated.
193
+ // Correct — one line requires login (reads ctx.auth.user; 401 without).
193
194
  import { AuthActorResolver } from '@adonis-agora/agent'
194
195
  export default defineConfig({
195
196
  model: () => aiSdkModel(openai('gpt-4o-mini')),
@@ -197,55 +198,27 @@ export default defineConfig({
197
198
  })
198
199
  ```
199
200
 
200
- Mechanism: the provider installs `UnconfiguredActorResolver` by default, whose
201
- `resolve()` unconditionally throws rather than invent a caller — the route replies 401
202
- before the model ever runs.
203
- Source: `packages/adonis/src/actor-resolver.ts` (`UnconfiguredActorResolver`),
204
- `packages/adonis/docs/governance/authorization.mdx` ("No resolver, no service").
205
-
206
- ### MEDIUM — expecting quota enforcement after configuring only a store
207
-
208
- ```ts
209
- // Wrong — no `quota` key means budgets are DISABLED (fail-open), even with a ledger full of usage.
210
- export default defineConfig({ model, store: 'lucid', stores: { lucid: stores.lucid() } })
211
- ```
212
-
213
- ```ts
214
- // Correct — opt in; the check then runs fail-closed before the first model call.
215
- export default defineConfig({
216
- ...,
217
- quota: quotas.ledger({ limitTokens: 500_000 }),
218
- })
219
- ```
220
-
221
- Mechanism: the loop skips both `check` and `bump` entirely when `deps.quota` is
222
- undefined — there is no implicit limit.
223
- Source: `packages/adonis/docs/governance/quota-and-cost.mdx` ("Quota is opt-in, and off
224
- means open"), `packages/adonis/src/agent-loop.ts` (`if (deps.quota !== undefined)`).
201
+ Mechanism: with no resolver the provider installs `AnonymousActorResolver` and logs a boot
202
+ notice that the endpoints are public. Right for a free public chat (set a `quota` there);
203
+ wrong for an app whose users log in.
204
+ Source: `packages/adonis/src/anonymous-actor-resolver.ts`,
205
+ `packages/adonis/docs/governance/authorization.mdx` ("No resolver: the endpoints are public").
225
206
 
226
- ### HIGH — incrementing counters in a custom QuotaStore's `bump`
207
+ ### MEDIUM — shipping a public (anonymous) chat without a budget
227
208
 
228
209
  ```ts
229
- // Wrong — double-counts every turn when paired with the persisted usage ledger.
230
- const store: QuotaStore = {
231
- async check(actorRef, day) { /* read my counter */ },
232
- async bump(actorRef, day, tokens) { await redis.incrBy(key, tokens) },
233
- }
210
+ // Wrong — no actorResolver (public) and no quota: model spend is unbounded.
211
+ export default defineConfig({ model })
234
212
  ```
235
213
 
236
214
  ```ts
237
- // Correct — a ledger-backed store leaves bump empty: recordUsage already wrote the tokens.
238
- class LedgerQuota implements QuotaStore {
239
- async check(actorRef: string, day: string) { /* sum agent_token_usage */ }
240
- async bump(): Promise<void> {} // deliberate no-op — the ledger is the source of truth
241
- }
215
+ // Correct — limits apply per actor, i.e. per browser in anonymous mode.
216
+ export default defineConfig({ model, quota: { limits: { day: { tokens: 50_000 } } } })
242
217
  ```
243
218
 
244
- Mechanism: `store.recordUsage(...)` has already persisted each turn's tokens;
245
- `quotas.ledger.check` reads that ledger, so adding again in `bump` counts each turn twice.
246
- Only a store keeping its OWN tally (`quotas.memory`, a bespoke Redis counter) should add.
247
- Source: `packages/adonis/docs/governance/quota-and-cost.mdx` ("`bump` is a notification,
248
- not necessarily an increment"), `packages/adonis/src/stores/ledger-quota.ts`.
219
+ Mechanism: without `quota`, `GET /agent/quota` reports usage off the ledger but nothing
220
+ gates a send. With it, the chat route checks the report before starting the turn.
221
+ Source: `packages/adonis/docs/governance/quota-and-cost.mdx` ("Budgets").
249
222
 
250
223
  ### MEDIUM — reading `$0.00` rollups as "this model is free"
251
224
 
@@ -265,26 +238,23 @@ a free model. Per-run/per-usage rows keep their nulls.
265
238
  Source: `packages/adonis/docs/governance/quota-and-cost.mdx` ("`null` in the ledger,
266
239
  `0` in the rollups").
267
240
 
268
- ### MEDIUM — replacing the allowed attachment content types instead of extending
241
+ ### HIGH — sending attachment objects instead of `{ mediaId }` refs
269
242
 
270
243
  ```ts
271
- // Wrong — REPLACES the default list: png/jpeg/pdf/csv now get 415.
272
- attachmentAllowedContentTypes: ['application/json']
244
+ // Wrong — 400: a send names uploads by id only; a url from the client is never trusted.
245
+ { message, attachments: [{ mediaId, url, contentType, name }] }
273
246
  ```
274
247
 
275
248
  ```ts
276
- // Types match EXACTLY (no wildcards); spread the defaults you still want.
277
- attachmentAllowedContentTypes: [
278
- 'image/png', 'image/jpeg', 'image/gif', 'image/webp',
279
- 'application/pdf', 'text/plain', 'text/csv',
280
- 'text/markdown', // your addition
281
- ],
249
+ // Correct — the configured store (attachments: attachmentStores.media()) resolves the url.
250
+ { message, attachments: [{ mediaId }] }
282
251
  ```
283
252
 
284
- Mechanism: passing `attachmentAllowedContentTypes` replaces the default list rather than
285
- merging, and matching is exact — no `text/*` prefix rule — so anything absent is rejected
286
- with 415.
287
- Source: `packages/adonis/src/define_config.ts` (`AgentConfig.attachmentAllowedContentTypes`).
253
+ Mechanism: the chat route refuses any key besides `mediaId`, then resolves each id through
254
+ `AttachmentStagingStore.resolve({ mediaId, actor })` (`403` for another actor's upload).
255
+ Limits come from the store (`attachmentStores.media({ maxBytes, allowedContentTypes })`);
256
+ `allowedContentTypes` replaces the default list and matches exactly.
257
+ Source: `packages/adonis/docs/authoring/attachments.mdx`.
288
258
 
289
259
  See also: `agent-tools/SKILL.md` — registering tools the model can actually call;
290
260
  `agent-governance/SKILL.md` — what `AuthActorResolver` feeds into.
@@ -6,7 +6,7 @@ description: >-
6
6
  discovery from app/agent_tools via the .adonisjs/agent/tools.js barrel (toolsHook with
7
7
  source/importAlias/output) vs the runtime scan fallback, Standard Schema inputs
8
8
  (Zod/Valibot/ArkType), AiToolCtx actor scoping, constructor DI (@inject, lazy + cached),
9
- roles/ability fail-closed gating under defaultRoles, and the dataTool governed read-only
9
+ roles/ability gating under defaultRoles (open by default), and the dataTool governed read-only
10
10
  SQL satellite. Use for "write a tool", "my tool is never called", "tool forbidden",
11
11
  "agent_tools barrel not generating", or "let the agent query the database".
12
12
  metadata:
@@ -167,23 +167,24 @@ Source: `packages/adonis/docs/authoring/tools.mdx` ("Configuring the generator")
167
167
 
168
168
  ## Common mistakes
169
169
 
170
- ### CRITICAL — shipping tools without `roles` and calling them unreachable
170
+ ### CRITICAL — shipping a privileged tool without `roles`
171
171
 
172
172
  ```ts
173
- // Wrong — no roles → inherits defaultRoles ['ADMIN']; MEMBER actors never see this tool.
174
- static tool = { name: 'get_order', description: '...', input }
173
+ // Wrong — no roles → inherits defaultRoles, which is [] (open) by default: every actor,
174
+ // an anonymous visitor included, is offered this tool.
175
+ static tool = { name: 'refund_order', kind: 'action', description: '...', input }
175
176
  ```
176
177
 
177
178
  ```ts
178
- // Correct — declare who may invoke.
179
- static tool = { name: 'get_order', description: '...', input, roles: ['MEMBER'] }
179
+ // Correct — declare who may invoke (or set defaultRoles: ['ADMIN'] to close every tool by default).
180
+ static tool = { name: 'refund_order', kind: 'action', description: '...', input, roles: ['ADMIN'] }
180
181
  ```
181
182
 
182
183
  Mechanism: `definitionsFor` filters the offered tool list through the role gate BEFORE
183
- each model turn, so a tool the actor cannot invoke is never described to the model — it
184
- cannot call what it was never shown. This is why "the model didn't call my tool" is
185
- usually an authorization default, not a prompt problem.
186
- Source: `packages/adonis/docs/authoring/tools.mdx` (fail-closed callout),
184
+ each model turn, and `invoke` re-checks it. A tool with no `roles` takes `defaultRoles`;
185
+ an empty list is no restriction. An `action` still parks on approval — but by default the
186
+ requester approves, so for a public chat that is only a confirmation.
187
+ Source: `packages/adonis/docs/governance/authorization.mdx`,
187
188
  `packages/adonis/src/tool-registry.ts` (`DefaultRolesPolicy.can`).
188
189
 
189
190
  ### HIGH — trusting an id from the model's arguments instead of `ctx.actor`
@@ -1,23 +0,0 @@
1
- import type { AgentStore } from '../spi/agent-store.js';
2
- import type { QuotaStore } from '../spi/quota-store.js';
3
- import type { QuotaState } from '../types.js';
4
- /**
5
- * A production {@link QuotaStore} backed by the persisted usage ledger (the append-only
6
- * `agent_token_usage` table the Lucid store writes each turn to). `check` reads the actor's
7
- * tokens-so-far for the day straight from {@link AgentStore.quotaToday} and compares it to a fixed
8
- * daily limit; `bump` is a deliberate no-op because the loop's `recordUsage` already writes every
9
- * turn's tokens to that same ledger. One source of truth — so enforcement and what a usage dashboard
10
- * reports can never drift, and it works across replicas with no extra shared state.
11
- *
12
- * Ledger-agnostic by design: it wraps any {@link AgentStore} (the `lucid` store for production, the
13
- * in-memory store for tests), so no dedicated quota table is needed — the token-usage ledger IS the
14
- * ledger. Wire it in `config/agent.ts` via `quota: quotas.ledger({ limitTokens })`.
15
- */
16
- export declare class LedgerQuotaStore implements QuotaStore {
17
- private readonly store;
18
- private readonly limitTokens;
19
- constructor(store: AgentStore, limitTokens: number);
20
- check(actorRef: string, day: string): Promise<QuotaState>;
21
- bump(): Promise<void>;
22
- }
23
- //# sourceMappingURL=ledger-quota.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ledger-quota.d.ts","sourceRoot":"","sources":["../../../src/stores/ledger-quota.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAE/C,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAF9B,YACmB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,EAClC;IAEE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAO9D;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1B;CACF"}
@@ -1,32 +0,0 @@
1
- /**
2
- * A production {@link QuotaStore} backed by the persisted usage ledger (the append-only
3
- * `agent_token_usage` table the Lucid store writes each turn to). `check` reads the actor's
4
- * tokens-so-far for the day straight from {@link AgentStore.quotaToday} and compares it to a fixed
5
- * daily limit; `bump` is a deliberate no-op because the loop's `recordUsage` already writes every
6
- * turn's tokens to that same ledger. One source of truth — so enforcement and what a usage dashboard
7
- * reports can never drift, and it works across replicas with no extra shared state.
8
- *
9
- * Ledger-agnostic by design: it wraps any {@link AgentStore} (the `lucid` store for production, the
10
- * in-memory store for tests), so no dedicated quota table is needed — the token-usage ledger IS the
11
- * ledger. Wire it in `config/agent.ts` via `quota: quotas.ledger({ limitTokens })`.
12
- */
13
- export class LedgerQuotaStore {
14
- store;
15
- limitTokens;
16
- constructor(store, limitTokens) {
17
- this.store = store;
18
- this.limitTokens = limitTokens;
19
- }
20
- async check(actorRef, day) {
21
- const { usedTokens } = await this.store.quotaToday(actorRef, day);
22
- return {
23
- usedTokens,
24
- limitTokens: this.limitTokens,
25
- withinLimit: usedTokens < this.limitTokens,
26
- };
27
- }
28
- async bump() {
29
- // No-op: the ledger is the source of truth — recordUsage already persisted this turn's tokens.
30
- }
31
- }
32
- //# sourceMappingURL=ledger-quota.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ledger-quota.js","sourceRoot":"","sources":["../../../src/stores/ledger-quota.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAgB;IAER,KAAK;IACL,WAAW;IAF9B,YACmB,KAAiB,EACjB,WAAmB;qBADnB,KAAK;2BACL,WAAW;IAC3B,CAAC;IAEJ,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,GAAW;QACvC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAClE,OAAO;YACL,UAAU;YACV,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC,WAAW;SAC3C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI;QACR,+FAA+F;IACjG,CAAC;CACF"}