@canaryai/cli 0.1.14 → 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.
- package/dist/{chunk-2T64Z2NI.js → chunk-7R4YFGP6.js} +53 -2
- package/dist/chunk-7R4YFGP6.js.map +1 -0
- package/dist/chunk-DXJNFJ3A.js +64 -0
- package/dist/chunk-DXJNFJ3A.js.map +1 -0
- package/dist/{chunk-V7U52ISX.js → chunk-HOYYXZPV.js} +136 -131
- package/dist/chunk-HOYYXZPV.js.map +1 -0
- package/dist/{chunk-ROTCL5WO.js → chunk-TO66FC4R.js} +688 -479
- package/dist/chunk-TO66FC4R.js.map +1 -0
- package/dist/chunk-UEOXNF5X.js +371 -0
- package/dist/chunk-UEOXNF5X.js.map +1 -0
- package/dist/{feature-flag-ESPSOSKG.js → feature-flag-ZDLDYRSF.js} +15 -92
- package/dist/feature-flag-ZDLDYRSF.js.map +1 -0
- package/dist/index.js +17 -65
- package/dist/index.js.map +1 -1
- package/dist/{knobs-HKONHY55.js → knobs-3MKMOXIV.js} +19 -104
- package/dist/knobs-3MKMOXIV.js.map +1 -0
- package/dist/{local-browser-SYPTG6IQ.js → local-browser-GG5GUXDS.js} +3 -3
- package/dist/{mcp-TMD2R5Z6.js → mcp-AD67OLQM.js} +4 -4
- package/dist/{psql-6IFVXM3A.js → psql-IVAPNYZV.js} +2 -2
- package/dist/{redis-HZC32IEO.js → redis-LWY7L6AS.js} +2 -2
- package/dist/{release-WOD3DAX4.js → release-KQFCTAXA.js} +5 -35
- package/dist/release-KQFCTAXA.js.map +1 -0
- package/dist/runner/preload.js +7 -323
- package/dist/runner/preload.js.map +1 -1
- package/dist/test.js +5 -340
- package/dist/test.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-2T64Z2NI.js.map +0 -1
- package/dist/chunk-L26U3BST.js +0 -770
- package/dist/chunk-L26U3BST.js.map +0 -1
- package/dist/chunk-ROTCL5WO.js.map +0 -1
- package/dist/chunk-V7U52ISX.js.map +0 -1
- package/dist/feature-flag-ESPSOSKG.js.map +0 -1
- package/dist/knobs-HKONHY55.js.map +0 -1
- package/dist/release-WOD3DAX4.js.map +0 -1
- /package/dist/{local-browser-SYPTG6IQ.js.map → local-browser-GG5GUXDS.js.map} +0 -0
- /package/dist/{mcp-TMD2R5Z6.js.map → mcp-AD67OLQM.js.map} +0 -0
- /package/dist/{psql-6IFVXM3A.js.map → psql-IVAPNYZV.js.map} +0 -0
- /package/dist/{redis-HZC32IEO.js.map → redis-LWY7L6AS.js.map} +0 -0
package/dist/test.js
CHANGED
|
@@ -1,95 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
classifyFailure,
|
|
3
|
-
executeHealActions,
|
|
4
2
|
getEventLog,
|
|
5
3
|
loadCanaryConfig,
|
|
6
4
|
markPatched,
|
|
7
5
|
recordHealingEvent,
|
|
8
|
-
setEventLogPath
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
setEventLogPath,
|
|
7
|
+
wrapExpect,
|
|
8
|
+
wrapPage
|
|
9
|
+
} from "./chunk-TO66FC4R.js";
|
|
10
|
+
import "./chunk-DGUM43GV.js";
|
|
13
11
|
|
|
14
12
|
// src/test.ts
|
|
15
13
|
import * as playwright from "@playwright/test";
|
|
16
14
|
|
|
17
15
|
// src/runner/wrapper.ts
|
|
18
|
-
import { createRequire } from "module";
|
|
19
|
-
import fs from "fs";
|
|
20
|
-
var requireFn = typeof __require !== "undefined" ? __require : createRequire(import.meta.url);
|
|
21
|
-
function getTestContext() {
|
|
22
|
-
try {
|
|
23
|
-
const playwright2 = requireFn("@playwright/test");
|
|
24
|
-
if (playwright2?.test?.info) {
|
|
25
|
-
const info = playwright2.test.info();
|
|
26
|
-
if (info) {
|
|
27
|
-
return { testFile: info.file, testTitle: info.title };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
} catch {
|
|
31
|
-
}
|
|
32
|
-
return void 0;
|
|
33
|
-
}
|
|
34
|
-
function loadTestSource(filePath) {
|
|
35
|
-
if (!filePath) return void 0;
|
|
36
|
-
try {
|
|
37
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
38
|
-
} catch {
|
|
39
|
-
return void 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
var LOCATOR_ACTIONS = /* @__PURE__ */ new Set([
|
|
43
|
-
"click",
|
|
44
|
-
"dblclick",
|
|
45
|
-
"fill",
|
|
46
|
-
"check",
|
|
47
|
-
"uncheck",
|
|
48
|
-
"hover",
|
|
49
|
-
"press",
|
|
50
|
-
"type",
|
|
51
|
-
"selectOption",
|
|
52
|
-
"tap"
|
|
53
|
-
]);
|
|
54
|
-
var PAGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
55
|
-
"goto",
|
|
56
|
-
"click",
|
|
57
|
-
"dblclick",
|
|
58
|
-
"fill",
|
|
59
|
-
"check",
|
|
60
|
-
"uncheck",
|
|
61
|
-
"hover",
|
|
62
|
-
"press",
|
|
63
|
-
"type",
|
|
64
|
-
"selectOption",
|
|
65
|
-
"tap",
|
|
66
|
-
"waitForSelector"
|
|
67
|
-
]);
|
|
68
|
-
var LOCATOR_FACTORIES = /* @__PURE__ */ new Set([
|
|
69
|
-
"locator",
|
|
70
|
-
"getByRole",
|
|
71
|
-
"getByText",
|
|
72
|
-
"getByLabel",
|
|
73
|
-
"getByPlaceholder",
|
|
74
|
-
"getByAltText",
|
|
75
|
-
"getByTitle",
|
|
76
|
-
"getByTestId",
|
|
77
|
-
"frameLocator"
|
|
78
|
-
]);
|
|
79
|
-
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
80
|
-
"locator",
|
|
81
|
-
"first",
|
|
82
|
-
"last",
|
|
83
|
-
"nth",
|
|
84
|
-
"filter",
|
|
85
|
-
"getByRole",
|
|
86
|
-
"getByText",
|
|
87
|
-
"getByLabel",
|
|
88
|
-
"getByPlaceholder",
|
|
89
|
-
"getByAltText",
|
|
90
|
-
"getByTitle",
|
|
91
|
-
"getByTestId"
|
|
92
|
-
]);
|
|
93
16
|
function wrapPlaywright(real, options) {
|
|
94
17
|
if (real.__canaryPatched) return real;
|
|
95
18
|
const patchedTest = real.test.extend({
|
|
@@ -114,264 +37,6 @@ function wrapPlaywright(real, options) {
|
|
|
114
37
|
}
|
|
115
38
|
return patched2;
|
|
116
39
|
}
|
|
117
|
-
function wrapExpect(expectImpl, options) {
|
|
118
|
-
const wrapMatchers = (expectation, mode) => {
|
|
119
|
-
return new Proxy(expectation ?? {}, {
|
|
120
|
-
get(target, prop, receiver) {
|
|
121
|
-
const value = Reflect.get(target, prop, receiver);
|
|
122
|
-
if (typeof prop === "string" && typeof value === "function") {
|
|
123
|
-
return (...args) => runMatcherWithHealing({
|
|
124
|
-
matcher: value,
|
|
125
|
-
matcherName: prop,
|
|
126
|
-
args,
|
|
127
|
-
expectTarget: target,
|
|
128
|
-
mode,
|
|
129
|
-
debug: options.debug
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return value;
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
const proxy = new Proxy(expectImpl, {
|
|
137
|
-
apply(target, thisArg, argArray) {
|
|
138
|
-
const expectation = target.apply(thisArg, argArray);
|
|
139
|
-
return wrapMatchers(expectation, "hard");
|
|
140
|
-
},
|
|
141
|
-
get(target, prop, receiver) {
|
|
142
|
-
const value = Reflect.get(target, prop, receiver);
|
|
143
|
-
if (prop === "soft" && typeof value === "function") {
|
|
144
|
-
return (...args) => wrapMatchers(value.apply(target, args), "soft");
|
|
145
|
-
}
|
|
146
|
-
return value;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
return proxy;
|
|
150
|
-
}
|
|
151
|
-
function wrapPage(page, options) {
|
|
152
|
-
return new Proxy(page, {
|
|
153
|
-
get(target, prop, receiver) {
|
|
154
|
-
const value = Reflect.get(target, prop, receiver);
|
|
155
|
-
if (typeof prop === "string") {
|
|
156
|
-
if (LOCATOR_FACTORIES.has(prop) && typeof value === "function") {
|
|
157
|
-
return (...args) => {
|
|
158
|
-
const locator = value.apply(target, args);
|
|
159
|
-
return wrapLocator(locator, options, target);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
if (PAGE_ACTIONS.has(prop) && typeof value === "function") {
|
|
163
|
-
return async (...args) => {
|
|
164
|
-
return attemptWithHealing({
|
|
165
|
-
kind: "page",
|
|
166
|
-
action: prop,
|
|
167
|
-
target: safeTargetString(target),
|
|
168
|
-
locator: void 0,
|
|
169
|
-
page: target,
|
|
170
|
-
invoke: () => value.apply(target, args),
|
|
171
|
-
debug: options.debug
|
|
172
|
-
});
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return value;
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
function wrapLocator(locator, options, page) {
|
|
181
|
-
if (!locator || typeof locator !== "object") return locator;
|
|
182
|
-
return new Proxy(locator, {
|
|
183
|
-
get(target, prop, receiver) {
|
|
184
|
-
const value = Reflect.get(target, prop, receiver);
|
|
185
|
-
if (typeof prop === "string" && LOCATOR_ACTIONS.has(prop) && typeof value === "function") {
|
|
186
|
-
return async (...args) => {
|
|
187
|
-
return attemptWithHealing({
|
|
188
|
-
kind: "locator",
|
|
189
|
-
action: prop,
|
|
190
|
-
target: safeTargetString(target),
|
|
191
|
-
locator: target,
|
|
192
|
-
page,
|
|
193
|
-
invoke: () => value.apply(target, args),
|
|
194
|
-
debug: options.debug
|
|
195
|
-
});
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
if (typeof prop === "string" && LOCATOR_CHAIN_METHODS.has(prop) && typeof value === "function") {
|
|
199
|
-
return (...args) => wrapLocator(value.apply(target, args), options, page);
|
|
200
|
-
}
|
|
201
|
-
return value;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
async function attemptWithHealing(ctx) {
|
|
206
|
-
try {
|
|
207
|
-
return await Promise.resolve(ctx.invoke());
|
|
208
|
-
} catch (error) {
|
|
209
|
-
const failure = buildFailureContext(ctx.kind, ctx.action, ctx.target, error);
|
|
210
|
-
recordHealingEvent({ ...failure, healed: false });
|
|
211
|
-
const decision = classifyFailure(failure);
|
|
212
|
-
if (ctx.debug) {
|
|
213
|
-
console.log(
|
|
214
|
-
`[canary][debug] failure intercepted: kind=${ctx.kind} action=${ctx.action} reason=${decision.healable ? decision.reason ?? "healable" : decision.reason}`
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
if (!decision.healable) {
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
|
-
const testContext = getTestContext();
|
|
221
|
-
const outcome = await executeHealActions(decision, failure, {
|
|
222
|
-
kind: ctx.kind,
|
|
223
|
-
action: ctx.action,
|
|
224
|
-
target: ctx.target,
|
|
225
|
-
locator: isLocatorLike(ctx.locator) ? ctx.locator : void 0,
|
|
226
|
-
page: isPageLike(ctx.page) ? ctx.page : void 0,
|
|
227
|
-
testContext: {
|
|
228
|
-
testFile: testContext?.testFile,
|
|
229
|
-
testTitle: testContext?.testTitle,
|
|
230
|
-
testSource: loadTestSource(testContext?.testFile)
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
const summaryBase = {
|
|
234
|
-
...failure,
|
|
235
|
-
healed: false,
|
|
236
|
-
strategy: "agentic",
|
|
237
|
-
reason: outcome.reason ?? decision.reason,
|
|
238
|
-
actions: actionsToEventItems(outcome.actionsRun),
|
|
239
|
-
durationMs: outcome.durationMs,
|
|
240
|
-
mode: outcome.mode,
|
|
241
|
-
decision: decision.reason,
|
|
242
|
-
modelId: outcome.modelId,
|
|
243
|
-
summary: outcome.summary,
|
|
244
|
-
testFile: testContext?.testFile,
|
|
245
|
-
testTitle: testContext?.testTitle
|
|
246
|
-
};
|
|
247
|
-
if (outcome.healed) {
|
|
248
|
-
if (ctx.debug) {
|
|
249
|
-
console.log(`[canary][debug] healed via AI tool for action=${ctx.action}`);
|
|
250
|
-
}
|
|
251
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
252
|
-
return void 0;
|
|
253
|
-
}
|
|
254
|
-
if (outcome.shouldRetryOriginal) {
|
|
255
|
-
try {
|
|
256
|
-
const retried = await Promise.resolve(ctx.invoke());
|
|
257
|
-
if (ctx.debug) {
|
|
258
|
-
console.log(`[canary][debug] retry_original succeeded for action=${ctx.action}`);
|
|
259
|
-
}
|
|
260
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
261
|
-
return retried;
|
|
262
|
-
} catch (retryError) {
|
|
263
|
-
const retryInfo = errorInfo(retryError);
|
|
264
|
-
recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });
|
|
265
|
-
if (ctx.debug) {
|
|
266
|
-
console.log(
|
|
267
|
-
`[canary][debug] retry_original failed for action=${ctx.action}: ${retryInfo.message ?? retryError}`
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
throw retryError;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
recordHealingEvent(summaryBase);
|
|
274
|
-
throw error;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
async function runMatcherWithHealing(params) {
|
|
278
|
-
const { matcher, matcherName, args, expectTarget, mode, debug } = params;
|
|
279
|
-
const invoke = () => matcher.apply(expectTarget, args);
|
|
280
|
-
if (mode === "soft") {
|
|
281
|
-
return Promise.resolve(invoke());
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
return await Promise.resolve(invoke());
|
|
285
|
-
} catch (error) {
|
|
286
|
-
const target = stringifyTarget(args?.[0]);
|
|
287
|
-
const failure = buildFailureContext("expect", matcherName, target, error);
|
|
288
|
-
recordHealingEvent({ ...failure, healed: false });
|
|
289
|
-
const decision = classifyFailure(failure);
|
|
290
|
-
if (!decision.healable) {
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
const outcome = await executeHealActions(decision, failure, {
|
|
294
|
-
kind: "expect",
|
|
295
|
-
action: matcherName,
|
|
296
|
-
target,
|
|
297
|
-
locator: isLocatorLike(args?.[0]) ? args[0] : void 0
|
|
298
|
-
});
|
|
299
|
-
const testContextMatcher = getTestContext();
|
|
300
|
-
const summaryBase = {
|
|
301
|
-
...failure,
|
|
302
|
-
healed: false,
|
|
303
|
-
strategy: "agentic",
|
|
304
|
-
reason: outcome.reason ?? decision.reason,
|
|
305
|
-
actions: actionsToEventItems(outcome.actionsRun),
|
|
306
|
-
durationMs: outcome.durationMs,
|
|
307
|
-
mode: outcome.mode,
|
|
308
|
-
decision: decision.reason,
|
|
309
|
-
modelId: outcome.modelId,
|
|
310
|
-
summary: outcome.summary,
|
|
311
|
-
testFile: testContextMatcher?.testFile,
|
|
312
|
-
testTitle: testContextMatcher?.testTitle
|
|
313
|
-
};
|
|
314
|
-
if (outcome.shouldRetryOriginal) {
|
|
315
|
-
try {
|
|
316
|
-
const retried = await Promise.resolve(invoke());
|
|
317
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
318
|
-
return retried;
|
|
319
|
-
} catch (retryError) {
|
|
320
|
-
const retryInfo = errorInfo(retryError);
|
|
321
|
-
recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });
|
|
322
|
-
throw retryError;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
recordHealingEvent(summaryBase);
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function buildFailureContext(kind, action, target, error) {
|
|
330
|
-
const info = errorInfo(error);
|
|
331
|
-
return {
|
|
332
|
-
kind,
|
|
333
|
-
action,
|
|
334
|
-
target,
|
|
335
|
-
errorMessage: info.message,
|
|
336
|
-
errorName: info.name,
|
|
337
|
-
stack: info.stack
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function actionsToEventItems(actions) {
|
|
341
|
-
return actions.map((action) => ({ type: action }));
|
|
342
|
-
}
|
|
343
|
-
function errorInfo(error) {
|
|
344
|
-
if (error instanceof Error) {
|
|
345
|
-
return { message: error.message, name: error.name, stack: error.stack };
|
|
346
|
-
}
|
|
347
|
-
return { message: typeof error === "string" ? error : JSON.stringify(error) };
|
|
348
|
-
}
|
|
349
|
-
function isLocatorLike(candidate) {
|
|
350
|
-
return Boolean(candidate && typeof candidate === "object" && "scrollIntoViewIfNeeded" in candidate);
|
|
351
|
-
}
|
|
352
|
-
function isPageLike(candidate) {
|
|
353
|
-
return Boolean(candidate && typeof candidate === "object" && "waitForTimeout" in candidate);
|
|
354
|
-
}
|
|
355
|
-
function safeTargetString(target) {
|
|
356
|
-
try {
|
|
357
|
-
if (typeof target === "object" && target !== null && "toString" in target) {
|
|
358
|
-
const s = String(target.toString());
|
|
359
|
-
if (s && s !== "[object Object]") return s;
|
|
360
|
-
}
|
|
361
|
-
} catch {
|
|
362
|
-
}
|
|
363
|
-
return void 0;
|
|
364
|
-
}
|
|
365
|
-
function stringifyTarget(candidate) {
|
|
366
|
-
if (!candidate) return void 0;
|
|
367
|
-
if (typeof candidate === "string") return candidate;
|
|
368
|
-
if (typeof candidate === "object") {
|
|
369
|
-
if ("selector" in candidate && typeof candidate.selector === "string") {
|
|
370
|
-
return String(candidate.selector);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return safeTargetString(candidate);
|
|
374
|
-
}
|
|
375
40
|
|
|
376
41
|
// src/test.ts
|
|
377
42
|
var config = loadCanaryConfig();
|
package/dist/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test.ts","../src/runner/wrapper.ts"],"sourcesContent":["import * as playwright from \"@playwright/test\";\nimport { loadCanaryConfig } from \"./runner/config\";\nimport { getEventLog, markPatched, recordHealingEvent, setEventLogPath } from \"./runner/state\";\nimport { wrapPlaywright, type PlaywrightExports } from \"./runner/wrapper\";\n\nconst config = loadCanaryConfig();\nsetEventLogPath(config.eventLogPath);\ngetEventLog();\n\nconst patched = wrapPlaywright(playwright as unknown as PlaywrightExports, { debug: !!config.debug });\nmarkPatched();\n\nrecordHealingEvent({\n kind: \"unknown\",\n action: \"module_wrap_ready\",\n healed: false,\n});\n\nconst {\n test,\n expect,\n chromium,\n firefox,\n webkit,\n devices,\n request,\n selectors,\n defineConfig,\n} = patched as typeof import(\"@playwright/test\");\n\nexport { test, expect, chromium, firefox, webkit, devices, request, selectors, defineConfig };\nexport type * from \"@playwright/test\";\n","import { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport { classifyFailure, executeHealActions, type FailureContext } from \"./healer\";\nimport { recordHealingEvent } from \"./state\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst requireFn: any = typeof require !== \"undefined\" ? require : createRequire(import.meta.url);\n\nfunction getTestContext(): { testFile?: string; testTitle?: string } | undefined {\n try {\n const playwright = requireFn(\"@playwright/test\") as {\n test?: { info?: () => { file?: string; title?: string } };\n };\n if (playwright?.test?.info) {\n const info = playwright.test.info();\n if (info) {\n return { testFile: info.file, testTitle: info.title };\n }\n }\n } catch {\n // Not in a Playwright test context\n }\n return undefined;\n}\n\nfunction loadTestSource(filePath?: string): string | undefined {\n if (!filePath) return undefined;\n try {\n return fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\nexport type PlaywrightExports = {\n test: { extend: typeof import(\"@playwright/test\").test.extend };\n expect: typeof import(\"@playwright/test\").expect;\n [key: string]: unknown;\n};\n\ntype WrapOptions = {\n debug?: boolean;\n};\n\nconst LOCATOR_ACTIONS = new Set([\n \"click\",\n \"dblclick\",\n \"fill\",\n \"check\",\n \"uncheck\",\n \"hover\",\n \"press\",\n \"type\",\n \"selectOption\",\n \"tap\",\n]);\n\nconst PAGE_ACTIONS = new Set([\n \"goto\",\n \"click\",\n \"dblclick\",\n \"fill\",\n \"check\",\n \"uncheck\",\n \"hover\",\n \"press\",\n \"type\",\n \"selectOption\",\n \"tap\",\n \"waitForSelector\",\n]);\n\nconst LOCATOR_FACTORIES = new Set([\n \"locator\",\n \"getByRole\",\n \"getByText\",\n \"getByLabel\",\n \"getByPlaceholder\",\n \"getByAltText\",\n \"getByTitle\",\n \"getByTestId\",\n \"frameLocator\",\n]);\n\nconst LOCATOR_CHAIN_METHODS = new Set([\n \"locator\",\n \"first\",\n \"last\",\n \"nth\",\n \"filter\",\n \"getByRole\",\n \"getByText\",\n \"getByLabel\",\n \"getByPlaceholder\",\n \"getByAltText\",\n \"getByTitle\",\n \"getByTestId\",\n]);\n\nexport function wrapPlaywright(real: PlaywrightExports, options: WrapOptions): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const patchedTest = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: patchedTest,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (options.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright surface wrapped`);\n }\n return patched;\n}\n\nfunction wrapExpect(expectImpl: PlaywrightExports[\"expect\"], options: WrapOptions): PlaywrightExports[\"expect\"] {\n const wrapMatchers = (expectation: unknown, mode: \"hard\" | \"soft\") => {\n return new Proxy(expectation ?? {}, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop === \"string\" && typeof value === \"function\") {\n return (...args: unknown[]) =>\n runMatcherWithHealing({\n matcher: value as (...args: unknown[]) => unknown,\n matcherName: prop,\n args,\n expectTarget: target,\n mode,\n debug: options.debug,\n });\n }\n return value;\n },\n });\n };\n\n const proxy = new Proxy(expectImpl, {\n apply(target, thisArg, argArray) {\n const expectation = (target as (...args: unknown[]) => unknown).apply(thisArg, argArray as unknown[]);\n return wrapMatchers(expectation, \"hard\");\n },\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (prop === \"soft\" && typeof value === \"function\") {\n return (...args: unknown[]) => wrapMatchers((value as (...args: unknown[]) => unknown).apply(target, args), \"soft\");\n }\n return value;\n },\n });\n\n return proxy as PlaywrightExports[\"expect\"];\n}\n\nfunction wrapPage<PageType extends object>(page: PageType, options: WrapOptions): PageType {\n return new Proxy(page, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop === \"string\") {\n if (LOCATOR_FACTORIES.has(prop) && typeof value === \"function\") {\n return (...args: unknown[]) => {\n const locator = (value as (...args: unknown[]) => unknown).apply(target, args);\n return wrapLocator(locator, options, target);\n };\n }\n\n if (PAGE_ACTIONS.has(prop) && typeof value === \"function\") {\n return async (...args: unknown[]) => {\n return attemptWithHealing({\n kind: \"page\",\n action: prop,\n target: safeTargetString(target),\n locator: undefined,\n page: target as unknown,\n invoke: () => (value as (...args: unknown[]) => unknown).apply(target, args),\n debug: options.debug,\n });\n };\n }\n }\n\n return value;\n },\n });\n}\n\nfunction wrapLocator(locator: unknown, options: WrapOptions, page?: unknown): unknown {\n if (!locator || typeof locator !== \"object\") return locator;\n\n return new Proxy(locator, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop === \"string\" && LOCATOR_ACTIONS.has(prop) && typeof value === \"function\") {\n return async (...args: unknown[]) => {\n return attemptWithHealing({\n kind: \"locator\",\n action: prop,\n target: safeTargetString(target),\n locator: target as unknown,\n page: page,\n invoke: () => (value as (...args: unknown[]) => unknown).apply(target, args),\n debug: options.debug,\n });\n };\n }\n\n if (typeof prop === \"string\" && LOCATOR_CHAIN_METHODS.has(prop) && typeof value === \"function\") {\n return (...args: unknown[]) => wrapLocator((value as (...args: unknown[]) => unknown).apply(target, args), options, page);\n }\n\n return value;\n },\n });\n}\n\ntype AttemptContext = {\n kind: FailureContext[\"kind\"];\n action: string;\n target?: string;\n locator?: unknown;\n page?: unknown;\n invoke: () => unknown;\n debug?: boolean;\n};\n\nasync function attemptWithHealing<T>(ctx: AttemptContext): Promise<T> {\n try {\n return (await Promise.resolve(ctx.invoke())) as T;\n } catch (error) {\n const failure = buildFailureContext(ctx.kind, ctx.action, ctx.target, error);\n recordHealingEvent({ ...failure, healed: false });\n const decision = classifyFailure(failure);\n if (ctx.debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[canary][debug] failure intercepted: kind=${ctx.kind} action=${ctx.action} reason=${\n decision.healable ? decision.reason ?? \"healable\" : decision.reason\n }`\n );\n }\n if (!decision.healable) {\n throw error;\n }\n\n const testContext = getTestContext();\n const outcome = await executeHealActions(decision, failure, {\n kind: ctx.kind,\n action: ctx.action,\n target: ctx.target,\n locator: isLocatorLike(ctx.locator) ? ctx.locator : undefined,\n page: isPageLike(ctx.page) ? ctx.page : undefined,\n testContext: {\n testFile: testContext?.testFile,\n testTitle: testContext?.testTitle,\n testSource: loadTestSource(testContext?.testFile),\n },\n });\n\n const summaryBase = {\n ...failure,\n healed: false,\n strategy: 'agentic',\n reason: outcome.reason ?? decision.reason,\n actions: actionsToEventItems(outcome.actionsRun),\n durationMs: outcome.durationMs,\n mode: outcome.mode,\n decision: decision.reason,\n modelId: outcome.modelId,\n summary: outcome.summary,\n testFile: testContext?.testFile,\n testTitle: testContext?.testTitle,\n } as const;\n\n // If the agent marked healing complete,\n // consider it healed and return successfully without retrying the original\n if (outcome.healed) {\n if (ctx.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] healed via AI tool for action=${ctx.action}`);\n }\n recordHealingEvent({ ...summaryBase, healed: true });\n return undefined as T;\n }\n\n if (outcome.shouldRetryOriginal) {\n try {\n const retried = (await Promise.resolve(ctx.invoke())) as T;\n if (ctx.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] retry_original succeeded for action=${ctx.action}`);\n }\n recordHealingEvent({ ...summaryBase, healed: true });\n return retried;\n } catch (retryError) {\n const retryInfo = errorInfo(retryError);\n recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });\n if (ctx.debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[canary][debug] retry_original failed for action=${ctx.action}: ${retryInfo.message ?? retryError}`\n );\n }\n throw retryError;\n }\n }\n\n recordHealingEvent(summaryBase);\n throw error;\n }\n}\n\ntype MatcherContext = {\n matcher: (...args: unknown[]) => unknown;\n matcherName: string;\n args: unknown[];\n expectTarget: unknown;\n mode: \"hard\" | \"soft\";\n debug?: boolean;\n};\n\nasync function runMatcherWithHealing(params: MatcherContext): Promise<unknown> {\n const { matcher, matcherName, args, expectTarget, mode, debug } = params;\n\n const invoke = () => matcher.apply(expectTarget, args);\n\n // Skip healing for soft assertions to avoid altering semantics.\n if (mode === \"soft\") {\n return Promise.resolve(invoke());\n }\n\n try {\n return await Promise.resolve(invoke());\n } catch (error) {\n const target = stringifyTarget(args?.[0]);\n const failure = buildFailureContext(\"expect\", matcherName, target, error);\n recordHealingEvent({ ...failure, healed: false });\n const decision = classifyFailure(failure);\n if (!decision.healable) {\n throw error;\n }\n\n const outcome = await executeHealActions(decision, failure, {\n kind: \"expect\",\n action: matcherName,\n target,\n locator: isLocatorLike(args?.[0]) ? args[0] : undefined,\n });\n\n const testContextMatcher = getTestContext();\n const summaryBase = {\n ...failure,\n healed: false,\n strategy: 'agentic',\n reason: outcome.reason ?? decision.reason,\n actions: actionsToEventItems(outcome.actionsRun),\n durationMs: outcome.durationMs,\n mode: outcome.mode,\n decision: decision.reason,\n modelId: outcome.modelId,\n summary: outcome.summary,\n testFile: testContextMatcher?.testFile,\n testTitle: testContextMatcher?.testTitle,\n } as const;\n\n if (outcome.shouldRetryOriginal) {\n try {\n const retried = await Promise.resolve(invoke());\n recordHealingEvent({ ...summaryBase, healed: true });\n return retried;\n } catch (retryError) {\n const retryInfo = errorInfo(retryError);\n recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });\n throw retryError;\n }\n }\n\n recordHealingEvent(summaryBase);\n throw error;\n }\n}\n\nfunction buildFailureContext(\n kind: FailureContext[\"kind\"],\n action: string,\n target: string | undefined,\n error: unknown\n): FailureContext {\n const info = errorInfo(error);\n return {\n kind,\n action,\n target,\n errorMessage: info.message,\n errorName: info.name,\n stack: info.stack,\n };\n}\n\nfunction actionsToEventItems(actions: string[]): Array<{ type: string; detail?: string }> {\n return actions.map((action) => ({ type: action }));\n}\n\nfunction errorInfo(error: unknown): { message?: string; name?: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, stack: error.stack };\n }\n return { message: typeof error === \"string\" ? error : JSON.stringify(error) };\n}\n\nfunction isLocatorLike(candidate: unknown): candidate is { scrollIntoViewIfNeeded: () => Promise<unknown> } {\n return Boolean(candidate && typeof candidate === \"object\" && \"scrollIntoViewIfNeeded\" in (candidate as Record<string, unknown>));\n}\n\nfunction isPageLike(candidate: unknown): candidate is { waitForTimeout: (ms: number) => Promise<unknown> } {\n return Boolean(candidate && typeof candidate === \"object\" && \"waitForTimeout\" in (candidate as Record<string, unknown>));\n}\n\nfunction safeTargetString(target: unknown): string | undefined {\n try {\n if (typeof target === \"object\" && target !== null && \"toString\" in target) {\n const s = String((target as { toString: () => string }).toString());\n if (s && s !== \"[object Object]\") return s;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nfunction stringifyTarget(candidate: unknown): string | undefined {\n if (!candidate) return undefined;\n if (typeof candidate === \"string\") return candidate;\n if (typeof candidate === \"object\") {\n if (\n \"selector\" in (candidate as Record<string, unknown>) &&\n typeof (candidate as Record<string, unknown>).selector === \"string\"\n ) {\n return String((candidate as Record<string, unknown>).selector);\n }\n }\n return safeTargetString(candidate);\n}\n"],"mappings":"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","names":["playwright","patched"]}
|
|
1
|
+
{"version":3,"sources":["../src/test.ts","../src/runner/wrapper.ts"],"sourcesContent":["import * as playwright from \"@playwright/test\";\nimport { loadCanaryConfig } from \"./runner/config\";\nimport { getEventLog, markPatched, recordHealingEvent, setEventLogPath } from \"./runner/state\";\nimport { wrapPlaywright, type PlaywrightExports } from \"./runner/wrapper\";\n\nconst config = loadCanaryConfig();\nsetEventLogPath(config.eventLogPath);\ngetEventLog();\n\nconst patched = wrapPlaywright(playwright as unknown as PlaywrightExports, { debug: !!config.debug });\nmarkPatched();\n\nrecordHealingEvent({\n kind: \"unknown\",\n action: \"module_wrap_ready\",\n healed: false,\n});\n\nconst {\n test,\n expect,\n chromium,\n firefox,\n webkit,\n devices,\n request,\n selectors,\n defineConfig,\n} = patched as typeof import(\"@playwright/test\");\n\nexport { test, expect, chromium, firefox, webkit, devices, request, selectors, defineConfig };\nexport type * from \"@playwright/test\";\n","/**\n * Direct Playwright wrapper for the `canary/test` import path.\n *\n * Wraps `test`, `expect`, and the `page` fixture with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport { recordHealingEvent } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n type WrapOptions,\n} from \"./healing-helpers\";\n\nexport type { PlaywrightExports, WrapOptions };\n\nexport function wrapPlaywright(real: PlaywrightExports, options: WrapOptions): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const patchedTest = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: patchedTest,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (options.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright surface wrapped`);\n }\n return patched;\n}\n"],"mappings":";;;;;;;;;;;;AAAA,YAAY,gBAAgB;;;ACiBrB,SAAS,eAAe,MAAyB,SAAyC;AAC/F,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,cAAc,KAAK,KAAK,OAAO;AAAA,IACnC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAMA,WAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAACA,SAA0C,kBAAkB;AAC7D,MAAI,QAAQ,OAAO;AAEjB,YAAQ,IAAI,4CAA4C;AAAA,EAC1D;AACA,SAAOA;AACT;;;ADvCA,IAAM,SAAS,iBAAiB;AAChC,gBAAgB,OAAO,YAAY;AACnC,YAAY;AAEZ,IAAM,UAAU,eAAe,YAA4C,EAAE,OAAO,CAAC,CAAC,OAAO,MAAM,CAAC;AACpG,YAAY;AAEZ,mBAAmB;AAAA,EACjB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AACV,CAAC;AAED,IAAM;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAAI;","names":["patched"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canaryai/cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"ai": "^5.0.60",
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
47
47
|
"eventsource-parser": "^3.0.0",
|
|
48
|
+
"@chatsdet/browser-core": "workspace:*",
|
|
48
49
|
"zod": "^4.1.12"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype StoredAuth = {\n token?: string;\n apiUrl?: string;\n orgId?: string;\n orgName?: string;\n};\n\n/** Multi-profile auth file format */\ntype MultiProfileAuth = {\n profiles: Record<string, StoredAuth>;\n default: string;\n};\n\n/** Legacy flat format (single profile) */\ntype LegacyAuth = StoredAuth;\n\nconst AUTH_DIR = path.join(os.homedir(), \".config\", \"canary-cli\");\nconst AUTH_FILE = path.join(AUTH_DIR, \"auth.json\");\n\n/** Environment URL mappings shared across CLI commands */\nexport const ENV_URLS: Record<string, { api: string; app: string }> = {\n prod: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n production: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n dev: {\n api: \"https://api.dev.trycanary.ai\",\n app: \"https://app.dev.trycanary.ai\",\n },\n local: {\n api: \"http://localhost:3000\",\n app: \"http://localhost:5173\",\n },\n};\n\nfunction isMultiProfile(data: unknown): data is MultiProfileAuth {\n return typeof data === \"object\" && data !== null && \"profiles\" in data;\n}\n\n/** Read the raw auth file and normalize to multi-profile format */\nasync function readAuthFile(): Promise<MultiProfileAuth | null> {\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n return data;\n }\n\n // Legacy flat format — treat as the \"default\" profile\n const legacy = data as LegacyAuth;\n return {\n profiles: { default: legacy },\n default: \"default\",\n };\n } catch {\n return null;\n }\n}\n\nexport async function readStoredAuth(profile?: string): Promise<StoredAuth | null> {\n const auth = await readAuthFile();\n if (!auth) return null;\n\n const key = profile ?? auth.default;\n return auth.profiles[key] ?? null;\n}\n\nexport async function readStoredToken(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.token ?? null;\n}\n\nexport async function readStoredApiUrl(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.apiUrl ?? null;\n}\n\n/** Return all stored tokens across every profile */\nexport async function readAllStoredTokens(): Promise<string[]> {\n const auth = await readAuthFile();\n if (!auth) return [];\n return Object.values(auth.profiles)\n .map((p) => p.token)\n .filter((t): t is string => !!t);\n}\n\nexport async function saveAuth(auth: StoredAuth, profile?: string): Promise<string> {\n await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });\n\n // Read existing file (may be legacy or multi-profile)\n let existing: MultiProfileAuth;\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n existing = data;\n } else {\n // Migrate legacy flat format into profiles.default\n existing = {\n profiles: { default: data as LegacyAuth },\n default: \"default\",\n };\n }\n } catch {\n existing = { profiles: {}, default: \"default\" };\n }\n\n const key = profile ?? \"default\";\n existing.profiles[key] = auth;\n\n // Set default to this profile if no default exists yet\n if (!existing.profiles[existing.default]) {\n existing.default = key;\n }\n\n await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: \"utf8\", mode: 0o600 });\n return AUTH_FILE;\n}\n"],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAkBjB,IAAM,WAAW,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,YAAY;AAChE,IAAM,YAAY,KAAK,KAAK,UAAU,WAAW;AAG1C,IAAM,WAAyD;AAAA,EACpE,MAAM;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,YAAY;AAAA,IACV,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,KAAK;AAAA,IACH,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACF;AAEA,SAAS,eAAe,MAAyC;AAC/D,SAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,cAAc;AACpE;AAGA,eAAe,eAAiD;AAC9D,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,WAAW,MAAM;AACnD,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,eAAe,IAAI,GAAG;AACxB,aAAO;AAAA,IACT;AAGA,UAAM,SAAS;AACf,WAAO;AAAA,MACL,UAAU,EAAE,SAAS,OAAO;AAAA,MAC5B,SAAS;AAAA,IACX;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,eAAe,SAA8C;AACjF,QAAM,OAAO,MAAM,aAAa;AAChC,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,MAAM,WAAW,KAAK;AAC5B,SAAO,KAAK,SAAS,GAAG,KAAK;AAC/B;AAEA,eAAsB,gBAAgB,SAA0C;AAC9E,QAAM,OAAO,MAAM,eAAe,OAAO;AACzC,SAAO,MAAM,SAAS;AACxB;AAEA,eAAsB,iBAAiB,SAA0C;AAC/E,QAAM,OAAO,MAAM,eAAe,OAAO;AACzC,SAAO,MAAM,UAAU;AACzB;AAGA,eAAsB,sBAAyC;AAC7D,QAAM,OAAO,MAAM,aAAa;AAChC,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,SAAO,OAAO,OAAO,KAAK,QAAQ,EAC/B,IAAI,CAAC,MAAM,EAAE,KAAK,EAClB,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AACnC;AAEA,eAAsB,SAAS,MAAkB,SAAmC;AAClF,QAAM,GAAG,MAAM,UAAU,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAGzD,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,WAAW,MAAM;AACnD,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,eAAe,IAAI,GAAG;AACxB,iBAAW;AAAA,IACb,OAAO;AAEL,iBAAW;AAAA,QACT,UAAU,EAAE,SAAS,KAAmB;AAAA,QACxC,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,QAAQ;AACN,eAAW,EAAE,UAAU,CAAC,GAAG,SAAS,UAAU;AAAA,EAChD;AAEA,QAAM,MAAM,WAAW;AACvB,WAAS,SAAS,GAAG,IAAI;AAGzB,MAAI,CAAC,SAAS,SAAS,SAAS,OAAO,GAAG;AACxC,aAAS,UAAU;AAAA,EACrB;AAEA,QAAM,GAAG,UAAU,WAAW,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAClG,SAAO;AACT;","names":[]}
|