@x-code-cli/core 0.1.10 → 0.2.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 (111) hide show
  1. package/dist/agent/api-errors.d.ts.map +1 -1
  2. package/dist/agent/api-errors.js +18 -0
  3. package/dist/agent/api-errors.js.map +1 -1
  4. package/dist/agent/diff.d.ts +35 -0
  5. package/dist/agent/diff.d.ts.map +1 -0
  6. package/dist/agent/diff.js +83 -0
  7. package/dist/agent/diff.js.map +1 -0
  8. package/dist/agent/loop-state.d.ts +45 -3
  9. package/dist/agent/loop-state.d.ts.map +1 -1
  10. package/dist/agent/loop-state.js +24 -3
  11. package/dist/agent/loop-state.js.map +1 -1
  12. package/dist/agent/loop.d.ts +10 -6
  13. package/dist/agent/loop.d.ts.map +1 -1
  14. package/dist/agent/loop.js +212 -30
  15. package/dist/agent/loop.js.map +1 -1
  16. package/dist/agent/plan-storage.d.ts +55 -0
  17. package/dist/agent/plan-storage.d.ts.map +1 -0
  18. package/dist/agent/plan-storage.js +156 -0
  19. package/dist/agent/plan-storage.js.map +1 -0
  20. package/dist/agent/session-store.d.ts +114 -0
  21. package/dist/agent/session-store.d.ts.map +1 -0
  22. package/dist/agent/session-store.js +415 -0
  23. package/dist/agent/session-store.js.map +1 -0
  24. package/dist/agent/sub-agents/built-in.d.ts +3 -0
  25. package/dist/agent/sub-agents/built-in.d.ts.map +1 -0
  26. package/dist/agent/sub-agents/built-in.js +98 -0
  27. package/dist/agent/sub-agents/built-in.js.map +1 -0
  28. package/dist/agent/sub-agents/index.d.ts +7 -0
  29. package/dist/agent/sub-agents/index.d.ts.map +1 -0
  30. package/dist/agent/sub-agents/index.js +5 -0
  31. package/dist/agent/sub-agents/index.js.map +1 -0
  32. package/dist/agent/sub-agents/loader.d.ts +5 -0
  33. package/dist/agent/sub-agents/loader.d.ts.map +1 -0
  34. package/dist/agent/sub-agents/loader.js +117 -0
  35. package/dist/agent/sub-agents/loader.js.map +1 -0
  36. package/dist/agent/sub-agents/registry.d.ts +14 -0
  37. package/dist/agent/sub-agents/registry.d.ts.map +1 -0
  38. package/dist/agent/sub-agents/registry.js +37 -0
  39. package/dist/agent/sub-agents/registry.js.map +1 -0
  40. package/dist/agent/sub-agents/runner.d.ts +26 -0
  41. package/dist/agent/sub-agents/runner.d.ts.map +1 -0
  42. package/dist/agent/sub-agents/runner.js +287 -0
  43. package/dist/agent/sub-agents/runner.js.map +1 -0
  44. package/dist/agent/sub-agents/types.d.ts +63 -0
  45. package/dist/agent/sub-agents/types.d.ts.map +1 -0
  46. package/dist/agent/sub-agents/types.js +2 -0
  47. package/dist/agent/sub-agents/types.js.map +1 -0
  48. package/dist/agent/system-prompt.d.ts +15 -0
  49. package/dist/agent/system-prompt.d.ts.map +1 -1
  50. package/dist/agent/system-prompt.js +161 -0
  51. package/dist/agent/system-prompt.js.map +1 -1
  52. package/dist/agent/tool-execution.d.ts +4 -3
  53. package/dist/agent/tool-execution.d.ts.map +1 -1
  54. package/dist/agent/tool-execution.js +316 -14
  55. package/dist/agent/tool-execution.js.map +1 -1
  56. package/dist/agent/tool-result-sanitize.d.ts +12 -0
  57. package/dist/agent/tool-result-sanitize.d.ts.map +1 -1
  58. package/dist/agent/tool-result-sanitize.js +70 -0
  59. package/dist/agent/tool-result-sanitize.js.map +1 -1
  60. package/dist/config/index.d.ts +6 -0
  61. package/dist/config/index.d.ts.map +1 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/index.d.ts +11 -5
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +10 -3
  66. package/dist/index.js.map +1 -1
  67. package/dist/knowledge/session.d.ts +4 -7
  68. package/dist/knowledge/session.d.ts.map +1 -1
  69. package/dist/knowledge/session.js +20 -55
  70. package/dist/knowledge/session.js.map +1 -1
  71. package/dist/permissions/index.d.ts +18 -3
  72. package/dist/permissions/index.d.ts.map +1 -1
  73. package/dist/permissions/index.js +20 -2
  74. package/dist/permissions/index.js.map +1 -1
  75. package/dist/tools/ask-user.d.ts.map +1 -1
  76. package/dist/tools/ask-user.js +8 -6
  77. package/dist/tools/ask-user.js.map +1 -1
  78. package/dist/tools/enter-plan-mode.d.ts +25 -0
  79. package/dist/tools/enter-plan-mode.d.ts.map +1 -0
  80. package/dist/tools/enter-plan-mode.js +120 -0
  81. package/dist/tools/enter-plan-mode.js.map +1 -0
  82. package/dist/tools/exit-plan-mode.d.ts +13 -0
  83. package/dist/tools/exit-plan-mode.d.ts.map +1 -0
  84. package/dist/tools/exit-plan-mode.js +22 -0
  85. package/dist/tools/exit-plan-mode.js.map +1 -0
  86. package/dist/tools/grep.d.ts +1 -1
  87. package/dist/tools/index.d.ts +20 -4
  88. package/dist/tools/index.d.ts.map +1 -1
  89. package/dist/tools/index.js +7 -1
  90. package/dist/tools/index.js.map +1 -1
  91. package/dist/tools/save-knowledge.d.ts +2 -2
  92. package/dist/tools/shell-provider.d.ts +4 -0
  93. package/dist/tools/shell-provider.d.ts.map +1 -1
  94. package/dist/tools/shell-provider.js +2 -0
  95. package/dist/tools/shell-provider.js.map +1 -1
  96. package/dist/tools/task.d.ts +14 -0
  97. package/dist/tools/task.d.ts.map +1 -0
  98. package/dist/tools/task.js +95 -0
  99. package/dist/tools/task.js.map +1 -0
  100. package/dist/tools/todo-write.d.ts +21 -0
  101. package/dist/tools/todo-write.d.ts.map +1 -0
  102. package/dist/tools/todo-write.js +117 -0
  103. package/dist/tools/todo-write.js.map +1 -0
  104. package/dist/types/index.d.ts +103 -0
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/types/index.js.map +1 -1
  107. package/package.json +1 -1
  108. package/dist/knowledge/session-usage.d.ts +0 -24
  109. package/dist/knowledge/session-usage.d.ts.map +0 -1
  110. package/dist/knowledge/session-usage.js +0 -86
  111. package/dist/knowledge/session-usage.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ // @x-code-cli/core — Plan-mode file storage
