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.
Files changed (175) hide show
  1. package/CHANGELOG.md +23 -11
  2. package/README.md +13 -4
  3. package/dist/cli/agentSession.d.ts +62 -0
  4. package/dist/cli/agentSession.d.ts.map +1 -0
  5. package/dist/cli/agentSession.js +100 -0
  6. package/dist/cli/agentSession.js.map +1 -0
  7. package/dist/cli/artifactPaths.d.ts +42 -0
  8. package/dist/cli/artifactPaths.d.ts.map +1 -0
  9. package/dist/cli/artifactPaths.js +144 -0
  10. package/dist/cli/artifactPaths.js.map +1 -0
  11. package/dist/cli/bounds.d.ts +88 -0
  12. package/dist/cli/bounds.d.ts.map +1 -0
  13. package/dist/cli/bounds.js +128 -0
  14. package/dist/cli/bounds.js.map +1 -0
  15. package/dist/cli/client.d.ts +3 -0
  16. package/dist/cli/client.d.ts.map +1 -1
  17. package/dist/cli/client.js +24 -8
  18. package/dist/cli/client.js.map +1 -1
  19. package/dist/cli/daemon.d.ts +3 -0
  20. package/dist/cli/daemon.d.ts.map +1 -1
  21. package/dist/cli/daemon.js +152 -39
  22. package/dist/cli/daemon.js.map +1 -1
  23. package/dist/cli/defaults.d.ts +23 -3
  24. package/dist/cli/defaults.d.ts.map +1 -1
  25. package/dist/cli/defaults.js +50 -5
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/dispatcher.d.ts +54 -11
  28. package/dist/cli/dispatcher.d.ts.map +1 -1
  29. package/dist/cli/dispatcher.js +851 -54
  30. package/dist/cli/dispatcher.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +305 -56
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init.d.ts +14 -14
  35. package/dist/cli/init.d.ts.map +1 -1
  36. package/dist/cli/init.js +328 -180
  37. package/dist/cli/init.js.map +1 -1
  38. package/dist/cli/journal.d.ts +153 -0
  39. package/dist/cli/journal.d.ts.map +1 -0
  40. package/dist/cli/journal.js +252 -0
  41. package/dist/cli/journal.js.map +1 -0
  42. package/dist/cli/lineReader.d.ts +33 -0
  43. package/dist/cli/lineReader.d.ts.map +1 -0
  44. package/dist/cli/lineReader.js +66 -0
  45. package/dist/cli/lineReader.js.map +1 -0
  46. package/dist/cli/locatorCandidates.d.ts +44 -0
  47. package/dist/cli/locatorCandidates.d.ts.map +1 -0
  48. package/dist/cli/locatorCandidates.js +190 -0
  49. package/dist/cli/locatorCandidates.js.map +1 -0
  50. package/dist/cli/mcp/artifacts.d.ts +39 -1
  51. package/dist/cli/mcp/artifacts.d.ts.map +1 -1
  52. package/dist/cli/mcp/artifacts.js +104 -9
  53. package/dist/cli/mcp/artifacts.js.map +1 -1
  54. package/dist/cli/mcp/bounds.d.ts +65 -0
  55. package/dist/cli/mcp/bounds.d.ts.map +1 -0
  56. package/dist/cli/mcp/bounds.js +124 -0
  57. package/dist/cli/mcp/bounds.js.map +1 -0
  58. package/dist/cli/mcp/params.d.ts +38 -0
  59. package/dist/cli/mcp/params.d.ts.map +1 -0
  60. package/dist/cli/mcp/params.js +174 -0
  61. package/dist/cli/mcp/params.js.map +1 -0
  62. package/dist/cli/mcp/server.d.ts +94 -16
  63. package/dist/cli/mcp/server.d.ts.map +1 -1
  64. package/dist/cli/mcp/server.js +304 -150
  65. package/dist/cli/mcp/server.js.map +1 -1
  66. package/dist/cli/mcp/tools.d.ts +62 -17
  67. package/dist/cli/mcp/tools.d.ts.map +1 -1
  68. package/dist/cli/mcp/tools.js +373 -158
  69. package/dist/cli/mcp/tools.js.map +1 -1
  70. package/dist/cli/pageSemantics.d.ts +42 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +194 -0
  73. package/dist/cli/pageSemantics.js.map +1 -0
  74. package/dist/cli/parseArgs.d.ts +6 -18
  75. package/dist/cli/parseArgs.d.ts.map +1 -1
  76. package/dist/cli/parseArgs.js +689 -45
  77. package/dist/cli/parseArgs.js.map +1 -1
  78. package/dist/cli/protocol.d.ts +8 -0
  79. package/dist/cli/protocol.d.ts.map +1 -1
  80. package/dist/cli/sessionRegistry.d.ts +42 -0
  81. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  82. package/dist/cli/sessionRegistry.js +99 -0
  83. package/dist/cli/sessionRegistry.js.map +1 -0
  84. package/dist/cli/snapshot.d.ts +100 -18
  85. package/dist/cli/snapshot.d.ts.map +1 -1
  86. package/dist/cli/snapshot.js +248 -102
  87. package/dist/cli/snapshot.js.map +1 -1
  88. package/dist/cli/stateStore.d.ts +50 -0
  89. package/dist/cli/stateStore.d.ts.map +1 -0
  90. package/dist/cli/stateStore.js +122 -0
  91. package/dist/cli/stateStore.js.map +1 -0
  92. package/dist/index.d.ts +1 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/lib/bidi/index.d.ts +1 -1
  96. package/dist/lib/bidi/index.d.ts.map +1 -1
  97. package/dist/lib/bidi/index.js +2 -2
  98. package/dist/lib/bidi/index.js.map +1 -1
  99. package/dist/lib/bidi/logs.d.ts +3 -0
  100. package/dist/lib/bidi/logs.d.ts.map +1 -1
  101. package/dist/lib/bidi/logs.js +7 -0
  102. package/dist/lib/bidi/logs.js.map +1 -1
  103. package/dist/lib/bidi/network.d.ts +14 -1
  104. package/dist/lib/bidi/network.d.ts.map +1 -1
  105. package/dist/lib/bidi/network.js +45 -7
  106. package/dist/lib/bidi/network.js.map +1 -1
  107. package/dist/lib/bidi/storage.d.ts +30 -6
  108. package/dist/lib/bidi/storage.d.ts.map +1 -1
  109. package/dist/lib/bidi/storage.js +190 -45
  110. package/dist/lib/bidi/storage.js.map +1 -1
  111. package/dist/lib/bidi/types.d.ts +7 -2
  112. package/dist/lib/bidi/types.d.ts.map +1 -1
  113. package/dist/lib/browser.d.ts +6 -3
  114. package/dist/lib/browser.d.ts.map +1 -1
  115. package/dist/lib/browser.js +140 -30
  116. package/dist/lib/browser.js.map +1 -1
  117. package/dist/lib/browserContext.d.ts +54 -18
  118. package/dist/lib/browserContext.d.ts.map +1 -1
  119. package/dist/lib/browserContext.js +295 -92
  120. package/dist/lib/browserContext.js.map +1 -1
  121. package/dist/lib/by.d.ts +3 -3
  122. package/dist/lib/by.d.ts.map +1 -1
  123. package/dist/lib/by.js +7 -3
  124. package/dist/lib/by.js.map +1 -1
  125. package/dist/lib/driver.d.ts +7 -0
  126. package/dist/lib/driver.d.ts.map +1 -1
  127. package/dist/lib/driver.js +12 -0
  128. package/dist/lib/driver.js.map +1 -1
  129. package/dist/lib/errors.d.ts +8 -0
  130. package/dist/lib/errors.d.ts.map +1 -1
  131. package/dist/lib/errors.js +8 -0
  132. package/dist/lib/errors.js.map +1 -1
  133. package/dist/lib/initScript.d.ts +9 -0
  134. package/dist/lib/initScript.d.ts.map +1 -0
  135. package/dist/lib/initScript.js +14 -0
  136. package/dist/lib/initScript.js.map +1 -0
  137. package/dist/lib/page.d.ts +22 -0
  138. package/dist/lib/page.d.ts.map +1 -1
  139. package/dist/lib/page.js +48 -0
  140. package/dist/lib/page.js.map +1 -1
  141. package/dist/lib/secureFile.d.ts +8 -0
  142. package/dist/lib/secureFile.d.ts.map +1 -0
  143. package/dist/lib/secureFile.js +67 -0
  144. package/dist/lib/secureFile.js.map +1 -0
  145. package/dist/lib/sessionStateValidation.d.ts +35 -0
  146. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  147. package/dist/lib/sessionStateValidation.js +223 -0
  148. package/dist/lib/sessionStateValidation.js.map +1 -0
  149. package/dist/lib/tracing.d.ts +2 -1
  150. package/dist/lib/tracing.d.ts.map +1 -1
  151. package/dist/lib/tracing.js +12 -1
  152. package/dist/lib/tracing.js.map +1 -1
  153. package/docs/agents.md +56 -61
  154. package/docs/api-reference.md +2 -1
  155. package/docs/browser-api.md +24 -4
  156. package/docs/browser-context.md +18 -12
  157. package/docs/cli.md +346 -26
  158. package/docs/error-codes.md +5 -4
  159. package/docs/getting-started.md +6 -1
  160. package/docs/index.md +1 -1
  161. package/docs/mcp.md +129 -62
  162. package/docs/public/examples/agent-actions.html +48 -0
  163. package/docs/public/examples/agent-debug.html +86 -0
  164. package/docs/public/examples/agent-late-mutation.html +18 -0
  165. package/docs/public/examples/agent-ref-shift.html +33 -0
  166. package/docs/recipes/login-once-reuse-session.md +32 -4
  167. package/docs/session-management.md +64 -3
  168. package/docs/why-craftdriver.md +1 -1
  169. package/package.json +3 -1
  170. package/skills/craftdriver/SKILL.md +59 -75
  171. package/skills/craftdriver/agents/openai.yaml +4 -0
  172. package/skills/craftdriver/cheatsheet.md +18 -4
  173. package/skills/craftdriver/cli.md +194 -68
  174. package/skills/craftdriver/patterns.md +14 -8
  175. 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"}