pantheon-opencode 1.3.3 → 1.3.4

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 (47) hide show
  1. package/README.md +115 -12
  2. package/bin/pantheon-init.mjs +160 -120
  3. package/opencode.json +0 -4
  4. package/package.json +7 -4
  5. package/scripts/doctor.mjs +56 -22
  6. package/scripts/hooks/on-subagent-delegation-stop.sh +22 -5
  7. package/scripts/install/opencode.mjs +108 -26
  8. package/scripts/install/plugin.mjs +112 -28
  9. package/scripts/install-mcp.mjs +1 -1
  10. package/scripts/redaction-gate.mjs +69 -0
  11. package/scripts/release-notes.mjs +317 -0
  12. package/scripts/test-context-compression.sh +5 -2
  13. package/scripts/validate-package.mjs +56 -0
  14. package/scripts/versioning.mjs +117 -18
  15. package/src/agents/zeus.md +6 -2
  16. package/src/pantheon/background-job-board.ts +24 -38
  17. package/src/pantheon/chat-reminders.ts +79 -0
  18. package/src/pantheon/compaction-assert.ts +160 -0
  19. package/src/pantheon/delegation-compaction.ts +143 -16
  20. package/src/pantheon/delegation-finalize.ts +8 -1
  21. package/src/pantheon/delegation-notify.ts +19 -157
  22. package/src/pantheon/delegation.ts +330 -28
  23. package/src/pantheon/dispatch-guard.ts +9 -4
  24. package/src/pantheon/goal-loop.ts +7 -4
  25. package/src/pantheon/hashline/tool.ts +2 -1
  26. package/src/pantheon/logger.ts +117 -0
  27. package/src/pantheon/plugin-once.ts +76 -0
  28. package/src/pantheon/preemptive-compact.ts +76 -0
  29. package/src/pantheon/presets.d.mts +31 -0
  30. package/src/pantheon/presets.mjs +105 -3
  31. package/src/pantheon/todo-enforcer.ts +79 -27
  32. package/src/pantheon/todo-preserve.ts +295 -0
  33. package/src/plugin.ts +177 -50
  34. package/src/plugins/pantheon-hooks.ts +35 -22
  35. package/src/plugins/tui/dist/server.d.ts +17 -0
  36. package/src/plugins/tui/dist/server.d.ts.map +1 -0
  37. package/src/plugins/tui/dist/server.js +20 -0
  38. package/src/plugins/tui/dist/server.js.map +1 -0
  39. package/src/plugins/tui/dist/tui.d.ts +303 -1
  40. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  41. package/src/plugins/tui/dist/tui.js +977 -350
  42. package/src/plugins/tui/dist/tui.tsx +1427 -150
  43. package/src/plugins/tui/package.json +4 -3
  44. package/src/plugins/tui/src/index.tsx +1427 -150
  45. package/src/plugins/tui/src/server.ts +15 -0
  46. package/src/plugins/tui/tsdown.config.ts +1 -1
  47. package/src/routing.yml +3 -1
@@ -1,8 +1,310 @@
1
1
  import { TuiPluginModule } from "@opencode-ai/plugin/tui";
2
2
  //#region src/index.d.ts
