craftdriver 1.7.0 → 1.8.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.
- package/CHANGELOG.md +23 -11
- package/README.md +13 -4
- package/dist/cli/agentSession.d.ts +62 -0
- package/dist/cli/agentSession.d.ts.map +1 -0
- package/dist/cli/agentSession.js +100 -0
- package/dist/cli/agentSession.js.map +1 -0
- package/dist/cli/artifactPaths.d.ts +42 -0
- package/dist/cli/artifactPaths.d.ts.map +1 -0
- package/dist/cli/artifactPaths.js +144 -0
- package/dist/cli/artifactPaths.js.map +1 -0
- package/dist/cli/bounds.d.ts +88 -0
- package/dist/cli/bounds.d.ts.map +1 -0
- package/dist/cli/bounds.js +128 -0
- package/dist/cli/bounds.js.map +1 -0
- package/dist/cli/client.d.ts +3 -0
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +24 -8
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/daemon.d.ts +3 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +152 -39
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/defaults.d.ts +23 -3
- package/dist/cli/defaults.d.ts.map +1 -1
- package/dist/cli/defaults.js +50 -5
- package/dist/cli/defaults.js.map +1 -1
- package/dist/cli/dispatcher.d.ts +54 -11
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +851 -54
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +305 -56
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +14 -14
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +328 -180
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/journal.d.ts +153 -0
- package/dist/cli/journal.d.ts.map +1 -0
- package/dist/cli/journal.js +252 -0
- package/dist/cli/journal.js.map +1 -0
- package/dist/cli/lineReader.d.ts +33 -0
- package/dist/cli/lineReader.d.ts.map +1 -0
- package/dist/cli/lineReader.js +66 -0
- package/dist/cli/lineReader.js.map +1 -0
- package/dist/cli/locatorCandidates.d.ts +44 -0
- package/dist/cli/locatorCandidates.d.ts.map +1 -0
- package/dist/cli/locatorCandidates.js +190 -0
- package/dist/cli/locatorCandidates.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +39 -1
- package/dist/cli/mcp/artifacts.d.ts.map +1 -1
- package/dist/cli/mcp/artifacts.js +104 -9
- package/dist/cli/mcp/artifacts.js.map +1 -1
- package/dist/cli/mcp/bounds.d.ts +65 -0
- package/dist/cli/mcp/bounds.d.ts.map +1 -0
- package/dist/cli/mcp/bounds.js +124 -0
- package/dist/cli/mcp/bounds.js.map +1 -0
- package/dist/cli/mcp/params.d.ts +38 -0
- package/dist/cli/mcp/params.d.ts.map +1 -0
- package/dist/cli/mcp/params.js +174 -0
- package/dist/cli/mcp/params.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +94 -16
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +304 -150
- package/dist/cli/mcp/server.js.map +1 -1
- package/dist/cli/mcp/tools.d.ts +62 -17
- package/dist/cli/mcp/tools.d.ts.map +1 -1
- package/dist/cli/mcp/tools.js +373 -158
- package/dist/cli/mcp/tools.js.map +1 -1
- package/dist/cli/pageSemantics.d.ts +42 -0
- package/dist/cli/pageSemantics.d.ts.map +1 -0
- package/dist/cli/pageSemantics.js +194 -0
- package/dist/cli/pageSemantics.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +6 -18
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +689 -45
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/cli/protocol.d.ts +8 -0
- package/dist/cli/protocol.d.ts.map +1 -1
- package/dist/cli/sessionRegistry.d.ts +42 -0
- package/dist/cli/sessionRegistry.d.ts.map +1 -0
- package/dist/cli/sessionRegistry.js +99 -0
- package/dist/cli/sessionRegistry.js.map +1 -0
- package/dist/cli/snapshot.d.ts +100 -18
- package/dist/cli/snapshot.d.ts.map +1 -1
- package/dist/cli/snapshot.js +248 -102
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/stateStore.d.ts +50 -0
- package/dist/cli/stateStore.d.ts.map +1 -0
- package/dist/cli/stateStore.js +122 -0
- package/dist/cli/stateStore.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/bidi/index.d.ts +1 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +2 -2
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +3 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +7 -0
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +14 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +45 -7
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +30 -6
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +190 -45
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +7 -2
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +6 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +140 -30
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +54 -18
- package/dist/lib/browserContext.d.ts.map +1 -1
- package/dist/lib/browserContext.js +295 -92
- package/dist/lib/browserContext.js.map +1 -1
- package/dist/lib/by.d.ts +3 -3
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +7 -3
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/driver.d.ts +7 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +12 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/errors.d.ts +8 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +8 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/initScript.d.ts +9 -0
- package/dist/lib/initScript.d.ts.map +1 -0
- package/dist/lib/initScript.js +14 -0
- package/dist/lib/initScript.js.map +1 -0
- package/dist/lib/page.d.ts +22 -0
- package/dist/lib/page.d.ts.map +1 -1
- package/dist/lib/page.js +48 -0
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/secureFile.d.ts +8 -0
- package/dist/lib/secureFile.d.ts.map +1 -0
- package/dist/lib/secureFile.js +67 -0
- package/dist/lib/secureFile.js.map +1 -0
- package/dist/lib/sessionStateValidation.d.ts +35 -0
- package/dist/lib/sessionStateValidation.d.ts.map +1 -0
- package/dist/lib/sessionStateValidation.js +223 -0
- package/dist/lib/sessionStateValidation.js.map +1 -0
- package/dist/lib/tracing.d.ts +2 -1
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js +12 -1
- package/dist/lib/tracing.js.map +1 -1
- package/docs/agents.md +56 -61
- package/docs/api-reference.md +2 -1
- package/docs/browser-api.md +24 -4
- package/docs/browser-context.md +18 -12
- package/docs/cli.md +346 -26
- package/docs/error-codes.md +5 -4
- package/docs/getting-started.md +6 -1
- package/docs/index.md +1 -1
- package/docs/mcp.md +129 -62
- package/docs/public/examples/agent-actions.html +48 -0
- package/docs/public/examples/agent-debug.html +86 -0
- package/docs/public/examples/agent-late-mutation.html +18 -0
- package/docs/public/examples/agent-ref-shift.html +33 -0
- package/docs/recipes/login-once-reuse-session.md +32 -4
- package/docs/session-management.md +64 -3
- package/docs/why-craftdriver.md +1 -1
- package/package.json +3 -1
- package/skills/craftdriver/SKILL.md +59 -75
- package/skills/craftdriver/agents/openai.yaml +4 -0
- package/skills/craftdriver/cheatsheet.md +18 -4
- package/skills/craftdriver/cli.md +194 -68
- package/skills/craftdriver/patterns.md +14 -8
- package/skills/craftdriver/workflow.md +149 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded console and network history for a session.
|
|
3
|
+
*
|
|
4
|
+
* An agent's loop is act, then ask. That shape drives every decision here:
|
|
5
|
+
*
|
|
6
|
+
* - **Capture starts with the browser, not with the question.** Listeners are
|
|
7
|
+
* wired when the session's browser is first handed out, so an event that
|
|
8
|
+
* fires during the very first navigation is still answerable afterwards.
|
|
9
|
+
* - **Every entry has a monotonic `seq`.** "What happened since my last
|
|
10
|
+
* command" is the actual question, and answering it by re-reading the whole
|
|
11
|
+
* buffer and diffing is both wasteful and wrong once eviction starts.
|
|
12
|
+
* - **Waiting scans before it subscribes.** By the time an agent asks, the
|
|
13
|
+
* event it cares about has usually already arrived. A subscribe-only wait
|
|
14
|
+
* therefore times out on the common case rather than the rare one — see
|
|
15
|
+
* `tests/cli/journal-wait-race.test.ts`, which pins that behavior on the
|
|
16
|
+
* library's own `waitForConsole`.
|
|
17
|
+
* - **Eviction is counted, never silent.** A caller that lost entries is told
|
|
18
|
+
* how many, because "no errors in the log" and "the errors fell off the end
|
|
19
|
+
* of the log" must not look alike.
|
|
20
|
+
*
|
|
21
|
+
* Bounds are on both entries and bytes: a page in a render loop hits the entry
|
|
22
|
+
* cap, and a page logging serialized blobs hits the byte cap. Neither should
|
|
23
|
+
* be able to grow a long-lived daemon without limit.
|
|
24
|
+
*/
|
|
25
|
+
import type { Browser } from '../lib/browser.js';
|
|
26
|
+
/** Entries retained per session before the oldest are evicted. */
|
|
27
|
+
export declare const MAX_JOURNAL_ENTRIES = 500;
|
|
28
|
+
/** Total retained text per session. Sized to stay well under a daemon's RSS. */
|
|
29
|
+
export declare const MAX_JOURNAL_BYTES: number;
|
|
30
|
+
/** Longest single retained text. Longer ones are truncated with a marker. */
|
|
31
|
+
export declare const MAX_ENTRY_TEXT = 2000;
|
|
32
|
+
/**
|
|
33
|
+
* `error` covers both uncaught exceptions and `console.error` output.
|
|
34
|
+
*
|
|
35
|
+
* They are recorded as distinct entries — an exception is kind `error`, a
|
|
36
|
+
* `console.error` is kind `console` at level `error` — but a caller asking for
|
|
37
|
+
* "errors" means both. Splitting them at the filter made
|
|
38
|
+
* `logs --kind error` return nothing on a page that had loudly logged an
|
|
39
|
+
* error, which is exactly the silent-empty answer this module exists to avoid.
|
|
40
|
+
*/
|
|
41
|
+
export type JournalKind = 'console' | 'error' | 'request' | 'response';
|
|
42
|
+
interface BaseEntry {
|
|
43
|
+
/** Monotonic across the session; never reused, survives eviction. */
|
|
44
|
+
seq: number;
|
|
45
|
+
kind: JournalKind;
|
|
46
|
+
/** ISO 8601, from the browser's own event timestamp where available. */
|
|
47
|
+
time: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ConsoleEntry extends BaseEntry {
|
|
50
|
+
kind: 'console' | 'error';
|
|
51
|
+
level: string;
|
|
52
|
+
/** Console method (`log`, `warn`, …) or `exception` for a thrown error. */
|
|
53
|
+
method: string;
|
|
54
|
+
text: string;
|
|
55
|
+
/** Top stack frame only — enough to locate, too little to be a dump. */
|
|
56
|
+
origin?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface NetworkEntry extends BaseEntry {
|
|
59
|
+
kind: 'request' | 'response';
|
|
60
|
+
url: string;
|
|
61
|
+
method: string;
|
|
62
|
+
status?: number;
|
|
63
|
+
mimeType?: string;
|
|
64
|
+
/** Browsing context id when the event carries one. */
|
|
65
|
+
context?: string;
|
|
66
|
+
}
|
|
67
|
+
export type JournalEntry = ConsoleEntry | NetworkEntry;
|
|
68
|
+
export interface JournalQuery {
|
|
69
|
+
/** Return entries with `seq` strictly greater than this. */
|
|
70
|
+
since?: number;
|
|
71
|
+
kinds?: JournalKind[];
|
|
72
|
+
/**
|
|
73
|
+
* Console level filter (`error`, `warn`, …). Implies console entries: a
|
|
74
|
+
* network row has no level, and returning one alongside the warnings an
|
|
75
|
+
* agent asked for is noise, not a match.
|
|
76
|
+
*/
|
|
77
|
+
level?: string;
|
|
78
|
+
/** Substring match against text or URL. */
|
|
79
|
+
contains?: string;
|
|
80
|
+
limit?: number;
|
|
81
|
+
}
|
|
82
|
+
export interface JournalPage {
|
|
83
|
+
entries: JournalEntry[];
|
|
84
|
+
/** Highest `seq` issued so far — pass back as `since` next time. */
|
|
85
|
+
cursor: number;
|
|
86
|
+
/** Entries evicted over the session's lifetime. */
|
|
87
|
+
dropped: number;
|
|
88
|
+
/**
|
|
89
|
+
* Entries evicted that the caller had not yet read, i.e. those at or below
|
|
90
|
+
* the requested `since` boundary that no longer exist. Non-zero means this
|
|
91
|
+
* page has a hole in it.
|
|
92
|
+
*/
|
|
93
|
+
droppedBeforeCursor: number;
|
|
94
|
+
/** True when `limit` cut the result short. */
|
|
95
|
+
truncated: boolean;
|
|
96
|
+
}
|
|
97
|
+
export declare class SessionJournal {
|
|
98
|
+
private entries;
|
|
99
|
+
private bytes;
|
|
100
|
+
private nextSeq;
|
|
101
|
+
private droppedTotal;
|
|
102
|
+
/** Highest `seq` that has been evicted, so a reader can detect a hole. */
|
|
103
|
+
private highestDroppedSeq;
|
|
104
|
+
private waiters;
|
|
105
|
+
private detachers;
|
|
106
|
+
private attached;
|
|
107
|
+
/**
|
|
108
|
+
* The browser currently wired up.
|
|
109
|
+
*
|
|
110
|
+
* Identity matters, not just "have we attached once". `quit` closes the
|
|
111
|
+
* browser and the next command launches a new one; treating attach as a
|
|
112
|
+
* no-op because it had already run left the journal wired to a dead browser,
|
|
113
|
+
* silently capturing nothing while still answering queries with an empty
|
|
114
|
+
* list — which reads as "the page was quiet".
|
|
115
|
+
*/
|
|
116
|
+
private attachedTo;
|
|
117
|
+
/** Whether capture is live. False when the browser has no BiDi session. */
|
|
118
|
+
get isCapturing(): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Wire listeners onto a launched browser.
|
|
121
|
+
*
|
|
122
|
+
* Console and network history are BiDi-only. Rather than throwing — which
|
|
123
|
+
* would make an unrelated command fail on a Classic session — this records
|
|
124
|
+
* that capture is off, and the query commands report it.
|
|
125
|
+
*/
|
|
126
|
+
attach(browser: Browser): void;
|
|
127
|
+
/** Unsubscribe from the current browser without ending the journal. */
|
|
128
|
+
private releaseListeners;
|
|
129
|
+
private record;
|
|
130
|
+
private evict;
|
|
131
|
+
query(q?: JournalQuery): JournalPage;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve with the first entry matching `q`, scanning what is already
|
|
134
|
+
* buffered before subscribing.
|
|
135
|
+
*
|
|
136
|
+
* The scan-first order is the whole point: the event an agent asks about
|
|
137
|
+
* has usually already arrived. Returns null on timeout rather than
|
|
138
|
+
* throwing, so the caller decides whether absence is an error.
|
|
139
|
+
*/
|
|
140
|
+
waitFor(q: JournalQuery, timeoutMs: number): Promise<JournalEntry | null>;
|
|
141
|
+
/**
|
|
142
|
+
* Drop retained entries.
|
|
143
|
+
*
|
|
144
|
+
* The sequence counter deliberately survives, for the same reason snapshot
|
|
145
|
+
* refs do: a cursor an agent still holds must not start matching new
|
|
146
|
+
* entries after a clear.
|
|
147
|
+
*/
|
|
148
|
+
clear(): void;
|
|
149
|
+
/** Stop capturing and release listeners and pending waiters. */
|
|
150
|
+
detach(): void;
|
|
151
|
+
}
|
|
152
|
+
export {};
|
|
153
|
+
//# sourceMappingURL=journal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../src/cli/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/** Entries retained per session before the oldest are evicted. */
|
|
2
|
+
export const MAX_JOURNAL_ENTRIES = 500;
|
|
3
|
+
/** Total retained text per session. Sized to stay well under a daemon's RSS. */
|
|
4
|
+
export const MAX_JOURNAL_BYTES = 512 * 1024;
|
|
5
|
+
/** Longest single retained text. Longer ones are truncated with a marker. */
|
|
6
|
+
export const MAX_ENTRY_TEXT = 2_000;
|
|
7
|
+
function truncate(text) {
|
|
8
|
+
if (text.length <= MAX_ENTRY_TEXT)
|
|
9
|
+
return text;
|
|
10
|
+
return `${text.slice(0, MAX_ENTRY_TEXT)}… (${text.length - MAX_ENTRY_TEXT} more chars)`;
|
|
11
|
+
}
|
|
12
|
+
/** Approximate retained size of an entry. Exact byte accounting is not worth
|
|
13
|
+
* a serialization pass per event; this tracks the fields that actually grow. */
|
|
14
|
+
function entrySize(entry) {
|
|
15
|
+
return entryText(entry).length + 128;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The growing field of an entry, for filtering and size accounting.
|
|
19
|
+
*
|
|
20
|
+
* Narrowed on the property rather than on `kind`: both members carry a union
|
|
21
|
+
* of `kind` values, which is not a discriminant TypeScript can narrow through.
|
|
22
|
+
*/
|
|
23
|
+
function entryText(entry) {
|
|
24
|
+
return 'text' in entry ? entry.text : entry.url;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Does `entry` satisfy `q`?
|
|
28
|
+
*
|
|
29
|
+
* One implementation for both `query` and `waitFor`: they had the same
|
|
30
|
+
* predicate written twice, and a filter that behaves differently depending on
|
|
31
|
+
* whether you polled or waited would be a genuinely confusing bug.
|
|
32
|
+
*/
|
|
33
|
+
function matchesQuery(entry, q) {
|
|
34
|
+
if (entry.seq <= (q.since ?? 0))
|
|
35
|
+
return false;
|
|
36
|
+
if (q.kinds && q.kinds.length > 0) {
|
|
37
|
+
const wantsError = q.kinds.includes('error');
|
|
38
|
+
// `console.error` is kind `console`; a caller asking for errors wants it.
|
|
39
|
+
const isErrorish = entry.kind === 'error' || ('level' in entry && entry.level === 'error');
|
|
40
|
+
if (!q.kinds.includes(entry.kind) && !(wantsError && isErrorish))
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (q.level && (!('level' in entry) || entry.level !== q.level))
|
|
44
|
+
return false;
|
|
45
|
+
if (q.contains && !entryText(entry).toLowerCase().includes(q.contains.toLowerCase())) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
export class SessionJournal {
|
|
51
|
+
entries = [];
|
|
52
|
+
bytes = 0;
|
|
53
|
+
nextSeq = 1;
|
|
54
|
+
droppedTotal = 0;
|
|
55
|
+
/** Highest `seq` that has been evicted, so a reader can detect a hole. */
|
|
56
|
+
highestDroppedSeq = 0;
|
|
57
|
+
waiters = new Set();
|
|
58
|
+
detachers = [];
|
|
59
|
+
attached = false;
|
|
60
|
+
/**
|
|
61
|
+
* The browser currently wired up.
|
|
62
|
+
*
|
|
63
|
+
* Identity matters, not just "have we attached once". `quit` closes the
|
|
64
|
+
* browser and the next command launches a new one; treating attach as a
|
|
65
|
+
* no-op because it had already run left the journal wired to a dead browser,
|
|
66
|
+
* silently capturing nothing while still answering queries with an empty
|
|
67
|
+
* list — which reads as "the page was quiet".
|
|
68
|
+
*/
|
|
69
|
+
attachedTo = null;
|
|
70
|
+
/** Whether capture is live. False when the browser has no BiDi session. */
|
|
71
|
+
get isCapturing() {
|
|
72
|
+
return this.attached;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Wire listeners onto a launched browser.
|
|
76
|
+
*
|
|
77
|
+
* Console and network history are BiDi-only. Rather than throwing — which
|
|
78
|
+
* would make an unrelated command fail on a Classic session — this records
|
|
79
|
+
* that capture is off, and the query commands report it.
|
|
80
|
+
*/
|
|
81
|
+
attach(browser) {
|
|
82
|
+
// Same browser: already wired, and re-wiring would double-record.
|
|
83
|
+
if (this.attached && this.attachedTo === browser)
|
|
84
|
+
return;
|
|
85
|
+
// Different browser: drop the old listeners before wiring the new one.
|
|
86
|
+
if (this.attachedTo !== null)
|
|
87
|
+
this.releaseListeners();
|
|
88
|
+
try {
|
|
89
|
+
const logs = browser.logs;
|
|
90
|
+
this.detachers.push(logs.onLog((message) => {
|
|
91
|
+
const isError = message.type === 'javascript';
|
|
92
|
+
const frame = message.stackTrace?.[0];
|
|
93
|
+
this.record({
|
|
94
|
+
seq: 0,
|
|
95
|
+
kind: isError ? 'error' : 'console',
|
|
96
|
+
time: message.timestamp.toISOString(),
|
|
97
|
+
level: message.level,
|
|
98
|
+
method: message.type === 'console' ? message.method : 'exception',
|
|
99
|
+
text: truncate(message.text ?? ''),
|
|
100
|
+
...(frame ? { origin: `${frame.url}:${frame.lineNumber}` } : {}),
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// No BiDi: `browser.logs` throws. Leave capture off rather than
|
|
106
|
+
// failing the command that happened to launch the browser.
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const network = browser.network;
|
|
111
|
+
// Deliberately no bodies, cookies, or headers of any kind: they are
|
|
112
|
+
// bulky, rarely what an agent is asking about, and an Authorization
|
|
113
|
+
// header is pure liability in a log.
|
|
114
|
+
//
|
|
115
|
+
// URLs are recorded whole, query string included, so a token passed as
|
|
116
|
+
// a query parameter does appear here. That is deliberate: the URL is
|
|
117
|
+
// the one part an agent actually needs to identify a request, and it is
|
|
118
|
+
// returned only when the caller asks for logs. The journal itself keeps
|
|
119
|
+
// it in memory and never persists or transmits it independently, but a
|
|
120
|
+
// CLI/MCP caller can put that URL into an agent transcript. That is the
|
|
121
|
+
// real boundary; headers, cookies and bodies are never captured.
|
|
122
|
+
this.detachers.push(network.on('request', (req) => {
|
|
123
|
+
this.record({
|
|
124
|
+
seq: 0,
|
|
125
|
+
kind: 'request',
|
|
126
|
+
time: new Date().toISOString(),
|
|
127
|
+
url: truncate(req.url),
|
|
128
|
+
method: req.method,
|
|
129
|
+
...(req.context ? { context: req.context } : {}),
|
|
130
|
+
});
|
|
131
|
+
}));
|
|
132
|
+
this.detachers.push(network.on('response', (res) => {
|
|
133
|
+
this.record({
|
|
134
|
+
seq: 0,
|
|
135
|
+
kind: 'response',
|
|
136
|
+
time: new Date().toISOString(),
|
|
137
|
+
url: truncate(res.url),
|
|
138
|
+
// The response event carries no browsing context, so a response
|
|
139
|
+
// is attributed browser-wide rather than to a page. Requests do
|
|
140
|
+
// carry one; the asymmetry is the protocol's, not a lapse here.
|
|
141
|
+
method: res.request.method,
|
|
142
|
+
...(res.status !== undefined ? { status: res.status } : {}),
|
|
143
|
+
...(res.mimeType ? { mimeType: res.mimeType } : {}),
|
|
144
|
+
});
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Console capture may still be live; network simply stays off.
|
|
149
|
+
}
|
|
150
|
+
this.attached = true;
|
|
151
|
+
this.attachedTo = browser;
|
|
152
|
+
}
|
|
153
|
+
/** Unsubscribe from the current browser without ending the journal. */
|
|
154
|
+
releaseListeners() {
|
|
155
|
+
for (const off of this.detachers.splice(0)) {
|
|
156
|
+
try {
|
|
157
|
+
off();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* the browser is going away regardless */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
this.attached = false;
|
|
164
|
+
this.attachedTo = null;
|
|
165
|
+
}
|
|
166
|
+
record(entry) {
|
|
167
|
+
entry.seq = this.nextSeq++;
|
|
168
|
+
this.entries.push(entry);
|
|
169
|
+
this.bytes += entrySize(entry);
|
|
170
|
+
this.evict();
|
|
171
|
+
// Notify after eviction so a waiter can never be handed an entry that is
|
|
172
|
+
// already gone from the buffer.
|
|
173
|
+
for (const waiter of [...this.waiters]) {
|
|
174
|
+
if (waiter.match(entry)) {
|
|
175
|
+
this.waiters.delete(waiter);
|
|
176
|
+
waiter.resolve(entry);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
evict() {
|
|
181
|
+
while (this.entries.length > MAX_JOURNAL_ENTRIES ||
|
|
182
|
+
(this.bytes > MAX_JOURNAL_BYTES && this.entries.length > 1)) {
|
|
183
|
+
const gone = this.entries.shift();
|
|
184
|
+
if (!gone)
|
|
185
|
+
break;
|
|
186
|
+
this.bytes -= entrySize(gone);
|
|
187
|
+
this.droppedTotal++;
|
|
188
|
+
this.highestDroppedSeq = gone.seq;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
query(q = {}) {
|
|
192
|
+
const since = q.since ?? 0;
|
|
193
|
+
const matched = this.entries.filter((entry) => matchesQuery(entry, q));
|
|
194
|
+
const limit = q.limit ?? MAX_JOURNAL_ENTRIES;
|
|
195
|
+
// Keep the newest when cutting: an agent asking "what just happened"
|
|
196
|
+
// wants the tail, not the head.
|
|
197
|
+
const truncated = matched.length > limit;
|
|
198
|
+
const entries = truncated ? matched.slice(-limit) : matched;
|
|
199
|
+
return {
|
|
200
|
+
entries,
|
|
201
|
+
cursor: this.nextSeq - 1,
|
|
202
|
+
dropped: this.droppedTotal,
|
|
203
|
+
droppedBeforeCursor: this.highestDroppedSeq > since ? this.highestDroppedSeq - since : 0,
|
|
204
|
+
truncated,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Resolve with the first entry matching `q`, scanning what is already
|
|
209
|
+
* buffered before subscribing.
|
|
210
|
+
*
|
|
211
|
+
* The scan-first order is the whole point: the event an agent asks about
|
|
212
|
+
* has usually already arrived. Returns null on timeout rather than
|
|
213
|
+
* throwing, so the caller decides whether absence is an error.
|
|
214
|
+
*/
|
|
215
|
+
waitFor(q, timeoutMs) {
|
|
216
|
+
const existing = this.query({ ...q, limit: 1 });
|
|
217
|
+
if (existing.entries.length > 0)
|
|
218
|
+
return Promise.resolve(existing.entries[0]);
|
|
219
|
+
const match = (entry) => matchesQuery(entry, q);
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const waiter = { match, resolve: (entry) => { clearTimeout(timer); resolve(entry); } };
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
this.waiters.delete(waiter);
|
|
224
|
+
resolve(null);
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
// Do not hold the process open for a wait nobody is blocked on.
|
|
227
|
+
timer.unref?.();
|
|
228
|
+
this.waiters.add(waiter);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Drop retained entries.
|
|
233
|
+
*
|
|
234
|
+
* The sequence counter deliberately survives, for the same reason snapshot
|
|
235
|
+
* refs do: a cursor an agent still holds must not start matching new
|
|
236
|
+
* entries after a clear.
|
|
237
|
+
*/
|
|
238
|
+
clear() {
|
|
239
|
+
this.entries = [];
|
|
240
|
+
this.bytes = 0;
|
|
241
|
+
}
|
|
242
|
+
/** Stop capturing and release listeners and pending waiters. */
|
|
243
|
+
detach() {
|
|
244
|
+
this.releaseListeners();
|
|
245
|
+
// Release anyone blocked: a shutdown must not leave a caller waiting out
|
|
246
|
+
// the full timeout for an event that can no longer arrive.
|
|
247
|
+
for (const waiter of this.waiters)
|
|
248
|
+
waiter.resolve(null);
|
|
249
|
+
this.waiters.clear();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/cli/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded reader for the newline-delimited JSON both agent transports speak.
|
|
3
|
+
*
|
|
4
|
+
* Shared deliberately: MCP over stdio and the daemon over its Unix socket
|
|
5
|
+
* frame messages identically, and an unbounded accumulator is a
|
|
6
|
+
* resource-exhaustion hazard on either. A peer that writes bytes without a
|
|
7
|
+
* newline would otherwise grow the buffer until the process dies — worse in
|
|
8
|
+
* the daemon, which is long-lived and never exits to reclaim it.
|
|
9
|
+
*
|
|
10
|
+
* Over-long frames are dropped rather than truncated: half a JSON object is
|
|
11
|
+
* not a message, and parsing one would be worse than reporting the overrun.
|
|
12
|
+
*/
|
|
13
|
+
/** Maximum UTF-8 payload for one JSON line: 1 MiB, excluding `\n`. */
|
|
14
|
+
export declare const MAX_FRAME_BYTES: number;
|
|
15
|
+
export interface LineValue {
|
|
16
|
+
kind: 'line';
|
|
17
|
+
line: string;
|
|
18
|
+
}
|
|
19
|
+
export interface OversizedLine {
|
|
20
|
+
kind: 'oversized';
|
|
21
|
+
}
|
|
22
|
+
export type LineReaderEvent = LineValue | OversizedLine;
|
|
23
|
+
/** Incremental line reader that discards a frame as soon as it exceeds its cap. */
|
|
24
|
+
export declare class BoundedLineReader {
|
|
25
|
+
private readonly maxFrameBytes;
|
|
26
|
+
private buffer;
|
|
27
|
+
private bufferedBytes;
|
|
28
|
+
private discarding;
|
|
29
|
+
constructor(maxFrameBytes?: number);
|
|
30
|
+
push(chunk: string | Buffer): LineReaderEvent[];
|
|
31
|
+
private pushSegment;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=lineReader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineReader.d.ts","sourceRoot":"","sources":["../../src/cli/lineReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,sEAAsE;AACtE,eAAO,MAAM,eAAe,QAAc,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC;AAExD,mFAAmF;AACnF,qBAAa,iBAAiB;IAKhB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAJ1C,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAS;gBAEE,aAAa,GAAE,MAAwB;IAEpE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,eAAe,EAAE;IAmB/C,OAAO,CAAC,WAAW;CA4BpB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded reader for the newline-delimited JSON both agent transports speak.
|
|
3
|
+
*
|
|
4
|
+
* Shared deliberately: MCP over stdio and the daemon over its Unix socket
|
|
5
|
+
* frame messages identically, and an unbounded accumulator is a
|
|
6
|
+
* resource-exhaustion hazard on either. A peer that writes bytes without a
|
|
7
|
+
* newline would otherwise grow the buffer until the process dies — worse in
|
|
8
|
+
* the daemon, which is long-lived and never exits to reclaim it.
|
|
9
|
+
*
|
|
10
|
+
* Over-long frames are dropped rather than truncated: half a JSON object is
|
|
11
|
+
* not a message, and parsing one would be worse than reporting the overrun.
|
|
12
|
+
*/
|
|
13
|
+
/** Maximum UTF-8 payload for one JSON line: 1 MiB, excluding `\n`. */
|
|
14
|
+
export const MAX_FRAME_BYTES = 1024 * 1024;
|
|
15
|
+
/** Incremental line reader that discards a frame as soon as it exceeds its cap. */
|
|
16
|
+
export class BoundedLineReader {
|
|
17
|
+
maxFrameBytes;
|
|
18
|
+
buffer = '';
|
|
19
|
+
bufferedBytes = 0;
|
|
20
|
+
discarding = false;
|
|
21
|
+
constructor(maxFrameBytes = MAX_FRAME_BYTES) {
|
|
22
|
+
this.maxFrameBytes = maxFrameBytes;
|
|
23
|
+
}
|
|
24
|
+
push(chunk) {
|
|
25
|
+
const value = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
26
|
+
const events = [];
|
|
27
|
+
let offset = 0;
|
|
28
|
+
while (offset <= value.length) {
|
|
29
|
+
const newline = value.indexOf('\n', offset);
|
|
30
|
+
const terminated = newline !== -1;
|
|
31
|
+
const end = terminated ? newline : value.length;
|
|
32
|
+
const segment = value.slice(offset, end);
|
|
33
|
+
this.pushSegment(segment, terminated, events);
|
|
34
|
+
if (!terminated)
|
|
35
|
+
break;
|
|
36
|
+
offset = newline + 1;
|
|
37
|
+
if (offset === value.length)
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
return events;
|
|
41
|
+
}
|
|
42
|
+
pushSegment(segment, terminated, events) {
|
|
43
|
+
if (this.discarding) {
|
|
44
|
+
// Still swallowing the tail of an over-long frame; resume at its newline.
|
|
45
|
+
if (terminated)
|
|
46
|
+
this.discarding = false;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const segmentBytes = Buffer.byteLength(segment, 'utf8');
|
|
50
|
+
if (this.bufferedBytes + segmentBytes > this.maxFrameBytes) {
|
|
51
|
+
this.buffer = '';
|
|
52
|
+
this.bufferedBytes = 0;
|
|
53
|
+
this.discarding = !terminated;
|
|
54
|
+
events.push({ kind: 'oversized' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.buffer += segment;
|
|
58
|
+
this.bufferedBytes += segmentBytes;
|
|
59
|
+
if (!terminated)
|
|
60
|
+
return;
|
|
61
|
+
events.push({ kind: 'line', line: this.buffer });
|
|
62
|
+
this.buffer = '';
|
|
63
|
+
this.bufferedBytes = 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=lineReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineReader.js","sourceRoot":"","sources":["../../src/cli/lineReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;AAa3C,mFAAmF;AACnF,MAAM,OAAO,iBAAiB;IAKC;IAJrB,MAAM,GAAG,EAAE,CAAC;IACZ,aAAa,GAAG,CAAC,CAAC;IAClB,UAAU,GAAG,KAAK,CAAC;IAE3B,YAA6B,gBAAwB,eAAe;QAAvC,kBAAa,GAAb,aAAa,CAA0B;IAAG,CAAC;IAExE,IAAI,CAAC,KAAsB;QACzB,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzE,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,OAAO,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,UAAU,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC;YAClC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACzC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,UAAU;gBAAE,MAAM;YACvB,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC;YACrB,IAAI,MAAM,KAAK,KAAK,CAAC,MAAM;gBAAE,MAAM;QACrC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,WAAW,CACjB,OAAe,EACf,UAAmB,EACnB,MAAyB;QAEzB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,0EAA0E;YAC1E,IAAI,UAAU;gBAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxC,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,aAAa,GAAG,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3D,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;YACvB,IAAI,CAAC,UAAU,GAAG,CAAC,UAAU,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,IAAI,YAAY,CAAC;QACnC,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable locator candidates for an element the agent found by ref.
|
|
3
|
+
*
|
|
4
|
+
* Refs (`e7`) are exploration-only: they are meaningful for one document
|
|
5
|
+
* in one session and must never reach a committed test. But an agent that
|
|
6
|
+
* has just inspected an element still needs to *write* a selector for it,
|
|
7
|
+
* and guessing from a snapshot line is how brittle tests get born.
|
|
8
|
+
*
|
|
9
|
+
* So: take an element, read the evidence the live page actually offers
|
|
10
|
+
* (role, accessible name, label, test id, text, stable attributes), build
|
|
11
|
+
* candidates in durability order, and then — the part that matters —
|
|
12
|
+
* re-resolve every candidate against the current document and report
|
|
13
|
+
* whether it is unique, ambiguous, or missing.
|
|
14
|
+
*
|
|
15
|
+
* A candidate is only ever reported as `unique` when it resolves to
|
|
16
|
+
* exactly one element *and that element is the one asked about*. There is
|
|
17
|
+
* no semantic guessing and no runtime self-healing here: if the page
|
|
18
|
+
* offers nothing durable, that is the honest answer, and the agent should
|
|
19
|
+
* ask for a test id rather than commit a selector that will rot.
|
|
20
|
+
*/
|
|
21
|
+
import type { Browser } from '../lib/browser.js';
|
|
22
|
+
import type { By } from '../lib/by.js';
|
|
23
|
+
export type CandidateKind = 'role' | 'label' | 'testid' | 'text' | 'css';
|
|
24
|
+
export type CandidateStatus = 'unique' | 'ambiguous' | 'missing';
|
|
25
|
+
export interface LocatorCandidate {
|
|
26
|
+
/** CLI/selector form, e.g. `role=button[name=Pay now]`. */
|
|
27
|
+
selector: string;
|
|
28
|
+
/** craftdriver test code for the same target. */
|
|
29
|
+
code: string;
|
|
30
|
+
kind: CandidateKind;
|
|
31
|
+
/** Live re-resolution result against the current document. */
|
|
32
|
+
status: CandidateStatus;
|
|
33
|
+
/** How many elements the candidate matched. */
|
|
34
|
+
matches: number;
|
|
35
|
+
}
|
|
36
|
+
export interface LocatorReport {
|
|
37
|
+
target: string;
|
|
38
|
+
candidates: LocatorCandidate[];
|
|
39
|
+
/** First candidate that uniquely resolved to the target, if any. */
|
|
40
|
+
best: string | null;
|
|
41
|
+
note?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function locatorCandidates(browser: Browser, by: By, limit?: number): Promise<LocatorReport>;
|
|
44
|
+
//# sourceMappingURL=locatorCandidates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locatorCandidates.d.ts","sourceRoot":"","sources":["../../src/cli/locatorCandidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AASvC,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,8DAA8D;IAC9D,MAAM,EAAE,eAAe,CAAC;IACxB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAsCD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,EAAE,EACN,KAAK,SAAI,GACR,OAAO,CAAC,aAAa,CAAC,CAsDxB"}
|