@x-code-cli/core 0.1.11 → 0.2.1

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 (115) 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 +324 -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 +15 -5
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +13 -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 +21 -4
  72. package/dist/permissions/index.d.ts.map +1 -1
  73. package/dist/permissions/index.js +37 -3
  74. package/dist/permissions/index.js.map +1 -1
  75. package/dist/permissions/session-store.d.ts +60 -0
  76. package/dist/permissions/session-store.d.ts.map +1 -0
  77. package/dist/permissions/session-store.js +233 -0
  78. package/dist/permissions/session-store.js.map +1 -0
  79. package/dist/tools/ask-user.d.ts.map +1 -1
  80. package/dist/tools/ask-user.js +8 -6
  81. package/dist/tools/ask-user.js.map +1 -1
  82. package/dist/tools/enter-plan-mode.d.ts +25 -0
  83. package/dist/tools/enter-plan-mode.d.ts.map +1 -0
  84. package/dist/tools/enter-plan-mode.js +120 -0
  85. package/dist/tools/enter-plan-mode.js.map +1 -0
  86. package/dist/tools/exit-plan-mode.d.ts +13 -0
  87. package/dist/tools/exit-plan-mode.d.ts.map +1 -0
  88. package/dist/tools/exit-plan-mode.js +22 -0
  89. package/dist/tools/exit-plan-mode.js.map +1 -0
  90. package/dist/tools/grep.d.ts +1 -1
  91. package/dist/tools/index.d.ts +20 -4
  92. package/dist/tools/index.d.ts.map +1 -1
  93. package/dist/tools/index.js +7 -1
  94. package/dist/tools/index.js.map +1 -1
  95. package/dist/tools/save-knowledge.d.ts +2 -2
  96. package/dist/tools/shell-provider.d.ts +4 -0
  97. package/dist/tools/shell-provider.d.ts.map +1 -1
  98. package/dist/tools/shell-provider.js +2 -0
  99. package/dist/tools/shell-provider.js.map +1 -1
  100. package/dist/tools/task.d.ts +14 -0
  101. package/dist/tools/task.d.ts.map +1 -0
  102. package/dist/tools/task.js +95 -0
  103. package/dist/tools/task.js.map +1 -0
  104. package/dist/tools/todo-write.d.ts +21 -0
  105. package/dist/tools/todo-write.d.ts.map +1 -0
  106. package/dist/tools/todo-write.js +117 -0
  107. package/dist/tools/todo-write.js.map +1 -0
  108. package/dist/types/index.d.ts +104 -1
  109. package/dist/types/index.d.ts.map +1 -1
  110. package/dist/types/index.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/knowledge/session-usage.d.ts +0 -24
  113. package/dist/knowledge/session-usage.d.ts.map +0 -1
  114. package/dist/knowledge/session-usage.js +0 -86
  115. package/dist/knowledge/session-usage.js.map +0 -1