3
+ type DelegationEntry = {
4
+ /** Job alias, e.g. "apo-1" (from the H1 title, falling back to filename). */
5
+ alias: string;
6
+ /** Parent session the job was launched from (dir name under .pantheon/delegations). */
7
+ sessionID: string;
8
+ /** Child session id (= board task id, from the `Task ID` header). The
9
+ * children channel always sets it from the child session itself; the md
10
+ * channel parses it so child↔md matching works by taskID. */
11
+ taskID?: string;
12
+ /** Agent name, e.g. "apollo". */
13
+ agent: string;
14
+ state: 'running' | 'completed' | 'error' | 'cancelled';
15
+ /** Epoch ms of the `Started` header. */
16
+ startedAt: number;
17
+ /** Epoch ms of the `Finalized` header — null while still running. */
18
+ updatedAt: number | null;
19
+ timedOut: boolean;
20
+ description: string;
21
+ /** True while the panel is waiting for pantheon_delegation_read. */
22
+ read?: boolean;
23
+ /** Internal provenance used to keep a finalized md report authoritative.
24
+ * 'children-only' = a native task() child session with NO board report
25
+ * (rendered with the distinct `[task]` tag); 'md' = board report wins. */
26
+ source?: 'child' | 'live' | 'md' | 'children-only';
27
+ };
28
+ /** Parse one delegation report md header into a structured entry.
29
+ * Returns null (skip) when the file is not a recognizable report:
30
+ * missing agent/state/startedAt, an unknown state, or an unparsable
31
+ * Started timestamp. The alias falls back to the file name when the H1
32
+ * title is missing. Pure — no I/O.
33
+ *
34
+ * Linear, single-pass over `raw.split('\n')` with plain string operations
35
+ * (startsWith/indexOf/slice) — zero regex, so worst case is O(bytes) even
36
+ * on adversarial whitespace-heavy input (ReDoS regression, CodeQL 12x HIGH). */
37
+ declare function parseDelegationMarkdown(raw: string, fileAlias?: string, sessionID?: string): DelegationEntry | null;
38
+ /** Read every delegation report under `<dir>/<sessionID>/<alias>.md`.
39
+ * Fail-open: a missing/unreadable directory yields [], and each unreadable
40
+ * or malformed file is skipped individually. Entries are sorted running
41
+ * first, then terminal by `updatedAt` (most recent first) so the panel can
42
+ * render them in order directly. */
43
+ declare function readDelegationEntries(dir: string): Promise<DelegationEntry[]>;
44
+ /** Read delegation reports from EVERY session under
45
+ * `<root>/.pantheon/delegations/<sessionID>/<alias>.md` — the panel's
46
+ * HISTORY channel. Unlike the children/live channels it does NOT depend on a
47
+ * resolved sessionID: with no focused session (null/placeholder), the panel
48
+ * still shows the reports from all past sessions (running first, Finalized
49
+ * desc — the sort applied by readDelegationEntries). Fail-open: a
50
+ * missing/unreadable directory yields []. */
51
+ declare function readAllDelegationEntries(root: string): Promise<DelegationEntry[]>;
52
+ /** Resolve the directory where the job board writes delegation md reports.
53
+ * The board writes `.pantheon/delegations` RELATIVE to the server cwd,
54
+ * which the TUI exposes as `TuiState.path.directory`. `project` does NOT
55
+ * exist on `TuiState.path` (the old `state?.project ?? state?.worktree`
56
+ * resolution was always undefined for the first term) and `worktree` is
57
+ * `/` when there is no git (e.g. the sandbox test project) — a root of
58
+ * `''` or `'/'` must fall back to `process.cwd()`. */
59
+ declare function resolveDelegationsDir(state: {
60
+ directory?: string;
61
+ worktree?: string;
62
+ } | undefined, cwd?: string): string;
63
+ /** Project root derived from the delegations dir: `<root>/.pantheon/delegations`
64
+ * → `<root>`. Used to point the panel logger at the REAL hooks.log — passing
65
+ * the delegations dir (or its dirname) directly made createTuiLogger append
66
+ * to `<root>/.pantheon/.pantheon/logs/hooks.log`, a nested empty dir the real
67
+ * log never saw. Pure — no I/O. */
68
+ declare function panelLogDir(delegationsDir: string): string;
69
+ /** Where the panel logger appends lines: `<projectRoot>/.pantheon/logs/hooks.log`.
70
+ * Pure — testable without the runtime. */
71
+ declare function tuiLogPath(projectRoot: string): string;
72
+ /** Sort delegations: running first, then terminal by recency (updatedAt,
73
+ * falling back to startedAt, descending). Shared by the md reader and
74
+ * mergeDelegationSources. */
75
+ declare function compareDelegationEntries(a: DelegationEntry, b: DelegationEntry): number;
76
+ /** The list the panel actually renders: running jobs first, then the most
77
+ * recent terminal reports (capped). Pure — so the history-only panel (no
78
+ * sessionID) is testable without the TUI runtime. The header count uses the
79
+ * same "running + recentes" list. */
80
+ declare function visibleDelegationList(all: readonly DelegationEntry[], maxTerminal?: number): DelegationEntry[];
81
+ /** Compact elapsed-time label: "5m 12s", "1h 30m", "2d 4h" — ticks every
82
+ * second for running jobs. */
83
+ declare function fmtElapsed(ms: number): string;
84
+ /** Elapsed label for one entry: running → ticks `now - startedAt`, terminal
85
+ * → fixed `updatedAt - startedAt` (em dash when no finalized timestamp). */
86
+ declare function delegationElapsed(entry: DelegationEntry, now: number): string;
87
+ /** The activity labels shown by the animated row. Keeping this pure makes the
88
+ * state machine testable without booting OpenCode's renderer. */
89
+ type DelegationActivity = 'delegating' | 'working' | 'reading' | 'completed' | 'error' | 'cancelled';
90
+ declare function delegationActivity(entry: DelegationEntry): DelegationActivity;
91
+ declare function delegationActivityLabel(entry: DelegationEntry): string;
92
+ /** Return a deterministic spinner frame. The View ticks this every 140ms. */
93
+ declare function delegationSpinnerFrame(now: number): string;
94
+ /** Merge the immediate tool-event channel into the child-session channel.
95
+ *
96
+ * Children remain the durable source, while live entries make a delegation
97
+ * visible before the child API/report catches up. A finalized md entry wins
98
+ * over a stale live entry; a child-only row is upgraded with live agent,
99
+ * alias, phase and timestamps. */
100
+ declare function mergeChildDelegationSources(children: readonly DelegationEntry[], live: readonly LiveDelegationEntry[]): DelegationEntry[];
101
+ /** Duck-typed subset of a tool part (SDK v2 `ToolPart` / `ToolState`). */
102
+ type DelegationToolPart = {
103
+ id?: string;
104
+ callID?: string;
105
+ sessionID?: string;
106
+ type?: string;
107
+ tool?: string;
108
+ state?: {
109
+ status?: string;
110
+ input?: Record<string, unknown>;
111
+ output?: string;
112
+ error?: string;
113
+ time?: {
114
+ start?: number;
115
+ end?: number;
116
+ };
117
+ };
118
+ };
119
+ /** One live delegation tracked in-memory, keyed by the delegate callID. */
120
+ type LiveDelegationEntry = {
121
+ /** Tool call id of the pantheon_delegate part (stable across events). */
122
+ callID: string;
123
+ /** Part id (for message.part.removed cleanup). */
124
+ partID: string;
125
+ /** Parent session the delegation was launched from. */
126
+ sessionID: string;
127
+ tool: 'pantheon_delegate' | 'pantheon_delegation_read';
128
+ /** Agent name (from the delegate input args). */
129
+ agent: string;
130
+ description: string;
131
+ /** Known after the delegate tool completes (parsed from its output). */
132
+ alias: string | null;
133
+ /** Child session id (parsed from the delegate output). */
134
+ taskID: string | null;
135
+ state: 'running' | 'completed' | 'error' | 'cancelled';
136
+ startedAt: number;
137
+ updatedAt: number | null;
138
+ /** True once a pantheon_delegation_read for this job has been observed. */
139
+ read: boolean;
140
+ };
141
+ /** Result of parsing one tool part into lifecycle-relevant fields. */
142
+ type ParsedDelegationToolPart = {
143
+ callID: string;
144
+ partID: string;
145
+ sessionID: string;
146
+ tool: 'pantheon_delegate' | 'pantheon_delegation_read';
147
+ /** null for read parts (no agent arg — the id targets an existing job). */
148
+ agent: string | null;
149
+ description: string;
150
+ status: 'pending' | 'running' | 'completed' | 'error';
151
+ alias: string | null;
152
+ taskID: string | null;
153
+ startedAt: number;
154
+ endAt: number | null;
155
+ };
156
+ /** Extract the tool name + args from a `message.part.updated` part and
157
+ * reduce it to what the panel needs. Returns null for anything that is
158
+ * not a pantheon delegation tool part (or is missing its callID). */
159
+ declare function parseDelegationToolPart(part: DelegationToolPart, now?: number): ParsedDelegationToolPart | null;
160
+ /** Apply one tool part to the live map. Returns true when the map changed.
161
+ * Pure w.r.t. I/O — only mutates `map`. */
162
+ declare function reduceDelegationToolPart(map: Map<string, LiveDelegationEntry>, part: DelegationToolPart, now?: number): boolean;
163
+ /** Remove a live entry by part id (message.part.removed) or call id.
164
+ * Returns true when something was removed. */
165
+ declare function removeDelegationEntry(map: Map<string, LiveDelegationEntry>, partIDOrCallID: string): boolean;
166
+ /** Collect pantheon delegation tool parts from a session's messages.
167
+ * Messages may carry their parts inline (duck-typed `msg.parts`); when
168
+ * they don't, the optional `getParts(messageID)` callback is used (the TUI
169
+ * SDK exposes `api.state.part(messageID)`). Pure w.r.t. I/O — used by the
170
+ * mount re-scan to re-seed the live map after compaction/attach. */
171
+ declare function collectDelegationToolParts(messages: readonly {
172
+ id?: string;
173
+ parts?: unknown[];
174
+ }[] | undefined, getParts?: (messageID: string) => readonly unknown[] | undefined): DelegationToolPart[];
175
+ /** Apply a batch of tool parts (in message order) to the live map. Used on
176
+ * mount to re-seed entries that `message.part.removed` (compaction) wiped,
177
+ * from the session's existing tool parts. Returns how many parts changed
178
+ * the map (0 on the second identical seed — idempotent, no extra bumps). */
179
+ declare function seedLiveDelegationMap(map: Map<string, LiveDelegationEntry>, parts: readonly DelegationToolPart[], now?: number): number;
180
+ /** Convert a live entry into the shared display shape. Alias falls back to
181
+ * a `live-<callID>` prefix while the delegate tool has not completed yet. */
182
+ declare function toDelegationEntry(live: LiveDelegationEntry): DelegationEntry;
183
+ /** Combine the live channel with the md (historical) channel into one
184
+ * display list. Dedupes by (sessionID, alias) — aliases are per-parent-
185
+ * session, so the same alias in different sessions stays separate. A
186
+ * terminal md entry is authoritative over a live running entry for the
187
+ * same job (it carries Finalized/timedOut/cancelled from finalize). */
188
+ declare function mergeDelegationSources(live: readonly LiveDelegationEntry[], md: readonly DelegationEntry[]): DelegationEntry[];
189
+ /** Server-aligned session id validity: opencode rejects anything not starting
190
+ * with "ses" (SchemaError). This deliberately mirrors that exact contract —
191
+ * nothing stricter, nothing looser — so a template placeholder ("{sessionID}"),
192
+ * its URL-encoded form ("%7BsessionID%7D", what the server reported in the
193
+ * schema error), an empty/undefined value, or a foreign id (e.g. "wrk_") can
194
+ * never reach a path and error-spam the log. Confirmed: "{sessionID}" starts
195
+ * with "{" and "%7BsessionID%7D" with "%" — both fail startsWith("ses"), so
196
+ * the placeholder is rejected WITHOUT an explicit denylist (covered by tests). */
197
+ declare function isValidSessionId(id: unknown): id is string;
198
+ /** Sources the sidebar can resolve the CURRENT session id from. Duck-typed
199
+ * subsets of TuiPluginApi / TuiState / TuiRouteCurrent so the helper stays
200
+ * pure and testable without the TUI runtime. */
201
+ type TuiSessionSources = {
202
+ /** sidebar_content slot prop (`session_id`). */
203
+ sessionID?: string | null;
204
+ api?: {
205
+ /** Runtime state superset — may expose the current session id. */
206
+ state?: {
207
+ sessionID?: unknown;
208
+ };
209
+ /** Typed route: { name: 'session', params: { sessionID } } when in one. */
210
+ route?: {
211
+ current?: {
212
+ name?: string;
213
+ params?: Record<string, unknown>;
214
+ };
215
+ };
216
+ } | null;
217
+ };
218
+ /** Resolve the current session id for the sidebar. Order: slot prop →
219
+ * api.state.sessionID (runtime superset) → api.route.current.params.sessionID
220
+ * (typed route). Every source is validated; invalid/absent → next source.
221
+ * NEVER returns a placeholder or non-ses id. Null → callers MUST skip the
222
+ * fetch (empty panel, zero errors). Pure — no I/O, no runtime required. */
223
+ declare function resolveCurrentSessionID(sources: TuiSessionSources): string | null;
224
+ /** THE single choke point for every `session.children` / session-API path.
225
+ * Returns `{ path: { id } }` ONLY for a server-valid session id; returns
226
+ * null for anything else (placeholder, empty, foreign id) so the caller
227
+ * skips the call entirely instead of sending an unsubstituted placeholder
228
+ * (the "%7BsessionID%7D" regression). Every session-API call site MUST go
229
+ * through this function (enforced by the source-scan test in
230
+ * tests/pantheon/tui-delegations.test.ts). */
231
+ declare function safeSessionPath(id: unknown): {
232
+ path: {
233
+ id: string;
234
+ };
235
+ } | null;
236
+ /** Build the `session.children` path ONLY from a validated session id.
237
+ * Delegates to {@link safeSessionPath} — the single choke point. Returns
238
+ * null for null/invalid ids so the caller skips the fetch instead of
239
+ * sending an unsubstituted placeholder (the "%7BsessionID%7D" regression). */
240
+ declare function buildChildrenPath(id: string | null | undefined): {
241
+ path: {
242
+ id: string;
243
+ };
244
+ } | null;
245
+ /** Duck-typed subset of a child Session (+ its live status type). */
246
+ type ChildDelegationLike = {
247
+ /** Child session id (= board task id). */
248
+ id: string;
249
+ /** Session title — the delegate's description or prompt prefix. */
250
+ title?: string;
251
+ /** Agent name when the child session carries one (duck-typed; native
252
+ * task() children may expose it, board reports always do). */
253
+ agent?: string;
254
+ /** Status type from api.state.session.status: 'busy' | 'retry' | 'idle',
255
+ * or undefined when the status API is unavailable. */
256
+ status?: string;
257
+ time?: {
258
+ created?: number;
259
+ updated?: number;
260
+ };
261
+ };
262
+ /** Map a child status type to a display state. busy/retry → running
263
+ * (the child is actively working), idle → completed, unknown → running
264
+ * (fail-open: a freshly-seen child is assumed active; the 1s poll + md
265
+ * correct it as soon as terminal data exists). */
266
+ declare function childStatusToState(status: string | undefined): 'running' | 'completed';
267
+ /** Row tag for a delegation entry: `[task]` for native task() children
268
+ * (source 'children-only' — no board report), `[<alias>]` for board rows
269
+ * ([apo-1]). The panel renders the tag with a distinct style so native
270
+ * task() children are visually separable from pantheon_delegate jobs. */
271
+ declare function delegationTag(entry: DelegationEntry): string;
272
+ /** Turn child sessions (PRIMARY) enriched with md reports into the display
273
+ * list. One entry per child id (duplicates across re-fetches collapse).
274
+ * The md report is matched by `Task ID` (== child.id) and supplies alias,
275
+ * agent, description, terminal state and duration. A child without a
276
+ * report still renders: description from its title, agent from the child
277
+ * itself (fallback 'agent'), state derived from its status, startedAt from
278
+ * time.created. A report-less child is a NATIVE task() child (every
279
+ * child of the current session — pantheon_delegate OR the native `task()`
280
+ * tool — carries parentID = caller), so it gets source 'children-only'
281
+ * and the `[task]` tag instead of a board alias.
282
+ * Terminal md state wins over the derived state; a running md defers to
283
+ * the child's live status. Sorted running-first (compareDelegationEntries).
284
+ * Pure — no I/O. */
285
+ declare function childrenToDelegationEntries(children: readonly ChildDelegationLike[] | undefined, md: readonly DelegationEntry[], now?: number): DelegationEntry[];
286
+ /** Navigate the TUI to a child session (click/Enter on a delegation row).
287
+ * Returns false when the route API is unavailable or the target id is
288
+ * missing/placeholder — the row stays inert instead of crashing. Only a
289
+ * server-valid session id ("ses...") ever reaches the router, so an
290
+ * unsubstituted "{sessionID}" placeholder can never be routed. */
291
+ declare function navigateToDelegationSession(route: {
292
+ navigate?: (name: string, params?: Record<string, unknown>) => void;
293
+ } | undefined, taskID: string | undefined): boolean;
294
+ /** Plugin-level live delegation store shared with the event subscriptions
295
+ * in `tui()`: the map of live entries + a version signal bumped on every
296
+ * mutation. The View subscribes to the version (in an effect) to refresh the
297
+ * durable child list and also reads the map as an optimistic live source. */
298
+ type LiveDelegationStore = {
299
+ map: Map<string, LiveDelegationEntry>;
300
+ /** Reactive version getter — View reads it inside an effect to re-fetch. */
301
+ version: () => number;
302
+ /** Bump the version after a live mutation. */
303
+ bump: () => void;
304
+ };
3
305
  declare const plugin: TuiPluginModule & {
4
306
  id: string;
5
307
  };