2
+ //
3
+ // Plans live in `.x-code/plans/<slug>-<YYYYMMDD-HHMMSS>.md` inside the
4
+ // user's project (NOT in the global `~/.x-code/`). This mirrors how
5
+ // `.x-code/sessions/` and `.x-code/memory/` are scoped: per-project,
6
+ // gitignored, never shared across repos. The slug-then-timestamp shape
7
+ // matches the legacy filenames already living under `.x-code/plans/`
8
+ // (e.g. `vue-3-vite-typescript-snake-game-20260420-102410.md`) — both
9
+ // human-skimmable in `ls` AND sortable by recency.
10
+ //
11
+ // Claude Code stores plans globally under `~/.claude/plans/{slug}.md`
12
+ // with random word-pair slugs (`brilliant-crystal.md`). We chose
13
+ // project-local + topic-derived slug on the user's request — easier to
14
+ // find later, and the plan stays with the repo it was written for.
15
+ import fs from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ import { generateText } from 'ai';
18
+ import { getThinkingProviderOptions } from '../providers/thinking.js';
19
+ import { debugLog, XCODE_DIR } from '../utils.js';
20
+ const PLANS_SUBDIR = 'plans';
21
+ const SLUG_MAX_LEN = 40;
22
+ /** Convert an arbitrary task description into a filesystem-safe,
23
+ * lower-case, hyphen-separated slug. Drops anything outside
24
+ * `[a-z0-9 -]` (so CJK / emoji / punctuation collapse to nothing —
25
+ * CJK-only tasks produce an empty slug, which is intentional and
26
+ * caught by callers' timestamp-only fallback). Length capped at
27
+ * SLUG_MAX_LEN cells so `ls` columns stay readable. Exported so
28
+ * session-usage filenames can mirror the same shape as plan files. */
29
+ export function slugify(text) {
30
+ return text
31
+ .toLowerCase()
32
+ .trim()
33
+ .replace(/[^a-z0-9\s-]+/g, '')
34
+ .replace(/\s+/g, '-')
35
+ .replace(/-+/g, '-')
36
+ .replace(/^-+|-+$/g, '')
37
+ .slice(0, SLUG_MAX_LEN)
38
+ .replace(/-+$/g, '');
39
+ }
40
+ /** Format a Date as `YYYYMMDD-HHMMSS`. Local time, no zone suffix —
41
+ * matches the legacy plan-file convention which is what the user is
42
+ * used to scanning visually. */
43
+ function formatTimestamp(d) {
44
+ const pad = (n) => String(n).padStart(2, '0');
45
+ return (`${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}` +
46
+ `-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`);
47
+ }
48
+ function plansDir() {
49
+ return path.join(process.cwd(), XCODE_DIR, PLANS_SUBDIR);
50
+ }
51
+ /** Build a fresh plan-file path from a task description (typically the
52
+ * user's most recent message). Pure function — no I/O — so callers
53
+ * can stash the path on LoopState before the file actually exists.
54
+ * Format: `<slug>-<timestamp>.md` (slug-only when timestamp could
55
+ * conflict, timestamp-only when the task text produces an empty
56
+ * slug). Pass `opts.slug` when the caller already has a precomputed
57
+ * slug (e.g. agentLoop's session-wide LLM-generated `taskSlug`) to
58
+ * skip the local slugify pass — important for non-ASCII task text
59
+ * where slugify would return empty. */
60
+ export function makePlanFilePath(taskText, opts) {
61
+ const slug = opts?.slug ?? slugify(taskText);
62
+ const ts = formatTimestamp(opts?.now ?? new Date());
63
+ const name = slug ? `${slug}-${ts}` : ts;
64
+ return path.join(plansDir(), `${name}.md`);
65
+ }
66
+ /** Min length of a locally-slugified result for the fast path to
67
+ * apply. Below this we assume the user's first message had little
68
+ * ASCII content (typical CJK-only message: 0; "fix bug": ≥6) and ask
69
+ * the model for an English summary instead of producing an unhelpful
70
+ * one-letter filename. */
71
+ const ASCII_FAST_PATH_MIN_LEN = 6;
72
+ /** Cap on raw user text sent to the slug model. The summary only
73
+ * needs the gist; a 5000-character paste would just waste input
74
+ * tokens. */
75
+ const TASK_TEXT_TRUNCATE = 500;
76
+ /** Hard cap on output tokens for the slug call. Sized for "2-4 short
77
+ * English words" (~10 visible tokens) PLUS a comfortable margin for
78
+ * reasoning models that emit hidden thinking tokens before any
79
+ * visible text. We disable thinking explicitly below where the
80
+ * provider supports it, but DeepSeek's `disabled` and Anthropic's
81
+ * `disabled` aren't always honored on every model id, so the budget
82
+ * has to survive a small amount of forced reasoning too. */
83
+ const SLUG_MAX_OUTPUT_TOKENS = 256;
84
+ /** Derive a human-skimmable filename slug for the session.
85
+ *
86
+ * Fast path: if `slugify(taskText)` already produces ≥6 chars (i.e.
87
+ * the user typed something English-y), return it directly — zero
88
+ * network, zero tokens. Covers the entire English-prompt user base.
89
+ *
90
+ * Slow path: for CJK-only / emoji-heavy / very short first messages
91
+ * where slugify returns empty or near-empty, make ONE isolated
92
+ * generateText call asking for 2-4 lowercase English words. No
93
+ * message history, no tools, no system context — just the user's
94
+ * raw text (truncated) and a strict instruction. Disables thinking
95
+ * on providers that support it so the small token budget isn't
96
+ * spent on hidden reasoning before any visible text appears.
97
+ *
98
+ * Returns '' on any failure (including abort). Callers treat empty
99
+ * as "fall back to timestamp-only naming", matching pre-existing
100
+ * behavior so adding this helper can't regress anyone. */
101
+ export async function generateTaskSlug(taskText, model, modelId, signal) {
102
+ const localSlug = slugify(taskText);
103
+ if (localSlug.length >= ASCII_FAST_PATH_MIN_LEN) {
104
+ debugLog('slug.fast-path', `len=${localSlug.length} slug="${localSlug}"`);
105
+ return localSlug;
106
+ }
107
+ debugLog('slug.llm-start', `taskTextLen=${taskText.length} modelId=${modelId}`);
108
+ try {
109
+ const { text, usage, finishReason } = await generateText({
110
+ model,
111
+ abortSignal: signal,
112
+ providerOptions: getThinkingProviderOptions(modelId, false),
113
+ system: 'You convert user task descriptions into short English filename slugs. ' +
114
+ 'Reply with ONLY 2 to 4 lowercase English words separated by spaces. ' +
115
+ 'No punctuation, no quotes, no explanation, no prefixes like "slug:". ' +
116
+ 'If the input is non-English, translate the gist into English first.',
117
+ prompt: taskText.slice(0, TASK_TEXT_TRUNCATE),
118
+ maxOutputTokens: SLUG_MAX_OUTPUT_TOKENS,
119
+ });
120
+ const slug = slugify(text);
121
+ debugLog('slug.llm-result', `finishReason=${finishReason} rawText="${(text ?? '').slice(0, 80)}" slug="${slug}" tokens=${usage?.outputTokens ?? '?'}`);
122
+ return slug;
123
+ }
124
+ catch (err) {
125
+ debugLog('slug.llm-error', err instanceof Error ? err.message : String(err));
126
+ return '';
127
+ }
128
+ }
129
+ /** Make sure the plan directory exists. Recursive mkdir so we don't have
130
+ * to also ensure `.x-code/` separately — first plan written in a fresh
131
+ * project gets the parent created automatically. */
132
+ export async function ensurePlanDir() {
133
+ await fs.mkdir(plansDir(), { recursive: true });
134
+ }
135
+ /** Read the plan body at `planPath`. Empty string when the file doesn't
136
+ * exist — exitPlanMode calls this to grab whatever the model has
137
+ * written so far, and "no plan written yet" is a valid (if unhelpful)
138
+ * state. */
139
+ export async function readPlan(planPath) {
140
+ try {
141
+ return await fs.readFile(planPath, 'utf-8');
142
+ }
143
+ catch {
144
+ return '';
145
+ }
146
+ }
147
+ /** Persist the plan body to `planPath`. Used by exitPlanMode when the
148
+ * model passes a `plan` override so the on-disk record matches what
149
+ * the user is approving. Returns the path it wrote to (always equal
150
+ * to the input). */
151
+ export async function writePlan(planPath, body) {
152
+ await ensurePlanDir();
153
+ await fs.writeFile(planPath, body, 'utf-8');
154
+ return planPath;
155
+ }
156
+ //# sourceMappingURL=plan-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-storage.js","sourceRoot":"","sources":["../../src/agent/plan-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import type { ModelMessage } from 'ai';
2
+ import type { PermissionMode, TokenUsage } from '../types/index.js';
3
+ import type { LoopState } from './loop-state.js';
4
+ /** Build the on-disk filename for a session. Same shape as plan files
5
+ * (`<slug>-<id>.<ext>`) so `ls .x-code/sessions/` and `ls .x-code/plans/`
6
+ * scan the same way. Empty slug (CJK-only first message) collapses to
7
+ * pure-timestamp naming, matching the plan-file fallback. */
8
+ export declare function getSessionFilePath(state: {
9
+ sessionId: string;
10
+ taskSlug: string;
11
+ }, cwd?: string): string;
12
+ /** Write the session header. Idempotent: if the file already exists (resume
13
+ * path), we skip — the original header is preserved so picker metadata
14
+ * stays stable across resumes. */
15
+ export declare function appendHeader(state: LoopState, modelId: string, firstPrompt: string, cwd?: string): Promise<void>;
16
+ /** Flush every message in `state.messages` past `state.persistedMessageCount`
17
+ * to the jsonl file. The diff-based design keeps the writer decoupled from
18
+ * the many places in the agent loop that mutate state.messages directly
19
+ * (collectTurnResponse, processToolCalls, length-finish nudge, etc.) — we
20
+ * catch them all by sweeping at turn boundaries.
21
+ *
22
+ * After deep / light compaction the in-memory array shrinks. Callers must
23
+ * call `markBoundaryAndReflush` (below) instead of this — that path writes
24
+ * a compact-boundary marker so the loader can correctly truncate-on-load
25
+ * and then re-appends the trimmed messages so post-boundary jsonl content
26
+ * matches the new in-memory state. */
27
+ export declare function flushPendingMessages(state: LoopState): Promise<void>;
28
+ /** Append a usage snapshot for the current turn. Called from the agent loop
29
+ * after `collectTurnResponse` accepts the provider's `usage` object. The
30
+ * picker reads only the LAST usage line (tail scan) to display per-session
31
+ * totals — no need to keep older snapshots around any more efficiently. */
32
+ export declare function appendUsage(state: LoopState, modelId: string): Promise<void>;
33
+ /** Mark a compaction event and re-flush the (just-shrunk) message array.
34
+ * After this returns, the jsonl post-last-boundary content equals
35
+ * `state.messages` exactly — `loadSession` reconstructs the same in-memory
36
+ * state on resume.
37
+ *
38
+ * Why we re-append instead of relying on the pre-boundary messages: our
39
+ * `compressMessages` keeps a `recent N` slice verbatim, but those slices
40
+ * were already persisted before the boundary; the loader's
41
+ * "everything-after-last-boundary wins" rule would otherwise drop them.
42
+ * Duplicating ~6 messages on disk is cheap and keeps the load logic
43
+ * trivial.
44
+ *
45
+ * Light compaction (loop-guard pruning) calls this with `summary=undefined`
46
+ * — the trimmed messages still need a boundary so the loader doesn't
47
+ * resurrect the dropped loop-guard pairs. */
48
+ export declare function markBoundaryAndReflush(state: LoopState, summary?: string): Promise<void>;
49
+ /** Append an `interrupted` marker. Purely informational — the loader
50
+ * ignores it for state reconstruction; the picker can show "interrupted"
51
+ * next to sessions that ended mid-turn so users know what they're
52
+ * resuming into. */
53
+ export declare function appendInterrupted(state: LoopState): Promise<void>;
54
+ export interface LoadedSession {
55
+ sessionId: string;
56
+ taskSlug: string;
57
+ startedAt: string;
58
+ modelId: string;
59
+ cwd: string;
60
+ gitBranch?: string;
61
+ firstPrompt: string;
62
+ messages: ModelMessage[];
63
+ tokenUsage: TokenUsage;
64
+ turnCount: number;
65
+ /** Path of the jsonl file so the agent loop can keep appending to the
66
+ * same file when the user resumes. */
67
+ filePath: string;
68
+ }
69
+ /** Walk a session jsonl and reconstruct a LoadedSession.
70
+ *
71
+ * Compact-boundary semantics (matches Claude Code): every time we see a
72
+ * `compact-boundary` line, the message accumulator is cleared. So the
73
+ * returned `messages` reflects only what's after the LAST boundary —
74
+ * which by construction equals the in-memory state at the point of
75
+ * compaction (see `markBoundaryAndReflush`).
76
+ *
77
+ * Trailing tool_call / tool_result orphans are trimmed (the next API
78
+ * request would otherwise reject the message array) — see
79
+ * `sanitizeMessageTail` for the exact rule. */
80
+ export declare function loadSession(filePath: string): Promise<LoadedSession | null>;
81
+ export interface SessionListEntry {
82
+ filePath: string;
83
+ sessionId: string;
84
+ taskSlug: string;
85
+ firstPrompt: string;
86
+ startedAt: string;
87
+ modelId: string;
88
+ /** File mtime in epoch milliseconds — sort key for the picker. */
89
+ mtime: number;
90
+ tokenUsage: TokenUsage | null;
91
+ }
92
+ /** Enumerate every session jsonl in the current project, newest first.
93
+ * Reads only the head (~8KB, for the header line) and tail (~4KB, for
94
+ * the last usage line) of each file — no full-file load — so the picker
95
+ * is responsive even with hundreds of historical sessions. Files
96
+ * without a parseable header are dropped silently. */
97
+ export declare function listSessions(cwd?: string): Promise<SessionListEntry[]>;
98
+ /** Pick the most recently modified session file in the current project, or
99
+ * null if none exist. Used by `xc --continue` / `-c` to skip the picker
100
+ * and resume the latest session unconditionally. */
101
+ export declare function pickLatestSession(cwd?: string): Promise<SessionListEntry | null>;
102
+ /** Stable identifier for a session in picker UI. We can't use the filename
103
+ * directly (it can collide visually when multiple sessions share a slug)
104
+ * and the sessionId alone isn't unique across renames — but the file path
105
+ * is, by definition. Hashed to keep the choice label compact. */
106
+ export declare function shortIdFor(filePath: string): string;
107
+ /** Build a LoopState seeded from a previously-saved session. The agent
108
+ * loop accepts `existingState` and will continue appending to the same
109
+ * jsonl file (filename derives from `sessionId` + `taskSlug`, both
110
+ * preserved here). `persistedMessageCount` is set to the loaded length
111
+ * so the very first flush after the next user submit only appends NEW
112
+ * messages — the loaded tail is already on disk. */
113
+ export declare function hydrateLoopState(loaded: LoadedSession, initialMode?: PermissionMode): LoopState;
114
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/agent/session-store.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAQhD;;;8DAG8D;AAC9D,wBAAgB,kBAAkB,CAChC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC9C,GAAG,GAAE,MAAsB,GAC1B,MAAM,CAGR;AA6ED;;mCAEmC;AACnC,wBAAsB,YAAY,CAChC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;;;;;;uCAUuC;AACvC,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB1E;AAED;;;4EAG4E;AAC5E,wBAAsB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWlF;AAED;;;;;;;;;;;;;;8CAc8C;AAC9C,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED;;;qBAGqB;AACrB,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvE;AAID,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB;2CACuC;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAWD;;;;;;;;;;gDAUgD;AAChD,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA+CjF;AAqDD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,UAAU,GAAG,IAAI,CAAA;CAC9B;AAED;;;;uDAIuD;AACvD,wBAAsB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuD3F;AAgBD;;qDAEqD;AACrD,wBAAsB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAGrG;AAED;;;kEAGkE;AAClE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;qDAKqD;AACrD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,GAAE,cAA0B,GAAG,SAAS,CAW1G"}