libretto 0.6.11 → 0.6.13
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/README.md +7 -8
- package/README.template.md +7 -8
- package/dist/cli/cli.js +0 -22
- package/dist/cli/commands/browser.js +18 -24
- package/dist/cli/commands/execution.js +254 -234
- package/dist/cli/commands/experiments.js +100 -0
- package/dist/cli/commands/setup.js +3 -310
- package/dist/cli/commands/shared.js +10 -0
- package/dist/cli/commands/snapshot.js +46 -64
- package/dist/cli/commands/status.js +1 -40
- package/dist/cli/core/browser.js +303 -124
- package/dist/cli/core/config.js +5 -6
- package/dist/cli/core/context.js +4 -0
- package/dist/cli/core/daemon/config.js +0 -6
- package/dist/cli/core/daemon/daemon.js +497 -90
- package/dist/cli/core/daemon/ipc.js +170 -129
- package/dist/cli/core/daemon/snapshot.js +48 -9
- package/dist/cli/core/experiments.js +39 -0
- package/dist/cli/core/session.js +5 -4
- package/dist/cli/core/skill-version.js +2 -1
- package/dist/cli/core/workflow-runner/runner.js +147 -0
- package/dist/cli/core/workflow-runtime.js +60 -0
- package/dist/cli/index.js +0 -2
- package/dist/cli/router.js +4 -3
- package/dist/shared/debug/pause-handler.d.ts +9 -0
- package/dist/shared/debug/pause-handler.js +15 -0
- package/dist/shared/debug/pause.d.ts +1 -2
- package/dist/shared/debug/pause.js +13 -36
- package/dist/shared/instrumentation/instrument.js +4 -4
- package/dist/shared/ipc/child-process-transport.d.ts +7 -0
- package/dist/shared/ipc/child-process-transport.js +60 -0
- package/dist/shared/ipc/child-process-transport.spec.d.ts +2 -0
- package/dist/shared/ipc/child-process-transport.spec.js +68 -0
- package/dist/shared/ipc/ipc.d.ts +46 -0
- package/dist/shared/ipc/ipc.js +165 -0
- package/dist/shared/ipc/ipc.spec.d.ts +2 -0
- package/dist/shared/ipc/ipc.spec.js +114 -0
- package/dist/shared/ipc/socket-transport.d.ts +9 -0
- package/dist/shared/ipc/socket-transport.js +143 -0
- package/dist/shared/ipc/socket-transport.spec.d.ts +2 -0
- package/dist/shared/ipc/socket-transport.spec.js +117 -0
- package/dist/shared/package-manager.d.ts +7 -0
- package/dist/shared/package-manager.js +60 -0
- package/dist/shared/paths/paths.d.ts +1 -8
- package/dist/shared/paths/paths.js +1 -49
- package/dist/shared/snapshot/capture-snapshot.d.ts +9 -0
- package/dist/shared/snapshot/capture-snapshot.js +463 -0
- package/dist/shared/snapshot/diff-snapshots.d.ts +72 -0
- package/dist/shared/snapshot/diff-snapshots.js +358 -0
- package/dist/shared/snapshot/render-snapshot.d.ts +39 -0
- package/dist/shared/snapshot/render-snapshot.js +651 -0
- package/dist/shared/snapshot/snapshot.spec.d.ts +2 -0
- package/dist/shared/snapshot/snapshot.spec.js +333 -0
- package/dist/shared/snapshot/types.d.ts +40 -0
- package/dist/shared/snapshot/types.js +0 -0
- package/dist/shared/snapshot/wait-for-page-stable.d.ts +17 -0
- package/dist/shared/snapshot/wait-for-page-stable.js +281 -0
- package/dist/shared/state/session-state.d.ts +1 -0
- package/dist/shared/state/session-state.js +1 -0
- package/docs/experiments.md +67 -0
- package/docs/releasing.md +8 -6
- package/package.json +5 -2
- package/skills/libretto/SKILL.md +19 -19
- package/skills/libretto/references/configuration-file-reference.md +6 -12
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto-readonly/SKILL.md +2 -9
- package/src/cli/AGENTS.md +7 -0
- package/src/cli/cli.ts +0 -23
- package/src/cli/commands/browser.ts +14 -18
- package/src/cli/commands/execution.ts +303 -271
- package/src/cli/commands/experiments.ts +120 -0
- package/src/cli/commands/setup.ts +3 -400
- package/src/cli/commands/shared.ts +20 -0
- package/src/cli/commands/snapshot.ts +54 -94
- package/src/cli/commands/status.ts +1 -48
- package/src/cli/core/browser.ts +372 -150
- package/src/cli/core/config.ts +4 -5
- package/src/cli/core/context.ts +4 -0
- package/src/cli/core/daemon/config.ts +35 -19
- package/src/cli/core/daemon/daemon.ts +645 -107
- package/src/cli/core/daemon/ipc.ts +319 -214
- package/src/cli/core/daemon/snapshot.ts +71 -15
- package/src/cli/core/experiments.ts +56 -0
- package/src/cli/core/resolve-model.ts +5 -0
- package/src/cli/core/session.ts +5 -4
- package/src/cli/core/skill-version.ts +2 -1
- package/src/cli/core/workflow-runner/runner.ts +237 -0
- package/src/cli/core/workflow-runtime.ts +86 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/router.ts +4 -3
- package/src/shared/debug/pause-handler.ts +20 -0
- package/src/shared/debug/pause.ts +14 -48
- package/src/shared/instrumentation/instrument.ts +4 -4
- package/src/shared/ipc/AGENTS.md +24 -0
- package/src/shared/ipc/child-process-transport.spec.ts +86 -0
- package/src/shared/ipc/child-process-transport.ts +96 -0
- package/src/shared/ipc/ipc.spec.ts +161 -0
- package/src/shared/ipc/ipc.ts +288 -0
- package/src/shared/ipc/socket-transport.spec.ts +141 -0
- package/src/shared/ipc/socket-transport.ts +189 -0
- package/src/shared/package-manager.ts +76 -0
- package/src/shared/paths/paths.ts +0 -72
- package/src/shared/snapshot/capture-snapshot.ts +615 -0
- package/src/shared/snapshot/diff-snapshots.ts +579 -0
- package/src/shared/snapshot/render-snapshot.ts +962 -0
- package/src/shared/snapshot/snapshot.spec.ts +388 -0
- package/src/shared/snapshot/types.ts +43 -0
- package/src/shared/snapshot/wait-for-page-stable.ts +425 -0
- package/src/shared/state/session-state.ts +1 -0
- package/dist/cli/commands/ai.js +0 -109
- package/dist/cli/core/ai-model.js +0 -192
- package/dist/cli/core/api-snapshot-analyzer.js +0 -86
- package/dist/cli/core/daemon/index.js +0 -16
- package/dist/cli/core/daemon/spawn.js +0 -90
- package/dist/cli/core/pause-signals.js +0 -29
- package/dist/cli/core/snapshot-analyzer.js +0 -666
- package/dist/cli/workers/run-integration-runtime.js +0 -235
- package/dist/cli/workers/run-integration-worker-protocol.js +0 -17
- package/dist/cli/workers/run-integration-worker.js +0 -64
- package/scripts/summarize-evals.mjs +0 -135
- package/src/cli/commands/ai.ts +0 -143
- package/src/cli/core/ai-model.ts +0 -298
- package/src/cli/core/api-snapshot-analyzer.ts +0 -110
- package/src/cli/core/daemon/index.ts +0 -24
- package/src/cli/core/daemon/spawn.ts +0 -171
- package/src/cli/core/pause-signals.ts +0 -35
- package/src/cli/core/snapshot-analyzer.ts +0 -855
- package/src/cli/workers/run-integration-runtime.ts +0 -326
- package/src/cli/workers/run-integration-worker-protocol.ts +0 -19
- package/src/cli/workers/run-integration-worker.ts +0 -72
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
+
const DEFAULT_MUTATION_IDLE_MS = 400;
|
|
5
|
+
const DEFAULT_MINIMUM_WAIT_MS = 800;
|
|
6
|
+
const DEFAULT_POLL_INTERVAL_MS = 100;
|
|
7
|
+
|
|
8
|
+
type LoadState = "domcontentloaded" | "load";
|
|
9
|
+
|
|
10
|
+
export type PageStabilityWaitOptions = {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
mutationIdleMs?: number;
|
|
13
|
+
minimumWaitMs?: number;
|
|
14
|
+
pollIntervalMs?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PageStabilityWaitResult = {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
diagnostics: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type PageStabilityWaitArgs = Required<PageStabilityWaitOptions>;
|
|
23
|
+
|
|
24
|
+
type BrowserWaiterApi = {
|
|
25
|
+
waitForStability(args: PageStabilityWaitArgs): Promise<string | null>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function preparePageStabilityWait(
|
|
29
|
+
page: Page,
|
|
30
|
+
options: Pick<PageStabilityWaitOptions, "timeoutMs"> = {},
|
|
31
|
+
): Promise<PageStabilityWaitResult> {
|
|
32
|
+
const diagnostic = await installBrowserStabilityWaiterOnPage(
|
|
33
|
+
page,
|
|
34
|
+
options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
35
|
+
);
|
|
36
|
+
return {
|
|
37
|
+
ok: diagnostic === null,
|
|
38
|
+
diagnostics: diagnostic === null ? [] : [diagnostic],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function waitForPageStable(
|
|
43
|
+
page: Page,
|
|
44
|
+
options: PageStabilityWaitOptions = {},
|
|
45
|
+
): Promise<PageStabilityWaitResult> {
|
|
46
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
47
|
+
const mutationIdleMs = options.mutationIdleMs ?? DEFAULT_MUTATION_IDLE_MS;
|
|
48
|
+
const minimumWaitMs = options.minimumWaitMs ?? DEFAULT_MINIMUM_WAIT_MS;
|
|
49
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
|
|
52
|
+
const loadDiagnostics = await Promise.all([
|
|
53
|
+
waitForLoadState(page, "domcontentloaded", deadline),
|
|
54
|
+
waitForLoadState(page, "load", deadline),
|
|
55
|
+
]);
|
|
56
|
+
const browserDiagnostic = await waitForBrowserStability(page, {
|
|
57
|
+
timeoutMs: Math.max(0, deadline - Date.now()),
|
|
58
|
+
mutationIdleMs,
|
|
59
|
+
minimumWaitMs,
|
|
60
|
+
pollIntervalMs,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const diagnostics = [...loadDiagnostics, browserDiagnostic].filter(
|
|
64
|
+
(diagnostic): diagnostic is string => diagnostic !== null,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function waitForLoadState(
|
|
71
|
+
page: Page,
|
|
72
|
+
state: LoadState,
|
|
73
|
+
deadline: number,
|
|
74
|
+
): Promise<string | null> {
|
|
75
|
+
const timeout = Math.max(0, deadline - Date.now());
|
|
76
|
+
if (timeout === 0) return `Timed out waiting for ${state}`;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await page.waitForLoadState(state, { timeout });
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return `Failed to wait for ${state}: ${errorMessage(error)}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function waitForBrowserStability(
|
|
87
|
+
page: Page,
|
|
88
|
+
args: PageStabilityWaitArgs,
|
|
89
|
+
): Promise<string | null> {
|
|
90
|
+
const deadline = Date.now() + args.timeoutMs;
|
|
91
|
+
let lastError: string | null = null;
|
|
92
|
+
|
|
93
|
+
while (Date.now() < deadline) {
|
|
94
|
+
const installDiagnostic = await installBrowserStabilityWaiterOnPage(
|
|
95
|
+
page,
|
|
96
|
+
Math.max(0, deadline - Date.now()),
|
|
97
|
+
);
|
|
98
|
+
if (installDiagnostic) return installDiagnostic;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return await page.evaluate(runBrowserStabilityWait, {
|
|
102
|
+
...args,
|
|
103
|
+
timeoutMs: Math.max(0, deadline - Date.now()),
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
lastError = errorMessage(error);
|
|
107
|
+
if (!isRetryableExecutionContextError(lastError)) {
|
|
108
|
+
return `Failed to wait for page stability: ${lastError}`;
|
|
109
|
+
}
|
|
110
|
+
await sleep(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return lastError
|
|
115
|
+
? `Failed to wait for page stability: ${lastError}`
|
|
116
|
+
: "Timed out waiting for page stability";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function installBrowserStabilityWaiterOnPage(
|
|
120
|
+
page: Page,
|
|
121
|
+
timeoutMs: number,
|
|
122
|
+
): Promise<string | null> {
|
|
123
|
+
const deadline = Date.now() + timeoutMs;
|
|
124
|
+
let lastError: string | null = null;
|
|
125
|
+
|
|
126
|
+
while (Date.now() < deadline) {
|
|
127
|
+
try {
|
|
128
|
+
await page.evaluate(installPageStabilityWaiter);
|
|
129
|
+
return null;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
lastError = errorMessage(error);
|
|
132
|
+
if (!isRetryableExecutionContextError(lastError)) {
|
|
133
|
+
return `Failed to install page stability waiter: ${lastError}`;
|
|
134
|
+
}
|
|
135
|
+
await sleep(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lastError
|
|
140
|
+
? `Failed to install page stability waiter: ${lastError}`
|
|
141
|
+
: "Timed out installing page stability waiter";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function installPageStabilityWaiter(): void {
|
|
145
|
+
const symbol = Symbol.for("libretto.pageStabilityWaiter");
|
|
146
|
+
const windowWithWaiter = window as Window & {
|
|
147
|
+
[symbol]?: BrowserWaiterApi;
|
|
148
|
+
};
|
|
149
|
+
if (windowWithWaiter[symbol]) return;
|
|
150
|
+
|
|
151
|
+
type WaiterState = {
|
|
152
|
+
pendingRequests: number;
|
|
153
|
+
pendingUrls: Set<string>;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
type ResourceSnapshot = {
|
|
157
|
+
pendingResources: number;
|
|
158
|
+
pendingResourceLabels: string[];
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const state: WaiterState = {
|
|
162
|
+
pendingRequests: 0,
|
|
163
|
+
pendingUrls: new Set<string>(),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const requestStarted = (url: string): void => {
|
|
167
|
+
state.pendingRequests += 1;
|
|
168
|
+
state.pendingUrls.add(url);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const requestFinished = (url: string): void => {
|
|
172
|
+
state.pendingRequests = Math.max(0, state.pendingRequests - 1);
|
|
173
|
+
state.pendingUrls.delete(url);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const originalFetch = window.fetch;
|
|
177
|
+
if (typeof originalFetch === "function") {
|
|
178
|
+
window.fetch = function trackedFetch(
|
|
179
|
+
this: Window,
|
|
180
|
+
input: RequestInfo | URL,
|
|
181
|
+
init?: RequestInit,
|
|
182
|
+
): Promise<Response> {
|
|
183
|
+
const url = requestUrl(input);
|
|
184
|
+
requestStarted(url);
|
|
185
|
+
return originalFetch.call(this, input, init).finally(() => {
|
|
186
|
+
requestFinished(url);
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
192
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
193
|
+
const requestUrls = new WeakMap<XMLHttpRequest, string>();
|
|
194
|
+
const startedRequests = new WeakSet<XMLHttpRequest>();
|
|
195
|
+
|
|
196
|
+
XMLHttpRequest.prototype.open = function trackedOpen(
|
|
197
|
+
this: XMLHttpRequest,
|
|
198
|
+
method: string,
|
|
199
|
+
url: string | URL,
|
|
200
|
+
async?: boolean,
|
|
201
|
+
username?: string | null,
|
|
202
|
+
password?: string | null,
|
|
203
|
+
): void {
|
|
204
|
+
requestUrls.set(this, String(url));
|
|
205
|
+
return originalOpen.call(
|
|
206
|
+
this,
|
|
207
|
+
method,
|
|
208
|
+
url,
|
|
209
|
+
async ?? true,
|
|
210
|
+
username,
|
|
211
|
+
password,
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
XMLHttpRequest.prototype.send = function trackedSend(
|
|
216
|
+
this: XMLHttpRequest,
|
|
217
|
+
body?: Document | XMLHttpRequestBodyInit | null,
|
|
218
|
+
): void {
|
|
219
|
+
const url = requestUrls.get(this) ?? "XMLHttpRequest";
|
|
220
|
+
requestStarted(url);
|
|
221
|
+
startedRequests.add(this);
|
|
222
|
+
|
|
223
|
+
const finish = (): void => {
|
|
224
|
+
if (!startedRequests.has(this)) return;
|
|
225
|
+
startedRequests.delete(this);
|
|
226
|
+
requestFinished(url);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
this.addEventListener("loadend", finish, { once: true });
|
|
230
|
+
try {
|
|
231
|
+
return originalSend.call(this, body);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
finish();
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const waitForStability = async (
|
|
239
|
+
args: PageStabilityWaitArgs,
|
|
240
|
+
): Promise<string | null> => {
|
|
241
|
+
const sleepInPage = (ms: number): Promise<void> =>
|
|
242
|
+
new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
243
|
+
|
|
244
|
+
const startedAt = Date.now();
|
|
245
|
+
let lastActivityAt = Date.now();
|
|
246
|
+
let lastResources: ResourceSnapshot = {
|
|
247
|
+
pendingResources: 0,
|
|
248
|
+
pendingResourceLabels: [],
|
|
249
|
+
};
|
|
250
|
+
let lastPendingRequests = state.pendingRequests;
|
|
251
|
+
let lastPendingUrls = [...state.pendingUrls];
|
|
252
|
+
|
|
253
|
+
const markActivity = (): void => {
|
|
254
|
+
lastActivityAt = Date.now();
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const observer = new MutationObserver(markActivity);
|
|
258
|
+
const root = document.documentElement ?? document.body;
|
|
259
|
+
if (root) {
|
|
260
|
+
observer.observe(root, {
|
|
261
|
+
attributes: true,
|
|
262
|
+
childList: true,
|
|
263
|
+
characterData: true,
|
|
264
|
+
subtree: true,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
while (Date.now() - startedAt < args.timeoutMs) {
|
|
270
|
+
lastResources = countPendingResourceElements();
|
|
271
|
+
lastPendingRequests = state.pendingRequests;
|
|
272
|
+
lastPendingUrls = [...state.pendingUrls];
|
|
273
|
+
const pageLoaded = document.readyState === "complete";
|
|
274
|
+
const mutationIdle = Date.now() - lastActivityAt >= args.mutationIdleMs;
|
|
275
|
+
const waitedLongEnough = Date.now() - startedAt >= args.minimumWaitMs;
|
|
276
|
+
const requestIdle = lastPendingRequests === 0;
|
|
277
|
+
const resourceIdle = lastResources.pendingResources === 0;
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
pageLoaded &&
|
|
281
|
+
mutationIdle &&
|
|
282
|
+
waitedLongEnough &&
|
|
283
|
+
requestIdle &&
|
|
284
|
+
resourceIdle
|
|
285
|
+
) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await sleepInPage(args.pollIntervalMs);
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
observer.disconnect();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return formatStabilityTimeout({
|
|
296
|
+
timeoutMs: args.timeoutMs,
|
|
297
|
+
readyState: document.readyState,
|
|
298
|
+
pendingRequests: lastPendingRequests,
|
|
299
|
+
pendingUrls: lastPendingUrls,
|
|
300
|
+
pendingResources: lastResources.pendingResources,
|
|
301
|
+
pendingResourceLabels: lastResources.pendingResourceLabels,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
Object.defineProperty(windowWithWaiter, symbol, {
|
|
306
|
+
value: { waitForStability } satisfies BrowserWaiterApi,
|
|
307
|
+
configurable: false,
|
|
308
|
+
enumerable: false,
|
|
309
|
+
writable: false,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
function countPendingResourceElements(): ResourceSnapshot {
|
|
313
|
+
const elements = Array.from(
|
|
314
|
+
document.querySelectorAll(
|
|
315
|
+
'img,video,audio,embed,object,iframe[src],link[rel="stylesheet"][href]',
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
let pendingResources = 0;
|
|
319
|
+
const pendingResourceLabels: string[] = [];
|
|
320
|
+
|
|
321
|
+
const markPending = (element: Element): void => {
|
|
322
|
+
pendingResources += 1;
|
|
323
|
+
if (pendingResourceLabels.length < 5) {
|
|
324
|
+
pendingResourceLabels.push(resourceLabel(element));
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
for (const element of elements) {
|
|
329
|
+
const tagName = element.tagName.toLowerCase();
|
|
330
|
+
if (tagName === "img") {
|
|
331
|
+
const image = element as HTMLImageElement;
|
|
332
|
+
if (image.loading !== "lazy" && !image.complete) markPending(element);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (tagName === "video" || tagName === "audio") {
|
|
337
|
+
const media = element as HTMLMediaElement;
|
|
338
|
+
if (media.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
339
|
+
markPending(element);
|
|
340
|
+
}
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (tagName === "iframe") {
|
|
345
|
+
const iframe = element as HTMLIFrameElement;
|
|
346
|
+
try {
|
|
347
|
+
if (
|
|
348
|
+
iframe.contentDocument &&
|
|
349
|
+
iframe.contentDocument.readyState !== "complete"
|
|
350
|
+
) {
|
|
351
|
+
markPending(element);
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
// Cross-origin iframes cannot be inspected; treat them as settled.
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (tagName === "link") {
|
|
360
|
+
const link = element as HTMLLinkElement;
|
|
361
|
+
if (!link.sheet) markPending(element);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { pendingResources, pendingResourceLabels };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function requestUrl(input: RequestInfo | URL): string {
|
|
369
|
+
if (typeof input === "string") return input;
|
|
370
|
+
if (input instanceof URL) return input.href;
|
|
371
|
+
return input.url;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function resourceLabel(element: Element): string {
|
|
375
|
+
const tagName = element.tagName.toLowerCase();
|
|
376
|
+
const source =
|
|
377
|
+
element.getAttribute("src") ?? element.getAttribute("href") ?? "";
|
|
378
|
+
return source ? `${tagName}:${source}` : tagName;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function formatStabilityTimeout(args: {
|
|
382
|
+
timeoutMs: number;
|
|
383
|
+
readyState: DocumentReadyState;
|
|
384
|
+
pendingRequests: number;
|
|
385
|
+
pendingUrls: string[];
|
|
386
|
+
pendingResources: number;
|
|
387
|
+
pendingResourceLabels: string[];
|
|
388
|
+
}): string {
|
|
389
|
+
const urls = args.pendingUrls.slice(0, 5).join(", ");
|
|
390
|
+
const resources = args.pendingResourceLabels.join(", ");
|
|
391
|
+
return (
|
|
392
|
+
`Timed out waiting for page stability after ${args.timeoutMs}ms ` +
|
|
393
|
+
`(readyState=${args.readyState}, pendingRequests=${args.pendingRequests}` +
|
|
394
|
+
`${urls ? `, pendingUrls=${urls}` : ""}, pendingResources=${args.pendingResources}` +
|
|
395
|
+
`${resources ? `, pendingResourceLabels=${resources}` : ""})`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function runBrowserStabilityWait(
|
|
401
|
+
args: PageStabilityWaitArgs,
|
|
402
|
+
): Promise<string | null> {
|
|
403
|
+
const symbol = Symbol.for("libretto.pageStabilityWaiter");
|
|
404
|
+
const waiter = (
|
|
405
|
+
window as unknown as Window & Record<symbol, BrowserWaiterApi | undefined>
|
|
406
|
+
)[symbol];
|
|
407
|
+
if (!waiter) return "Page stability waiter was not installed.";
|
|
408
|
+
return waiter.waitForStability(args);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function sleep(ms: number): Promise<void> {
|
|
412
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function errorMessage(error: unknown): string {
|
|
416
|
+
return error instanceof Error ? error.message : String(error);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function isRetryableExecutionContextError(message: string): boolean {
|
|
420
|
+
return (
|
|
421
|
+
message.includes("Execution context was destroyed") ||
|
|
422
|
+
message.includes("Cannot find context with specified id") ||
|
|
423
|
+
message.includes("Most likely the page has been closed")
|
|
424
|
+
);
|
|
425
|
+
}
|
|
@@ -31,6 +31,7 @@ export const SessionStateFileSchema = z.object({
|
|
|
31
31
|
status: SessionStatusSchema.optional(),
|
|
32
32
|
mode: SessionAccessModeSchema.default("write-access"),
|
|
33
33
|
viewport: SessionViewportSchema.optional(),
|
|
34
|
+
stayOpenOnSuccess: z.boolean().optional(),
|
|
34
35
|
provider: ProviderStateSchema.optional(),
|
|
35
36
|
daemonSocketPath: z.string().optional(),
|
|
36
37
|
});
|
package/dist/cli/commands/ai.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
readSnapshotModel,
|
|
4
|
-
writeSnapshotModel,
|
|
5
|
-
clearSnapshotModel
|
|
6
|
-
} from "../core/config.js";
|
|
7
|
-
import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
|
|
8
|
-
import { DEFAULT_SNAPSHOT_MODELS } from "../core/ai-model.js";
|
|
9
|
-
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
10
|
-
const PROVIDER_ALIASES = {
|
|
11
|
-
claude: DEFAULT_SNAPSHOT_MODELS.anthropic,
|
|
12
|
-
gemini: DEFAULT_SNAPSHOT_MODELS.google,
|
|
13
|
-
google: DEFAULT_SNAPSHOT_MODELS.google
|
|
14
|
-
};
|
|
15
|
-
const CONFIGURE_PROVIDERS = [
|
|
16
|
-
"openai",
|
|
17
|
-
"anthropic",
|
|
18
|
-
"gemini",
|
|
19
|
-
"vertex"
|
|
20
|
-
];
|
|
21
|
-
function formatConfigureProviders(separator = " | ") {
|
|
22
|
-
return CONFIGURE_PROVIDERS.join(separator);
|
|
23
|
-
}
|
|
24
|
-
function printSnapshotModelConfig(model, configPath) {
|
|
25
|
-
console.log(`Snapshot model: ${model}`);
|
|
26
|
-
console.log(`Config file: ${configPath}`);
|
|
27
|
-
}
|
|
28
|
-
function resolveModelFromInput(input) {
|
|
29
|
-
const trimmed = input.trim();
|
|
30
|
-
if (!trimmed) return null;
|
|
31
|
-
if (trimmed.includes("/")) return trimmed;
|
|
32
|
-
const normalized = trimmed.toLowerCase();
|
|
33
|
-
return DEFAULT_SNAPSHOT_MODELS[normalized] ?? PROVIDER_ALIASES[normalized] ?? null;
|
|
34
|
-
}
|
|
35
|
-
function runAiConfigure(input, options = {}) {
|
|
36
|
-
const configureCommandName = options.configureCommandName ?? "npx libretto ai configure";
|
|
37
|
-
const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
|
|
38
|
-
const presetArg = input.preset?.trim();
|
|
39
|
-
if (!presetArg && !input.clear) {
|
|
40
|
-
const model2 = readSnapshotModel(configPath);
|
|
41
|
-
if (!model2) {
|
|
42
|
-
console.log(
|
|
43
|
-
`No snapshot model set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
|
|
44
|
-
);
|
|
45
|
-
console.log(
|
|
46
|
-
"Provider credentials still come from your shell or .env file."
|
|
47
|
-
);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
printSnapshotModelConfig(model2, configPath);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (input.clear) {
|
|
54
|
-
const removed = clearSnapshotModel(configPath);
|
|
55
|
-
if (removed) {
|
|
56
|
-
console.log(`Cleared snapshot model config: ${configPath}`);
|
|
57
|
-
} else {
|
|
58
|
-
console.log("No snapshot model was set.");
|
|
59
|
-
}
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const model = resolveModelFromInput(presetArg);
|
|
63
|
-
if (!model) {
|
|
64
|
-
console.log(
|
|
65
|
-
`Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>
|
|
66
|
-
${configureCommandName}
|
|
67
|
-
${configureCommandName} --clear`
|
|
68
|
-
);
|
|
69
|
-
throw new Error(
|
|
70
|
-
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
writeSnapshotModel(model, configPath);
|
|
74
|
-
console.log("Snapshot model saved.");
|
|
75
|
-
printSnapshotModelConfig(model, configPath);
|
|
76
|
-
}
|
|
77
|
-
const aiConfigureInput = SimpleCLI.input({
|
|
78
|
-
positionals: [
|
|
79
|
-
SimpleCLI.positional("preset", z.string().optional(), {
|
|
80
|
-
help: "Provider shorthand or provider/model-id"
|
|
81
|
-
})
|
|
82
|
-
],
|
|
83
|
-
named: {
|
|
84
|
-
clear: SimpleCLI.flag({ help: "Clear existing AI config" })
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
const aiCommands = SimpleCLI.group({
|
|
88
|
-
description: "AI commands",
|
|
89
|
-
routes: {
|
|
90
|
-
configure: SimpleCLI.command({
|
|
91
|
-
description: "Configure AI runtime"
|
|
92
|
-
}).input(aiConfigureInput).handle(async ({ input }) => {
|
|
93
|
-
runAiConfigure(
|
|
94
|
-
{
|
|
95
|
-
clear: input.clear,
|
|
96
|
-
preset: input.preset
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
configureCommandName: `libretto ai configure`
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
export {
|
|
106
|
-
aiCommands,
|
|
107
|
-
aiConfigureInput,
|
|
108
|
-
runAiConfigure
|
|
109
|
-
};
|