ue-mcp 1.2.0 → 1.2.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.
@@ -1,26 +1,26 @@
1
1
  /**
2
- * A general guard pipeline around the editor bridge, in the shape of NestJS
3
- * guards/interceptors but agnostic to what any guard does.
2
+ * The bridge's binding of flowkit's guard pipeline.
4
3
  *
5
- * Every mutating and non-mutating action crosses `IBridge.call`. A `GuardedBridge`
6
- * runs a registry of `BridgeGuard`s on that seam: each guard may inspect the call,
7
- * run a `before` hook that can veto it (throw) or act on it (e.g. check a file
8
- * out), and an optional `after` hook that can observe or replace the result
9
- * (audit, transform). The chain itself knows nothing about source control,
10
- * policy, rate limiting, or any concrete concern - those are guards.
4
+ * Every mutating and non-mutating action crosses `IBridge.call`. That seam is
5
+ * guarded: each guard may inspect the call, run a `before` hook that can veto
6
+ * it (throw) or act on it (e.g. check a file out), and an `after` hook that can
7
+ * observe or replace the result. The chain itself knows nothing about source
8
+ * control, policy, rate limiting, or any concrete concern - those are guards.
11
9
  *
12
- * `CallContext` is the per-call execution context handed to guards. It carries
13
- * the raw method/params and a lazy enrichment layer (`write()` / `writeFiles()`)
14
- * that write-oriented guards may consult, computed on demand and cached so a
15
- * guard that ignores writes pays nothing.
10
+ * The pipeline, its ordering, and the `guard.<name>.<phase>` task convention
11
+ * live in `@db-lyon/flowkit/guard`, because none of that is about Unreal. What
12
+ * stays here is what is: the shape of a bridge call, and the write
13
+ * classification in `write-methods.ts` that decides which content paths a call
14
+ * is about to modify.
16
15
  */
16
+ import { GuardRegistry as FlowkitGuardRegistry, type Guard, type GuardContext } from "@db-lyon/flowkit/guard";
17
17
  import type { IBridge } from "../bridge.js";
18
18
  import type { EditorSession } from "../session.js";
19
19
  import { type WriteClassification } from "./write-methods.js";
20
20
  /** Resolve a UE content path to an absolute on-disk file, or null if it does not exist. */
21
21
  export type ResolveExistingFile = (contentPath: string) => string | null;