6
308
  //#endregion
7
- export { plugin as default };
309
+ export { ChildDelegationLike, DelegationActivity, DelegationEntry, DelegationToolPart, LiveDelegationEntry, LiveDelegationStore, ParsedDelegationToolPart, TuiSessionSources, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, fmtElapsed, isValidSessionId, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, toDelegationEntry, tuiLogPath, visibleDelegationList };
8
310
  //# sourceMappingURL=tui.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;cA8qCM,QAAQ;EAAoB"}
1
+ {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;KAm5BY;;EAEV;;EAEA;;;;EAIA;;EAEA;EACA;;EAEA;;EAEA;EACA;EACA;;EAEA;;;;EAIA;;;;;;;;;;;iBAsBc,wBACd,aACA,oBACA,qBACC;;;;;;iBA8GmB,sBAAsB,cAAc,QAAQ;;;;;;;;iBAwC5C,yBAAyB,eAAe,QAAQ;;;;;;;;iBAWtD,sBACd;EAAS;EAAoB;eAC7B;;;;;;iBAYc,YAAY;;;iBAMZ,WAAW;;;;iBAOX,yBAAyB,GAAG,iBAAiB,GAAG;;;;;iBAWhD,sBACd,cAAc,mBACd,uBACC;;;iBAQa,WAAW;;;iBAcX,kBAAkB,OAAO,iBAAiB;;;KAO9C;iBAQI,mBAAmB,OAAO,kBAAkB;iBAS5C,wBAAwB,OAAO;;iBAoB/B,uBAAuB;;;;;;;iBAWvB,4BACd,mBAAmB,mBACnB,eAAe,wBACd;;KA2DS;EACV;EACA;EACA;EACA;EACA;EACA;IACE;IACA,QAAQ;IACR;IACA;IACA;MAAS;MAAgB;;;;;KAKjB;;EAEV;;EAEA;;EAEA;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;;KAIU;EACV;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBAac,wBACd,MAAM,oBACN,eACC;;;iBA+Ea,yBACd,KAAK,YAAY,sBACjB,MAAM,oBACN;;;iBA+Fc,sBACd,KAAK,YAAY,sBACjB;;;;;;iBAiBc,2BACd;EAAqB;EAAa;iBAClC,YAAY,uDACX;;;;;iBA2Ba,sBACd,KAAK,YAAY,sBACjB,gBAAgB,sBAChB;;;iBAWc,kBAAkB,MAAM,sBAAsB;;;;;;iBAqB9C,uBACd,eAAe,uBACf,aAAa,oBACZ;;;;;;;;;iBA8Da,iBAAiB,cAAc;;;;KAOnC;;EAEV;EACA;;IAEE;MAAU;;;IAEV;MAAU;QAAY;QAAe,SAAS;;;;;;;;;;iBASlC,wBAAwB,SAAS;;;;;;;;iBAmBjC,gBAAgB;EAAgB;IAAQ;;;;;;;iBASxC,kBAAkB;EAAkC;IAAQ;;;;KAKhE;;EAEV;;EAEA;;;EAGA;;;EAGA;EACA;IACE;IACA;;;;;;;iBAQY,mBAAmB;;;;;iBASnB,cAAc,OAAO;;;;;;;;;;;;;;iBAiBrB,4BACd,mBAAmB,mCACnB,aAAa,mBACb,eACC;;;;;;iBAwCa,4BACd;EAAS,YAAY,cAAc,SAAS;eAC5C;;;;;KA8KU;EACV,KAAK,YAAY;;EAEjB;;EAEA;;cAicI,QAAQ;EAAoB"}