@@ -0,0 +1,60 @@
1
+ export interface AllowRule {
2
+ tool: string;
3
+ pattern: string;
4
+ type: 'exact' | 'prefix' | 'tool';
5
+ }
6
+ /**
7
+ * Extract a command prefix suitable for prefix-match rules.
8
+ * Returns `null` when no meaningful prefix can be derived.
9
+ *
10
+ * 'git commit -m "fix"' → 'git commit'
11
+ * 'pnpm run build' → 'pnpm run'
12
+ * 'npm install lodash' → 'npm install'
13
+ * 'NODE_ENV=prod npm run dev' → 'npm run'
14
+ * 'powershell -Command "Get-CimInstance ..."' → 'Get-CimInstance'
15
+ * 'powershell -Command "git status"' → 'git'
16
+ * 'ls -la' → null
17
+ * '' → null
18
+ */
19
+ export declare function extractCommandPrefix(command: string): string | null;
20
+ /**
21
+ * Generate the display label for the "don't ask again" option.
22
+ * Returns `null` when no meaningful rule can be suggested — the UI
23
+ * should hide the "don't ask again" option entirely in that case.
24
+ *
25
+ * Shell with prefix: `git commit:*`
26
+ * Shell without prefix: null (no safe rule to suggest)
27
+ * Write tools (writeFile, edit): `all edits this session` (session-only)
28
+ */
29
+ export declare function suggestRuleLabel(toolName: string, input: Record<string, unknown>): string | null;
30
+ /**
31
+ * Build the AllowRule for a "don't ask again" approval.
32
+ * Returns `null` when no meaningful rule can be built (shell command
33
+ * without derivable prefix) — caller should not save a rule.
34
+ *
35
+ * - Shell with derivable prefix (e.g. `git commit`) → prefix rule
36
+ * - Shell without prefix → null (UI should not offer this option)
37
+ * - writeFile / edit → tool-wide allow (session-only, not persisted)
38
+ *
39
+ * `persist` indicates whether the rule should be saved to disk.
40
+ * Write tools return persist=false (session-only, matching Claude Code).
41
+ */
42
+ export declare function buildAllowRule(toolName: string, input: Record<string, unknown>): {
43
+ rule: AllowRule;
44
+ persist: boolean;
45
+ } | null;
46
+ export declare function addSessionAllowRule(rule: AllowRule): void;
47
+ export declare function sessionRulesMatch(toolName: string, input: Record<string, unknown>): boolean;
48
+ export declare function clearSessionRules(): void;
49
+ /**
50
+ * Load persisted permission rules from `.x-code/local/permissions.json`
51
+ * into the in-memory store. Safe to call multiple times (deduplicates).
52
+ * Silently no-ops if the file doesn't exist or is malformed.
53
+ */
54
+ export declare function loadPersistedRules(cwd: string): void;
55
+ /**
56
+ * Persist a new rule to `.x-code/local/permissions.json`.
57
+ * Creates the file if it doesn't exist. Appends without duplicating.
58
+ */
59
+ export declare function persistRule(cwd: string, rule: AllowRule): void;
60
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/permissions/session-store.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CAClC;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAQhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAU7H;AA6ED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAE3F;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAoBpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAsB9D"}
@@ -0,0 +1,233 @@
1
+ // @x-code-cli/core — Permission memory with disk persistence.
2
+ //
3
+ // When a user approves a tool call with "don't ask again", the decision
4
+ // is stored as an AllowRule both in-memory AND on disk at
5
+ // `.x-code/local/permissions.json`. On next startup the persisted rules
6
+ // are loaded so approvals survive across sessions.
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { XCODE_DIR } from '../utils.js';
10
+ // Env-var assignment prefix: VAR=value (unquoted, safe chars only).
11
+ const ENV_VAR_RE = /^[A-Za-z_]\w*=[A-Za-z0-9_./:@-]*\s+/;
12
+ // Matches `powershell -Command "..."` or `powershell -c "..."` (case-insensitive).
13
+ const POWERSHELL_CMD_RE = /^powershell(?:\.exe)?\s+(?:-(?:Command|c)\s+)?["']/i;
14
+ // Extracts the first cmdlet or command name from inside quoted PowerShell.
15
+ // Handles Verb-Noun cmdlets (Get-Process) and plain commands (git, npm).
16
+ const PS_INNER_CMD_RE = /["']?\s*(?:&\s*)?([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+|[a-z][a-z0-9._-]*)/;
17
+ /**
18
+ * Extract a command prefix suitable for prefix-match rules.
19
+ * Returns `null` when no meaningful prefix can be derived.
20
+ *
21
+ * 'git commit -m "fix"' → 'git commit'
22
+ * 'pnpm run build' → 'pnpm run'
23
+ * 'npm install lodash' → 'npm install'
24
+ * 'NODE_ENV=prod npm run dev' → 'npm run'
25
+ * 'powershell -Command "Get-CimInstance ..."' → 'Get-CimInstance'
26
+ * 'powershell -Command "git status"' → 'git'
27
+ * 'ls -la' → null
28
+ * '' → null
29
+ */
30
+ export function extractCommandPrefix(command) {
31
+ let cmd = command.trim();
32
+ while (ENV_VAR_RE.test(cmd)) {
33
+ cmd = cmd.replace(ENV_VAR_RE, '');
34
+ }
35
+ // Handle `powershell -Command "..."`: extract the inner cmdlet/command.
36
+ if (POWERSHELL_CMD_RE.test(cmd)) {
37
+ const quoteStart = cmd.indexOf('"') !== -1 ? cmd.indexOf('"') : cmd.indexOf("'");
38
+ if (quoteStart !== -1) {
39
+ const inner = cmd.slice(quoteStart);
40
+ const m = PS_INNER_CMD_RE.exec(inner);
41
+ if (m?.[1])
42
+ return m[1];
43
+ }
44
+ return null;
45
+ }
46
+ const tokens = cmd.split(/\s+/).filter(Boolean);
47
+ if (tokens.length < 2)
48
+ return null;
49
+ const second = tokens[1];
50
+ if (/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(second)) {
51
+ return `${tokens[0]} ${second}`;
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Generate the display label for the "don't ask again" option.
57
+ * Returns `null` when no meaningful rule can be suggested — the UI
58
+ * should hide the "don't ask again" option entirely in that case.
59
+ *
60
+ * Shell with prefix: `git commit:*`
61
+ * Shell without prefix: null (no safe rule to suggest)
62
+ * Write tools (writeFile, edit): `all edits this session` (session-only)
63
+ */
64
+ export function suggestRuleLabel(toolName, input) {
65
+ if (toolName === 'shell') {
66
+ const cmd = input.command ?? '';
67
+ const prefix = extractCommandPrefix(cmd);
68
+ if (prefix)
69
+ return `${prefix}:*`;
70
+ return null;
71
+ }
72
+ return 'all edits this session';
73
+ }
74
+ /**
75
+ * Build the AllowRule for a "don't ask again" approval.
76
+ * Returns `null` when no meaningful rule can be built (shell command
77
+ * without derivable prefix) — caller should not save a rule.
78
+ *
79
+ * - Shell with derivable prefix (e.g. `git commit`) → prefix rule
80
+ * - Shell without prefix → null (UI should not offer this option)
81
+ * - writeFile / edit → tool-wide allow (session-only, not persisted)
82
+ *
83
+ * `persist` indicates whether the rule should be saved to disk.
84
+ * Write tools return persist=false (session-only, matching Claude Code).
85
+ */
86
+ export function buildAllowRule(toolName, input) {
87
+ if (toolName === 'shell') {
88
+ const cmd = input.command ?? '';
89
+ const prefix = extractCommandPrefix(cmd);
90
+ if (prefix) {
91
+ return { rule: { tool: toolName, pattern: prefix, type: 'prefix' }, persist: true };
92
+ }
93
+ return null;
94
+ }
95
+ return { rule: { tool: toolName, pattern: '*', type: 'tool' }, persist: false };
96
+ }
97
+ function stripEnvVars(command) {
98
+ let cmd = command.trim();
99
+ while (ENV_VAR_RE.test(cmd)) {
100
+ cmd = cmd.replace(ENV_VAR_RE, '');
101
+ }
102
+ return cmd.trim();
103
+ }
104
+ // ─── Serialization helpers ───
105
+ function ruleToString(rule) {
106
+ if (rule.type === 'tool')
107
+ return `${rule.tool}:*`;
108
+ if (rule.type === 'prefix')
109
+ return `${rule.tool}:${rule.pattern}:*`;
110
+ return `${rule.tool}:=${rule.pattern}`;
111
+ }
112
+ function parseRuleString(s) {
113
+ // tool:* → tool-wide
114
+ const toolWide = s.match(/^([^:]+):\*$/);
115
+ if (toolWide)
116
+ return { tool: toolWide[1], pattern: '*', type: 'tool' };
117
+ // tool:prefix:* → prefix match
118
+ const prefix = s.match(/^([^:]+):(.+):\*$/);
119
+ if (prefix)
120
+ return { tool: prefix[1], pattern: prefix[2], type: 'prefix' };
121
+ // tool:=exact → exact match
122
+ const exact = s.match(/^([^:]+):=(.+)$/);
123
+ if (exact)
124
+ return { tool: exact[1], pattern: exact[2], type: 'exact' };
125
+ return null;
126
+ }
127
+ function getPermissionsPath(cwd) {
128
+ return path.join(cwd, XCODE_DIR, 'local', 'permissions.json');
129
+ }
130
+ // ─── Store ───
131
+ class SessionPermissionStore {
132
+ rules = [];
133
+ addRule(rule) {
134
+ const exists = this.rules.some((r) => r.tool === rule.tool && r.pattern === rule.pattern && r.type === rule.type);
135
+ if (!exists)
136
+ this.rules.push(rule);
137
+ }
138
+ matches(toolName, input) {
139
+ for (const rule of this.rules) {
140
+ if (rule.tool !== toolName)
141
+ continue;
142
+ if (rule.type === 'tool')
143
+ return true;
144
+ if (toolName === 'shell') {
145
+ const cmd = input.command ?? '';
146
+ const prefix = extractCommandPrefix(cmd);
147
+ if (rule.type === 'exact' && stripEnvVars(cmd) === rule.pattern)
148
+ return true;
149
+ if (rule.type === 'prefix' && prefix) {
150
+ if (prefix === rule.pattern)
151
+ return true;
152
+ if (prefix.startsWith(rule.pattern + ' '))
153
+ return true;
154
+ }
155
+ }
156
+ }
157
+ return false;
158
+ }
159
+ clear() {
160
+ this.rules = [];
161
+ }
162
+ get size() {
163
+ return this.rules.length;
164
+ }
165
+ }
166
+ const store = new SessionPermissionStore();
167
+ export function addSessionAllowRule(rule) {
168
+ store.addRule(rule);
169
+ }
170
+ export function sessionRulesMatch(toolName, input) {
171
+ return store.matches(toolName, input);
172
+ }
173
+ export function clearSessionRules() {
174
+ store.clear();
175
+ }
176
+ // ─── Disk persistence ───
177
+ /**
178
+ * Load persisted permission rules from `.x-code/local/permissions.json`
179
+ * into the in-memory store. Safe to call multiple times (deduplicates).
180
+ * Silently no-ops if the file doesn't exist or is malformed.
181
+ */
182
+ export function loadPersistedRules(cwd) {
183
+ const filePath = getPermissionsPath(cwd);
184
+ let raw;
185
+ try {
186
+ raw = fs.readFileSync(filePath, 'utf-8');
187
+ }
188
+ catch {
189
+ return;
190
+ }
191
+ let data;
192
+ try {
193
+ data = JSON.parse(raw);
194
+ }
195
+ catch {
196
+ return;
197
+ }
198
+ if (!Array.isArray(data.allow))
199
+ return;
200
+ for (const entry of data.allow) {
201
+ if (typeof entry !== 'string')
202
+ continue;
203
+ const rule = parseRuleString(entry);
204
+ if (rule)
205
+ store.addRule(rule);
206
+ }
207
+ }
208
+ /**
209
+ * Persist a new rule to `.x-code/local/permissions.json`.
210
+ * Creates the file if it doesn't exist. Appends without duplicating.
211
+ */
212
+ export function persistRule(cwd, rule) {
213
+ const filePath = getPermissionsPath(cwd);
214
+ const ruleStr = ruleToString(rule);
215
+ let data = { allow: [] };
216
+ try {
217
+ const raw = fs.readFileSync(filePath, 'utf-8');
218
+ const parsed = JSON.parse(raw);
219
+ if (Array.isArray(parsed.allow)) {
220
+ data.allow = parsed.allow.filter((s) => typeof s === 'string');
221
+ }
222
+ }
223
+ catch {
224
+ // File doesn't exist or is malformed — start fresh.
225
+ }
226
+ if (data.allow.includes(ruleStr))
227
+ return;
228
+ data.allow.push(ruleStr);
229
+ const dir = path.dirname(filePath);
230
+ fs.mkdirSync(dir, { recursive: true });
231
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
232
+ }
233
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/permissions/session-store.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO;;;;;;SAiBlB,CAAA"}
1
+ {"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO;;;;;;SAqBlB,CAAA"}
@@ -2,17 +2,19 @@
2
2
  import { tool } from 'ai';
3
3
  import { z } from 'zod';
4
4
  export const askUser = tool({
5
- description: 'Ask the user a clarifying question with multiple-choice options. Use when you need user input to decide between approaches.',
5
+ description: 'Ask the user a clarifying question with multiple-choice options. Use when you need user input to decide between approaches. In **plan mode** this is also the primary "interview" tool — call it after every meaningful analysis or exploration to hand decision points back to the user with concrete next-step choices.',
6
6
  inputSchema: z.object({
7
- question: z.string().describe('The question to ask'),
7
+ question: z.string().describe('The question to ask. Markdown is rendered.'),
8
8
  options: z
9
9
  .array(z.object({
10
- label: z.string().describe('Option label (1-5 words)'),
11
- description: z.string().describe('What this option means'),
10
+ label: z.string().describe('Option label (1-8 words). Shown as the choice itself.'),
11
+ description: z
12
+ .string()
13
+ .describe('One-line tradeoff or scope hint shown beneath the label.'),
12
14
  }))
13
15
  .min(2)
14
- .max(4)
15
- .describe('Choices (an "Other" option is auto-appended)'),
16
+ .max(6)
17
+ .describe('Choices. DO NOT include an "Other"/freeform/custom-input option the UI auto-appends one as the last row, so adding your own creates a duplicate. 2-6 entries — for plan-mode interview menus 4-6 entries with both action options ("plan high-priority items") and meta options ("just discuss further") work best.'),
16
18
  }),
17
19
  // No execute — through callback to trigger UI rendering
18
20
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC;IAC1B,WAAW,EACT,6HAA6H;IAC/H,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QACpD,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YACtD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;SAC3D,CAAC,CACH;aACA,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,8CAA8C,CAAC;KAC5D,CAAC;IACF,wDAAwD;CACzD,CAAC,CAAA"}
1
+ {"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC;IAC1B,WAAW,EACT,2TAA2T;IAC7T,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QAC3E,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;YACnF,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,CAAC,0DAA0D,CAAC;SACxE,CAAC,CACH;aACA,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CACP,uTAAuT,CACxT;KACJ,CAAC;IACF,wDAAwD;CACzD,CAAC,CAAA"}
@@ -0,0 +1,25 @@
1
+ /** The tool description below is the heart of plan-mode auto-trigger
2
+ * behavior — it's what the model reads in the tool list and uses to
3
+ * decide whether to recommend plan mode. Ported (with naming
4
+ * adjustments for our tool surface) from Claude Code's
5
+ * `EnterPlanModeTool/prompt.ts` external-user prompt
6
+ * (`/d/res/claude-code/src/tools/EnterPlanModeTool/prompt.ts:16-99`).
7
+ *
8
+ * WHY THIS IS LONG: a one-line description ("use for complex tasks")
9
+ * produces a model that almost never calls the tool — it has no
10
+ * concrete trigger pattern to match against the user's request. CC's
11
+ * prompt deliberately includes 7 numbered criteria, multiple worked
12
+ * examples per criterion, and an explicit "PREFER plan mode unless
13
+ * simple" anchor — that's what gets the model to actually recommend
14
+ * plan mode for refactors, new features, architectural decisions,
15
+ * etc. The token cost (~600 tokens in tool list each turn) is what
16
+ * buys the auto-trigger behavior; without it plan mode is dead UX.
17
+ *
18
+ * No `execute` field — the side-effect (asking the user to confirm,
19
+ * mutating LoopState.permissionMode, invalidating the system-prompt
20
+ * cache) is handled manually in `processToolCalls`. Same pattern as
21
+ * `askUser`. */
22
+ export declare const enterPlanMode: import("ai").Tool<{
23
+ topic?: string | undefined;
24
+ }, never>;
25
+ //# sourceMappingURL=enter-plan-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter-plan-mode.d.ts","sourceRoot":"","sources":["../../src/tools/enter-plan-mode.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;iBAoBiB;AACjB,eAAO,MAAM,aAAa;;SAgGxB,CAAA"}
@@ -0,0 +1,120 @@
1
+ // @x-code-cli/core — enterPlanMode tool (mode switch, no execute — handled in agent loop)
2
+ import { tool } from 'ai';
3
+ import { z } from 'zod';
4
+ /** The tool description below is the heart of plan-mode auto-trigger
5
+ * behavior — it's what the model reads in the tool list and uses to
6
+ * decide whether to recommend plan mode. Ported (with naming
7
+ * adjustments for our tool surface) from Claude Code's
8
+ * `EnterPlanModeTool/prompt.ts` external-user prompt
9
+ * (`/d/res/claude-code/src/tools/EnterPlanModeTool/prompt.ts:16-99`).
10
+ *
11
+ * WHY THIS IS LONG: a one-line description ("use for complex tasks")
12
+ * produces a model that almost never calls the tool — it has no
13
+ * concrete trigger pattern to match against the user's request. CC's
14
+ * prompt deliberately includes 7 numbered criteria, multiple worked
15
+ * examples per criterion, and an explicit "PREFER plan mode unless
16
+ * simple" anchor — that's what gets the model to actually recommend
17
+ * plan mode for refactors, new features, architectural decisions,
18
+ * etc. The token cost (~600 tokens in tool list each turn) is what
19
+ * buys the auto-trigger behavior; without it plan mode is dead UX.
20
+ *
21
+ * No `execute` field — the side-effect (asking the user to confirm,
22
+ * mutating LoopState.permissionMode, invalidating the system-prompt
23
+ * cache) is handled manually in `processToolCalls`. Same pattern as
24
+ * `askUser`. */
25
+ export const enterPlanMode = tool({
26
+ description: `Use this tool proactively when you're about to start a non-trivial implementation task. Getting user sign-off on your approach before writing code prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore the codebase and design an implementation approach for user approval.
27
+
28
+ ## When to Use This Tool
29
+
30
+ **Prefer using enterPlanMode** for implementation tasks unless they're simple. Use it when ANY of these conditions apply:
31
+
32
+ 1. **New Feature Implementation**: Adding meaningful new functionality
33
+ - Example: "Add a logout button" - where should it go? What should happen on click?
34
+ - Example: "Add form validation" - what rules? What error messages?
35
+
36
+ 2. **Multiple Valid Approaches**: The task can be solved in several different ways
37
+ - Example: "Add caching to the API" - could use Redis, in-memory, file-based, etc.
38
+ - Example: "Improve performance" - many optimization strategies possible
39
+
40
+ 3. **Code Modifications**: Changes that affect existing behavior or structure
41
+ - Example: "Update the login flow" - what exactly should change?
42
+ - Example: "Refactor this component" - what's the target architecture?
43
+
44
+ 4. **Architectural Decisions**: The task requires choosing between patterns or technologies
45
+ - Example: "Add real-time updates" - WebSockets vs SSE vs polling
46
+ - Example: "Implement state management" - Redux vs Context vs custom solution
47
+
48
+ 5. **Multi-File Changes**: The task will likely touch more than 2-3 files
49
+ - Example: "Refactor the authentication system"
50
+ - Example: "Add a new API endpoint with tests"
51
+
52
+ 6. **Unclear Requirements**: You need to explore before understanding the full scope
53
+ - Example: "Make the app faster" - need to profile and identify bottlenecks
54
+ - Example: "Fix the bug in checkout" - need to investigate root cause
55
+
56
+ 7. **User Preferences Matter**: The implementation could reasonably go multiple ways
57
+ - If you would use askUser to clarify the approach, use enterPlanMode instead
58
+ - Plan mode lets you explore first, then present options with context
59
+
60
+ ## When NOT to Use This Tool
61
+
62
+ Only skip enterPlanMode for simple tasks:
63
+ - Single-line or few-line fixes (typos, obvious bugs, small tweaks)
64
+ - Adding a single function with clear requirements
65
+ - Tasks where the user has given very specific, detailed instructions
66
+ - Pure research / "what does X do" questions — just answer them directly
67
+
68
+ ## What Happens in Plan Mode
69
+
70
+ In plan mode, you'll:
71
+ 1. Thoroughly explore the codebase using readFile, glob, grep, and listDir
72
+ 2. Understand existing patterns and architecture
73
+ 3. Design an implementation approach
74
+ 4. Use askUser to clarify approaches with the user when needed
75
+ 5. Write the plan incrementally to a session-scoped plan file
76
+ 6. Exit plan mode with exitPlanMode when ready to implement
77
+
78
+ ## Examples
79
+
80
+ ### GOOD - Use enterPlanMode:
81
+ User: "Add user authentication to the app"
82
+ - Requires architectural decisions (session vs JWT, where to store tokens, middleware structure)
83
+
84
+ User: "Optimize the database queries"
85
+ - Multiple approaches possible, need to profile first, significant impact
86
+
87
+ User: "Implement dark mode"
88
+ - Architectural decision on theme system, affects many components
89
+
90
+ User: "Add a delete button to the user profile"
91
+ - Seems simple but involves: where to place it, confirmation dialog, API call, error handling, state updates
92
+
93
+ User: "Update the error handling in the API"
94
+ - Affects multiple files, user should approve the approach
95
+
96
+ ### BAD - Don't use enterPlanMode:
97
+ User: "Fix the typo in the README"
98
+ - Straightforward, no planning needed
99
+
100
+ User: "Add a console.log to debug this function"
101
+ - Simple, obvious implementation
102
+
103
+ User: "What files handle routing?"
104
+ - Research / Q&A task, not implementation — just answer
105
+
106
+ ## Important Notes
107
+
108
+ - This tool REQUIRES user approval — they must consent to entering plan mode (an approval dialog appears).
109
+ - If unsure whether to use it, err on the side of planning — it's better to get alignment upfront than to redo work.
110
+ - Do not call enterPlanMode if you are already in plan mode (check the system prompt; if you see plan-mode instructions you are already in it).`,
111
+ inputSchema: z.object({
112
+ topic: z
113
+ .string()
114
+ .min(1)
115
+ .max(60)
116
+ .optional()
117
+ .describe('STRONGLY RECOMMENDED. A 3-5 word English filename slug summarizing the user\'s task. Lowercase, hyphen-separated, no spaces or special chars. The plan file is named `<topic>-<YYYYMMDD-HHMMSS>.md` so this makes the file identifiable in `ls .x-code/plans/`. Translate non-English requests into English keywords (e.g. user asks "重构这个项目" → topic: "refactor-x-code-cli"; user asks "加 OAuth 登录" → topic: "add-oauth-login"). Omit only when you genuinely cannot summarize — the file then falls back to timestamp-only naming.'),
118
+ }),
119
+ });
120
+ //# sourceMappingURL=enter-plan-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter-plan-mode.js","sourceRoot":"","sources":["../../src/tools/enter-plan-mode.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;iBAoBiB;AACjB,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gJAoFiI;IAC9I,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,QAAQ,EAAE;aACV,QAAQ,CACP,sgBAAsgB,CACvgB;KACJ,CAAC;CACH,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ /** Present the plan to the user for approval. The user sees a Yes/No
2
+ * dialog with the plan body inline; on Yes, the session leaves plan mode
3
+ * and the next turn can write code; on No, the session stays in plan
4
+ * mode and the model is told to revise.
5
+ *
6
+ * No `execute` field — the dispatch path in `processToolCalls` reads the
7
+ * plan file, calls `callbacks.onPlanApprovalRequest(planText)`, and
8
+ * feeds the verdict back as a synthetic tool result so the model knows
9
+ * whether to proceed or iterate. */
10
+ export declare const exitPlanMode: import("ai").Tool<{
11
+ plan?: string | undefined;
12
+ }, never>;
13
+ //# sourceMappingURL=exit-plan-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exit-plan-mode.d.ts","sourceRoot":"","sources":["../../src/tools/exit-plan-mode.ts"],"names":[],"mappings":"AAKA;;;;;;;;qCAQqC;AACrC,eAAO,MAAM,YAAY;;SAWvB,CAAA"}
@@ -0,0 +1,22 @@
1
+ // @x-code-cli/core — exitPlanMode tool (user-approval gate, no execute — handled in agent loop)
2
+ import { tool } from 'ai';
3
+ import { z } from 'zod';
4
+ /** Present the plan to the user for approval. The user sees a Yes/No
5
+ * dialog with the plan body inline; on Yes, the session leaves plan mode
6
+ * and the next turn can write code; on No, the session stays in plan
7
+ * mode and the model is told to revise.
8
+ *
9
+ * No `execute` field — the dispatch path in `processToolCalls` reads the
10
+ * plan file, calls `callbacks.onPlanApprovalRequest(planText)`, and
11
+ * feeds the verdict back as a synthetic tool result so the model knows
12
+ * whether to proceed or iterate. */
13
+ export const exitPlanMode = tool({
14
+ description: 'Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval. This tool reads the plan from the file you wrote during planning — pass an optional `plan` parameter only if you want to override what is in the file. The user sees the plan content in an approval dialog and chooses Yes/No. The model cannot leave plan mode without user approval; if rejected, revise the plan file (using edit) and call this again. Do NOT use this for research / Q&A — only when the user has asked you to implement something and you have a complete plan written to the plan file. Do NOT use askUser to ask "is this plan okay?" — exitPlanMode is the only correct way to request plan approval.',
15
+ inputSchema: z.object({
16
+ plan: z
17
+ .string()
18
+ .optional()
19
+ .describe('Optional override for the plan body. By default the plan body comes from the plan file you wrote during planning — only pass this argument if you want to use different content (rare).'),
20
+ }),
21
+ });
22
+ //# sourceMappingURL=exit-plan-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exit-plan-mode.js","sourceRoot":"","sources":["../../src/tools/exit-plan-mode.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;qCAQqC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;IAC/B,WAAW,EACT,+tBAA+tB;IACjuB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,yLAAyL,CAC1L;KACJ,CAAC;CACH,CAAC,CAAA"}
@@ -1,7 +1,7 @@
1
1
  export declare const grep: import("ai").Tool<{
2
2
  pattern: string;
3
- path?: string | undefined;
4
3
  glob?: string | undefined;
4
+ path?: string | undefined;
5
5
  maxResults?: number | undefined;
6
6
  }, string>;
7
7
  //# sourceMappingURL=grep.d.ts.map
@@ -1,11 +1,14 @@
1
1
  import { askUser } from './ask-user.js';
2
2
  import { edit } from './edit.js';
3
+ import { enterPlanMode } from './enter-plan-mode.js';
4
+ import { exitPlanMode } from './exit-plan-mode.js';
3
5
  import { glob } from './glob.js';
4
6
  import { grep } from './grep.js';
5
7
  import { listDir } from './list-dir.js';
6
8
  import { readFile } from './read-file.js';
7
9
  import { saveKnowledge } from './save-knowledge.js';
8
10
  import { shell } from './shell.js';
11
+ import { todoWrite } from './todo-write.js';
9
12
  import { webFetch } from './web-fetch.js';
10
13
  import { webSearch } from './web-search.js';
11
14
  import { writeFile } from './write-file.js';
@@ -63,8 +66,8 @@ export declare const toolRegistry: {
63
66
  }, string>;
64
67
  grep: import("ai").Tool<{
65
68
  pattern: string;
66
- path?: string | undefined;
67
69
  glob?: string | undefined;
70
+ path?: string | undefined;
68
71
  maxResults?: number | undefined;
69
72
  }, string>;
70
73
  listDir: import("ai").Tool<{
@@ -86,14 +89,27 @@ export declare const toolRegistry: {
86
89
  question: string;
87
90
  }, never>;
88
91
  saveKnowledge: import("ai").Tool<{
89
- category: "user" | "feedback" | "project" | "reference";
92
+ category: "project" | "user" | "feedback" | "reference";
90
93
  action: "add" | "delete";
91
94
  key: string;
92
95
  fact: string;
93
- scope: "project" | "global";
96
+ scope: "global" | "project";
94
97
  }, string>;
98
+ enterPlanMode: import("ai").Tool<{
99
+ topic?: string | undefined;
100
+ }, never>;
101
+ exitPlanMode: import("ai").Tool<{
102
+ plan?: string | undefined;
103
+ }, never>;
104
+ todoWrite: import("ai").Tool<{
105
+ todos: {
106
+ content?: string | undefined;
107
+ status?: "pending" | "in_progress" | "completed" | undefined;
108
+ activeForm?: string | undefined;
109
+ }[];
110
+ }, never>;
95
111
  };
96
- export { readFile, writeFile, edit, shell, glob, grep, listDir, webSearch, webFetch, askUser, saveKnowledge, };
112
+ export { readFile, writeFile, edit, shell, glob, grep, listDir, webSearch, webFetch, askUser, saveKnowledge, enterPlanMode, exitPlanMode, todoWrite, };
97
113
  export { MAX_TOOL_RESULT_LINES, MAX_TOOL_RESULT_BYTES, MAX_AGGREGATE_TOOL_RESULT_BYTES, truncateToolResult, } from './truncate.js';
98
114
  export type { TruncateOptions } from './truncate.js';
99
115
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYxB,CAAA;AAED,OAAO,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,OAAO,EACP,aAAa,GACd,CAAA;AAED,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,OAAO,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,OAAO,EACP,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,GACV,CAAA;AAED,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}