22
- /** Per-call execution context passed to every guard. Analogous to a NestJS ExecutionContext. */
23
- export interface CallContext {
22
+ /** Per-call execution context passed to every guard. */
23
+ export interface CallContext extends GuardContext {
24
24
  readonly method: string;
25
25
  readonly params: Record<string, unknown>;
26
26
  readonly timeoutMs?: number;
@@ -28,36 +28,25 @@ export interface CallContext {
28
28
  readonly bridge: IBridge;
29
29
  /** The editor this call is bound to. Absent only for a bridge built outside a session. */
30
30
  readonly session?: EditorSession;
31
- /** Scratch space shared across guards for the life of one call. */
32
- readonly meta: Map<string, unknown>;
33
31
  /** Lazy: how this call classifies as a write (which content paths it touches). Cached. */
34
32
  write(): WriteClassification;
35
33
  /** Lazy: absolute, existing on-disk files this call will modify (subset of write paths). Cached. */
36
34
  writeFiles(): string[];
37
35
  }
38
36
  /**
39
- * A guard on the bridge pipeline. Any of the hooks is optional. Guards are
40
- * agnostic: source control, access policy, audit, rate limiting, and approval
41
- * gating are all just guards.
37
+ * A guard on the bridge pipeline. Guards are agnostic: source control, access
38
+ * policy, audit, rate limiting, and approval gating are all just guards.
42
39
  */
43
- export interface BridgeGuard {
44
- /** Stable identifier, for logging and ordering ties. */
45
- readonly name: string;
46
- /** Lower runs first in `before` and last in `after`. Default 0. */
47
- readonly order?: number;
48
- /** Whether this guard participates for a given call. Default: always. */
49
- appliesTo?(ctx: CallContext): boolean | Promise<boolean>;
50
- /** Runs before the call. Throw to DENY the call; side effects are allowed. */
51
- before?(ctx: CallContext): Promise<void>;
52
- /** Runs after a successful call. Return a value to replace the result; return nothing to leave it. */
53
- after?(ctx: CallContext, result: unknown): Promise<unknown | void>;
54
- }
55
- /** An ordered set of guards. Built-in guards register directly; plugin guards are discovered. */
56
- export declare class GuardRegistry {
57
- private guards;
58
- register(guard: BridgeGuard): this;
59
- list(): readonly BridgeGuard[];
60
- get size(): number;
40
+ export type BridgeGuard = Guard<CallContext, unknown>;
41
+ /** The bridge's guard set. Built-in guards register directly; plugin guards are discovered. */
42
+ export declare class GuardRegistry extends FlowkitGuardRegistry<CallContext, unknown> {
61
43
  }
44
+ /**
45
+ * The scope a `guard.<name>.<phase>Write` task binds to: the call resolves to
46
+ * existing on-disk files it is about to modify. Declared here rather than in
47
+ * the task layer so the hand-written and task-backed guards agree on what
48
+ * "write" means.
49
+ */
50
+ export declare function writeScope(ctx: CallContext): boolean;
62
51
  /** Build the per-call context, wiring the lazy write-enrichment helpers. */
63
52
  export declare function makeCallContext(method: string, params: Record<string, unknown>, timeoutMs: number | undefined, rawBridge: IBridge, resolveExistingFile: ResolveExistingFile, session?: EditorSession): CallContext;
@@ -1,34 +1,49 @@
1
+ /**
2
+ * The bridge's binding of flowkit's guard pipeline.
3
+ *
4
+ * Every mutating and non-mutating action crosses `IBridge.call`. That seam is
5
+ * guarded: each guard may inspect the call, run a `before` hook that can veto
6
+ * it (throw) or act on it (e.g. check a file out), and an `after` hook that can
7
+ * observe or replace the result. The chain itself knows nothing about source
8
+ * control, policy, rate limiting, or any concrete concern - those are guards.
9
+ *
10
+ * The pipeline, its ordering, and the `guard.<name>.<phase>` task convention
11
+ * live in `@db-lyon/flowkit/guard`, because none of that is about Unreal. What
12
+ * stays here is what is: the shape of a bridge call, and the write
13
+ * classification in `write-methods.ts` that decides which content paths a call
14
+ * is about to modify.
15
+ */
16
+ import { GuardRegistry as FlowkitGuardRegistry, guardContextBase, lazy, } from "@db-lyon/flowkit/guard";
1
17
  import { classifyWrite } from "./write-methods.js";
2
- /** An ordered set of guards. Built-in guards register directly; plugin guards are discovered. */
3
- export class GuardRegistry {
4
- guards = [];
5
- register(guard) {
6
- this.guards.push(guard);
7
- // Stable order: by `order`, then by name for determinism.
8
- this.guards.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.name.localeCompare(b.name));
9
- return this;
10
- }
11
- list() {
12
- return this.guards;
13
- }
14
- get size() {
15
- return this.guards.length;
16
- }
18
+ /** The bridge's guard set. Built-in guards register directly; plugin guards are discovered. */
19
+ export class GuardRegistry extends FlowkitGuardRegistry {
20
+ }
21
+ /**
22
+ * The scope a `guard.<name>.<phase>Write` task binds to: the call resolves to
23
+ * existing on-disk files it is about to modify. Declared here rather than in
24
+ * the task layer so the hand-written and task-backed guards agree on what
25
+ * "write" means.
26
+ */
27
+ export function writeScope(ctx) {
28
+ return ctx.writeFiles().length > 0;
17
29
  }
18
30
  /** Build the per-call context, wiring the lazy write-enrichment helpers. */
19
31
  export function makeCallContext(method, params, timeoutMs, rawBridge, resolveExistingFile, session) {
20
- let writeCache;
21
- let filesCache;
22
- const write = () => (writeCache ??= classifyWrite(method, params));
23
- const writeFiles = () => {
24
- if (filesCache)
25
- return filesCache;
32
+ const ctx = {
33
+ ...guardContextBase(),
34
+ method,
35
+ params,
36
+ timeoutMs,
37
+ bridge: rawBridge,
38
+ session,
39
+ };
40
+ const write = lazy(ctx, "write", () => classifyWrite(method, params));
41
+ const writeFiles = lazy(ctx, "writeFiles", () => {
26
42
  const c = write();
27
- filesCache = c.writes
43
+ return c.writes
28
44
  ? c.contentPaths.map(resolveExistingFile).filter((f) => f !== null)
29
45
  : [];
30
- return filesCache;
31
- };
32
- return { method, params, timeoutMs, bridge: rawBridge, session, meta: new Map(), write, writeFiles };
46
+ });
47
+ return Object.assign(ctx, { write, writeFiles });
33
48
  }
34
49
  //# sourceMappingURL=guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/flow/guard.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AAwC7E,iGAAiG;AACjG,MAAM,OAAO,aAAa;IAChB,MAAM,GAAkB,EAAE,CAAC;IAEnC,QAAQ,CAAC,KAAkB;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,MAA+B,EAC/B,SAA6B,EAC7B,SAAkB,EAClB,mBAAwC,EACxC,OAAuB;IAEvB,IAAI,UAA2C,CAAC;IAChD,IAAI,UAAgC,CAAC;IAErC,MAAM,KAAK,GAAG,GAAwB,EAAE,CAAC,CAAC,UAAU,KAAK,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxF,MAAM,UAAU,GAAG,GAAa,EAAE;QAChC,IAAI,UAAU;YAAE,OAAO,UAAU,CAAC;QAClC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QAClB,UAAU,GAAG,CAAC,CAAC,MAAM;YACnB,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;YAChF,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AACvG,CAAC"}
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/flow/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,aAAa,IAAI,oBAAoB,EACrC,gBAAgB,EAChB,IAAI,GAGL,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AA0B7E,+FAA+F;AAC/F,MAAM,OAAO,aAAc,SAAQ,oBAA0C;CAAG;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAgB;IACzC,OAAO,GAAG,CAAC,UAAU,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,MAA+B,EAC/B,SAA6B,EAC7B,SAAkB,EAClB,mBAAwC,EACxC,OAAuB;IAEvB,MAAM,GAAG,GAAG;QACV,GAAG,gBAAgB,EAAE;QACrB,MAAM;QACN,MAAM;QACN,SAAS;QACT,MAAM,EAAE,SAAS;QACjB,OAAO;KACO,CAAC;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,CAAC,MAAM;YACb,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;YAChF,CAAC,CAAC,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -1,13 +1,3 @@
1
- /**
2
- * GuardedBridge - runs a `GuardRegistry` pipeline around the editor bridge.
3
- *
4
- * Before a call reaches Unreal, each applicable guard's `before` hook runs in
5
- * order (any may throw to deny). After a successful call, each guard's `after`
6
- * hook runs in reverse order and may replace the result. With an empty registry
7
- * this is a pure pass-through, so it is always safe to install.
8
- *
9
- * Only `call` is gated; connection lifecycle delegates straight through.
10
- */
11
1
  import type { BridgeTarget, IBridge } from "../bridge.js";
12
2
  import type { EditorSession } from "../session.js";
13
3
  import { GuardRegistry, type ResolveExistingFile } from "./guard.js";
@@ -1,4 +1,17 @@
1
- import { makeCallContext, } from "./guard.js";
1
+ /**
2
+ * GuardedBridge - runs a `GuardRegistry` pipeline around the editor bridge.
3
+ *
4
+ * Before a call reaches Unreal, each applicable guard's `before` hook runs in
5
+ * order (any may throw to deny). After a successful call, each guard's `after`
6
+ * hook runs in reverse order and may replace the result. With an empty registry
7
+ * this is a pure pass-through, so it is always safe to install.
8
+ *
9
+ * The pipeline itself is `runGuarded` from flowkit; what this class adds is the
10
+ * `IBridge` shape. Only `call` is gated; connection lifecycle delegates
11
+ * straight through.
12
+ */
13
+ import { runGuarded } from "@db-lyon/flowkit/guard";
14
+ import { makeCallContext } from "./guard.js";
2
15
  export class GuardedBridge {
3
16
  inner;
4
17
  registry;
@@ -26,32 +39,14 @@ export class GuardedBridge {
26
39
  return this.inner.getTarget();
27
40
  }
28
41
  async call(method, params, timeoutMs) {
42
+ // `runGuarded` is already a pass-through on an empty registry, but building
43
+ // the context is not free and every bridge call lands here. Most servers run
44
+ // with no guards at all, so skip the allocation outright.
29
45
  if (this.registry.size === 0) {
30
46
  return this.inner.call(method, params, timeoutMs);
31
47
  }
32
48
  const ctx = makeCallContext(method, params ?? {}, timeoutMs, this.inner, this.resolveExistingFile, this.session);
33
- // Resolve applicability once so `before`/`after` see a consistent set.
34
- const applicable = [];
35
- for (const g of this.registry.list()) {
36
- if (!g.appliesTo || (await g.appliesTo(ctx)))
37
- applicable.push(g);
38
- }
39
- // before: in order. A throw denies the call and propagates unchanged.
40
- for (const g of applicable) {
41
- if (g.before)
42
- await g.before(ctx);
43
- }
44
- let result = await this.inner.call(method, params, timeoutMs);
45
- // after: in reverse order. A returned value replaces the result.
46
- for (let i = applicable.length - 1; i >= 0; i--) {
47
- const g = applicable[i];
48
- if (g.after) {
49
- const replaced = await g.after(ctx, result);
50
- if (replaced !== undefined)
51
- result = replaced;
52
- }
53
- }
54
- return result;
49
+ return runGuarded(ctx, this.registry, () => this.inner.call(method, params, timeoutMs));
55
50
  }
56
51
  }
57
52
  //# sourceMappingURL=guarded-bridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guarded-bridge.js","sourceRoot":"","sources":["../../src/flow/guarded-bridge.ts"],"names":[],"mappings":"AAYA,OAAO,EAEL,eAAe,GAGhB,MAAM,YAAY,CAAC;AAIpB,MAAM,OAAO,aAAa;IAEL;IACA;IACA;IAGA;IANnB,YACmB,KAAc,EACd,QAAuB,EACvB,mBAAwC;IACzD;2EACuE;IACtD,OAAuB;QALvB,UAAK,GAAL,KAAK,CAAS;QACd,aAAQ,GAAR,QAAQ,CAAe;QACvB,wBAAmB,GAAnB,mBAAmB,CAAqB;QAGxC,YAAO,GAAP,OAAO,CAAgB;IACvC,CAAC;IAEJ,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,SAAkB;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,eAAe,CAAC,YAAoB,EAAE,UAAmB;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CACR,MAAc,EACd,MAAgC,EAChC,SAAkB;QAElB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAEjH,uEAAuE;QACvE,MAAM,UAAU,GAAkB,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnE,CAAC;QAED,sEAAsE;QACtE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,MAAM;gBAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAE9D,iEAAiE;QACjE,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAChD,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC5C,IAAI,QAAQ,KAAK,SAAS;oBAAE,MAAM,GAAG,QAAQ,CAAC;YAChD,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
1
+ {"version":3,"file":"guarded-bridge.js","sourceRoot":"","sources":["../../src/flow/guarded-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,OAAO,EAAiB,eAAe,EAA4B,MAAM,YAAY,CAAC;AAItF,MAAM,OAAO,aAAa;IAEL;IACA;IACA;IAGA;IANnB,YACmB,KAAc,EACd,QAAuB,EACvB,mBAAwC;IACzD;2EACuE;IACtD,OAAuB;QALvB,UAAK,GAAL,KAAK,CAAS;QACd,aAAQ,GAAR,QAAQ,CAAe;QACvB,wBAAmB,GAAnB,mBAAmB,CAAqB;QAGxC,YAAO,GAAP,OAAO,CAAgB;IACvC,CAAC;IAEJ,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,SAAkB;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,eAAe,CAAC,YAAoB,EAAE,UAAmB;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CACR,MAAc,EACd,MAAgC,EAChC,SAAkB;QAElB,4EAA4E;QAC5E,6EAA6E;QAC7E,0DAA0D;QAC1D,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,GAAG,GAAG,eAAe,CACzB,MAAM,EACN,MAAM,IAAI,EAAE,EACZ,SAAS,EACT,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,mBAAmB,EACxB,IAAI,CAAC,OAAO,CACb,CAAC;QACF,OAAO,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1F,CAAC;CACF"}