libretto 0.1.5 → 0.2.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 (183) hide show
  1. package/README.md +213 -17
  2. package/bin/libretto.mjs +18 -0
  3. package/dist/cli/cli.js +201 -0
  4. package/dist/cli/commands/ai.js +21 -0
  5. package/dist/cli/commands/browser.js +56 -0
  6. package/dist/cli/commands/execution.js +407 -0
  7. package/dist/cli/commands/logs.js +65 -0
  8. package/dist/cli/commands/snapshot.js +99 -0
  9. package/dist/cli/core/ai-config.js +149 -0
  10. package/dist/cli/core/browser.js +687 -0
  11. package/dist/cli/core/context.js +113 -0
  12. package/dist/cli/core/pause-signals.js +29 -0
  13. package/dist/cli/core/session.js +183 -0
  14. package/dist/cli/core/snapshot-analyzer.js +492 -0
  15. package/dist/cli/core/telemetry.js +350 -0
  16. package/dist/cli/index.js +13 -0
  17. package/dist/cli/workers/run-integration-runtime.js +204 -0
  18. package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
  19. package/dist/cli/workers/run-integration-worker.js +83 -0
  20. package/dist/index.cjs +127 -0
  21. package/dist/index.d.cts +22 -0
  22. package/dist/index.d.ts +22 -0
  23. package/dist/index.js +110 -0
  24. package/dist/runtime/download/download.cjs +70 -0
  25. package/dist/runtime/download/download.d.cts +35 -0
  26. package/dist/runtime/download/download.d.ts +35 -0
  27. package/dist/runtime/download/download.js +45 -0
  28. package/dist/runtime/download/index.cjs +30 -0
  29. package/dist/runtime/download/index.d.cts +3 -0
  30. package/dist/runtime/download/index.d.ts +3 -0
  31. package/dist/runtime/download/index.js +8 -0
  32. package/dist/runtime/extract/extract.cjs +87 -0
  33. package/dist/runtime/extract/extract.d.cts +23 -0
  34. package/dist/runtime/extract/extract.d.ts +23 -0
  35. package/dist/runtime/extract/extract.js +63 -0
  36. package/dist/runtime/extract/index.cjs +28 -0
  37. package/dist/runtime/extract/index.d.cts +5 -0
  38. package/dist/runtime/extract/index.d.ts +5 -0
  39. package/dist/runtime/extract/index.js +4 -0
  40. package/dist/runtime/network/index.cjs +28 -0
  41. package/dist/runtime/network/index.d.cts +4 -0
  42. package/dist/runtime/network/index.d.ts +4 -0
  43. package/dist/runtime/network/index.js +6 -0
  44. package/dist/runtime/network/network.cjs +91 -0
  45. package/dist/runtime/network/network.d.cts +28 -0
  46. package/dist/runtime/network/network.d.ts +28 -0
  47. package/dist/runtime/network/network.js +67 -0
  48. package/dist/runtime/recovery/agent.cjs +218 -0
  49. package/dist/runtime/recovery/agent.d.cts +13 -0
  50. package/dist/runtime/recovery/agent.d.ts +13 -0
  51. package/dist/runtime/recovery/agent.js +194 -0
  52. package/dist/runtime/recovery/errors.cjs +122 -0
  53. package/dist/runtime/recovery/errors.d.cts +31 -0
  54. package/dist/runtime/recovery/errors.d.ts +31 -0
  55. package/dist/runtime/recovery/errors.js +98 -0
  56. package/dist/runtime/recovery/index.cjs +34 -0
  57. package/dist/runtime/recovery/index.d.cts +7 -0
  58. package/dist/runtime/recovery/index.d.ts +7 -0
  59. package/dist/runtime/recovery/index.js +10 -0
  60. package/dist/runtime/recovery/recovery.cjs +53 -0
  61. package/dist/runtime/recovery/recovery.d.cts +12 -0
  62. package/dist/runtime/recovery/recovery.d.ts +12 -0
  63. package/dist/runtime/recovery/recovery.js +29 -0
  64. package/dist/runtime/step/index.cjs +31 -0
  65. package/dist/runtime/step/index.d.cts +7 -0
  66. package/dist/runtime/step/index.d.ts +7 -0
  67. package/dist/runtime/step/index.js +6 -0
  68. package/dist/runtime/step/runner.cjs +208 -0
  69. package/dist/runtime/step/runner.d.cts +16 -0
  70. package/dist/runtime/step/runner.d.ts +16 -0
  71. package/dist/runtime/step/runner.js +187 -0
  72. package/dist/runtime/step/step.cjs +67 -0
  73. package/dist/runtime/step/step.d.cts +23 -0
  74. package/dist/runtime/step/step.d.ts +23 -0
  75. package/dist/runtime/step/step.js +43 -0
  76. package/dist/runtime/step/types.cjs +16 -0
  77. package/dist/runtime/step/types.d.cts +72 -0
  78. package/dist/runtime/step/types.d.ts +72 -0
  79. package/dist/runtime/step/types.js +0 -0
  80. package/dist/shared/config/config.cjs +44 -0
  81. package/dist/shared/config/config.d.cts +10 -0
  82. package/dist/shared/config/config.d.ts +10 -0
  83. package/dist/shared/config/config.js +18 -0
  84. package/dist/shared/config/index.cjs +32 -0
  85. package/dist/shared/config/index.d.cts +1 -0
  86. package/dist/shared/config/index.d.ts +1 -0
  87. package/dist/shared/config/index.js +10 -0
  88. package/dist/shared/debug/index.cjs +32 -0
  89. package/dist/shared/debug/index.d.cts +2 -0
  90. package/dist/shared/debug/index.d.ts +2 -0
  91. package/dist/shared/debug/index.js +10 -0
  92. package/dist/shared/debug/pause.cjs +56 -0
  93. package/dist/shared/debug/pause.d.cts +23 -0
  94. package/dist/shared/debug/pause.d.ts +23 -0
  95. package/dist/shared/debug/pause.js +30 -0
  96. package/dist/shared/instrumentation/errors.cjs +81 -0
  97. package/dist/shared/instrumentation/errors.d.cts +12 -0
  98. package/dist/shared/instrumentation/errors.d.ts +12 -0
  99. package/dist/shared/instrumentation/errors.js +57 -0
  100. package/dist/shared/instrumentation/index.cjs +35 -0
  101. package/dist/shared/instrumentation/index.d.cts +6 -0
  102. package/dist/shared/instrumentation/index.d.ts +6 -0
  103. package/dist/shared/instrumentation/index.js +12 -0
  104. package/dist/shared/instrumentation/instrument.cjs +206 -0
  105. package/dist/shared/instrumentation/instrument.d.cts +32 -0
  106. package/dist/shared/instrumentation/instrument.d.ts +32 -0
  107. package/dist/shared/instrumentation/instrument.js +190 -0
  108. package/dist/shared/llm/client.cjs +139 -0
  109. package/dist/shared/llm/client.d.cts +6 -0
  110. package/dist/shared/llm/client.d.ts +6 -0
  111. package/dist/shared/llm/client.js +115 -0
  112. package/dist/shared/llm/index.cjs +28 -0
  113. package/dist/shared/llm/index.d.cts +3 -0
  114. package/dist/shared/llm/index.d.ts +3 -0
  115. package/dist/shared/llm/index.js +4 -0
  116. package/dist/shared/llm/types.cjs +16 -0
  117. package/dist/shared/llm/types.d.cts +34 -0
  118. package/dist/shared/llm/types.d.ts +34 -0
  119. package/dist/shared/llm/types.js +0 -0
  120. package/dist/shared/logger/index.cjs +35 -0
  121. package/dist/shared/logger/index.d.cts +2 -0
  122. package/dist/shared/logger/index.d.ts +2 -0
  123. package/dist/shared/logger/index.js +12 -0
  124. package/dist/shared/logger/logger.cjs +200 -0
  125. package/dist/shared/logger/logger.d.cts +70 -0
  126. package/dist/shared/logger/logger.d.ts +70 -0
  127. package/dist/shared/logger/logger.js +176 -0
  128. package/dist/shared/logger/sinks.cjs +160 -0
  129. package/dist/shared/logger/sinks.d.cts +9 -0
  130. package/dist/shared/logger/sinks.d.ts +9 -0
  131. package/dist/shared/logger/sinks.js +124 -0
  132. package/dist/shared/paths/paths.cjs +104 -0
  133. package/dist/shared/paths/paths.d.cts +10 -0
  134. package/dist/shared/paths/paths.d.ts +10 -0
  135. package/dist/shared/paths/paths.js +73 -0
  136. package/dist/shared/run/api.cjs +35 -0
  137. package/dist/shared/run/api.d.cts +3 -0
  138. package/dist/shared/run/api.d.ts +3 -0
  139. package/dist/shared/run/api.js +12 -0
  140. package/dist/shared/run/browser.cjs +98 -0
  141. package/dist/shared/run/browser.d.cts +22 -0
  142. package/dist/shared/run/browser.d.ts +22 -0
  143. package/dist/shared/run/browser.js +74 -0
  144. package/dist/shared/state/index.cjs +38 -0
  145. package/dist/shared/state/index.d.cts +2 -0
  146. package/dist/shared/state/index.d.ts +2 -0
  147. package/dist/shared/state/index.js +16 -0
  148. package/dist/shared/state/session-state.cjs +85 -0
  149. package/dist/shared/state/session-state.d.cts +34 -0
  150. package/dist/shared/state/session-state.d.ts +34 -0
  151. package/dist/shared/state/session-state.js +56 -0
  152. package/dist/shared/visualization/ghost-cursor.cjs +174 -0
  153. package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
  154. package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
  155. package/dist/shared/visualization/ghost-cursor.js +145 -0
  156. package/dist/shared/visualization/highlight.cjs +134 -0
  157. package/dist/shared/visualization/highlight.d.cts +22 -0
  158. package/dist/shared/visualization/highlight.d.ts +22 -0
  159. package/dist/shared/visualization/highlight.js +108 -0
  160. package/dist/shared/visualization/index.cjs +45 -0
  161. package/dist/shared/visualization/index.d.cts +3 -0
  162. package/dist/shared/visualization/index.d.ts +3 -0
  163. package/dist/shared/visualization/index.js +24 -0
  164. package/dist/shared/workflow/workflow.cjs +47 -0
  165. package/dist/shared/workflow/workflow.d.cts +33 -0
  166. package/dist/shared/workflow/workflow.d.ts +33 -0
  167. package/dist/shared/workflow/workflow.js +21 -0
  168. package/package.json +123 -26
  169. package/.npmignore +0 -2
  170. package/bin/libretto +0 -31
  171. package/lib/connect.js +0 -34
  172. package/lib/export.js +0 -224
  173. package/lib/import.js +0 -166
  174. package/lib/index.js +0 -8
  175. package/lib/log.js +0 -9
  176. package/lib/validate.js +0 -20
  177. package/makefile +0 -8
  178. package/src/connect.coffee +0 -25
  179. package/src/export.coffee +0 -222
  180. package/src/import.coffee +0 -166
  181. package/src/index.coffee +0 -3
  182. package/src/log.coffee +0 -3
  183. package/src/validate.coffee +0 -10
@@ -0,0 +1,350 @@
1
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import {
3
+ getSessionActionsLogPath,
4
+ getSessionNetworkLogPath
5
+ } from "./context.js";
6
+ import { assertSessionStateExistsOrThrow } from "./session.js";
7
+ function readNetworkLog(session, opts = {}) {
8
+ assertSessionStateExistsOrThrow(session);
9
+ const logPath = getSessionNetworkLogPath(session);
10
+ if (!existsSync(logPath)) return [];
11
+ const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
12
+ let entries = lines.map(
13
+ (line) => JSON.parse(line)
14
+ );
15
+ if (opts.method) {
16
+ const m = opts.method.toUpperCase();
17
+ entries = entries.filter((e) => e.method === m);
18
+ }
19
+ if (opts.filter) {
20
+ const re = new RegExp(opts.filter, "i");
21
+ entries = entries.filter((e) => re.test(e.url));
22
+ }
23
+ const last = opts.last ?? 20;
24
+ if (entries.length > last) {
25
+ entries = entries.slice(-last);
26
+ }
27
+ return entries;
28
+ }
29
+ function formatNetworkEntry(e) {
30
+ const time = e.ts.replace(/.*T/, "").replace(/\.\d+Z$/, "");
31
+ const duration = e.durationMs != null ? `${e.durationMs}ms` : "?ms";
32
+ const size = e.size != null ? `${e.size}B` : "";
33
+ const parts = [
34
+ `[${time}]`,
35
+ `${e.status}`,
36
+ `${e.method.padEnd(6)}`,
37
+ e.url,
38
+ duration,
39
+ size
40
+ ].filter(Boolean);
41
+ let line = parts.join(" ");
42
+ if (e.postData) {
43
+ line += `
44
+ body: ${e.postData.substring(0, 120)}${e.postData.length > 120 ? "..." : ""}`;
45
+ }
46
+ return line;
47
+ }
48
+ function clearNetworkLog(session) {
49
+ assertSessionStateExistsOrThrow(session);
50
+ const logPath = getSessionNetworkLogPath(session);
51
+ writeFileSync(logPath, "");
52
+ }
53
+ function parentLogAction(session, entry) {
54
+ try {
55
+ const record = { ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry };
56
+ appendFileSync(getSessionActionsLogPath(session), JSON.stringify(record) + "\n");
57
+ } catch {
58
+ }
59
+ }
60
+ function readActionLog(session, opts = {}) {
61
+ assertSessionStateExistsOrThrow(session);
62
+ const logPath = getSessionActionsLogPath(session);
63
+ if (!existsSync(logPath)) return [];
64
+ const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
65
+ let entries = lines.map(
66
+ (line) => JSON.parse(line)
67
+ );
68
+ if (opts.action) {
69
+ const a = opts.action.toLowerCase();
70
+ entries = entries.filter((e) => e.action === a);
71
+ }
72
+ if (opts.source) {
73
+ const s = opts.source.toLowerCase();
74
+ entries = entries.filter((e) => e.source === s);
75
+ }
76
+ if (opts.filter) {
77
+ const re = new RegExp(opts.filter, "i");
78
+ entries = entries.filter(
79
+ (e) => re.test(e.action) || re.test(e.selector || "") || re.test(e.value || "") || re.test(e.url || "")
80
+ );
81
+ }
82
+ const last = opts.last ?? 20;
83
+ if (entries.length > last) {
84
+ entries = entries.slice(-last);
85
+ }
86
+ return entries;
87
+ }
88
+ function formatActionEntry(e) {
89
+ const time = e.ts.replace(/.*T/, "").replace(/\.\d+Z$/, "");
90
+ const src = e.source.toUpperCase().padEnd(5);
91
+ const parts = [`[${time}]`, `[${src}]`, e.action];
92
+ if (e.selector) parts.push(e.selector);
93
+ if (e.value) parts.push(`"${e.value}"`);
94
+ if (e.url) parts.push(e.url);
95
+ if (e.duration != null) parts.push(`${e.duration}ms`);
96
+ if (!e.success) parts.push(`ERROR: ${e.error || "unknown"}`);
97
+ return parts.join(" ");
98
+ }
99
+ function clearActionLog(session) {
100
+ assertSessionStateExistsOrThrow(session);
101
+ const logPath = getSessionActionsLogPath(session);
102
+ writeFileSync(logPath, "");
103
+ }
104
+ const LOCATOR_ACTION_METHODS = [
105
+ "click",
106
+ "dblclick",
107
+ "fill",
108
+ "type",
109
+ "press",
110
+ "check",
111
+ "uncheck",
112
+ "selectOption",
113
+ "hover",
114
+ "focus",
115
+ "scrollIntoViewIfNeeded",
116
+ "waitFor",
117
+ "innerHTML",
118
+ "innerText",
119
+ "textContent",
120
+ "inputValue",
121
+ "isChecked",
122
+ "isDisabled",
123
+ "isEditable",
124
+ "isEnabled",
125
+ "isHidden",
126
+ "isVisible",
127
+ "count",
128
+ "boundingBox",
129
+ "screenshot",
130
+ "evaluate",
131
+ "evaluateAll",
132
+ "evaluateHandle",
133
+ "getAttribute",
134
+ "dispatchEvent",
135
+ "setInputFiles",
136
+ "selectText",
137
+ "dragTo",
138
+ "highlight",
139
+ "tap"
140
+ ];
141
+ const LOCATOR_RETURNING_METHODS = [
142
+ "first",
143
+ "last",
144
+ "locator",
145
+ "getByRole",
146
+ "getByText",
147
+ "getByLabel",
148
+ "getByPlaceholder",
149
+ "getByAltText",
150
+ "getByTitle",
151
+ "getByTestId",
152
+ "filter",
153
+ "and",
154
+ "or"
155
+ ];
156
+ function formatHint(method, args) {
157
+ const formatted = args.map((a) => JSON.stringify(a)).join(", ");
158
+ return `${method}(${formatted})`;
159
+ }
160
+ function wrapLocator(locator, hint, session, page, onActivity) {
161
+ if (locator.__librettoActionLogged) return locator;
162
+ locator.__librettoActionLogged = true;
163
+ for (const actMethod of LOCATOR_ACTION_METHODS) {
164
+ if (typeof locator[actMethod] !== "function") continue;
165
+ const origAct = locator[actMethod].bind(locator);
166
+ locator[actMethod] = async (...actArgs) => {
167
+ const start = Date.now();
168
+ try {
169
+ await page.evaluate(() => {
170
+ window.__btApiActionInProgress = true;
171
+ });
172
+ } catch {
173
+ }
174
+ try {
175
+ const result = await origAct(...actArgs);
176
+ parentLogAction(session, {
177
+ action: actMethod,
178
+ source: "agent",
179
+ selector: hint,
180
+ value: actArgs[0] !== void 0 ? String(actArgs[0]).slice(0, 100) : void 0,
181
+ duration: Date.now() - start,
182
+ success: true
183
+ });
184
+ onActivity?.();
185
+ return result;
186
+ } catch (err) {
187
+ parentLogAction(session, {
188
+ action: actMethod,
189
+ source: "agent",
190
+ selector: hint,
191
+ duration: Date.now() - start,
192
+ success: false,
193
+ error: err.message
194
+ });
195
+ onActivity?.();
196
+ throw err;
197
+ } finally {
198
+ try {
199
+ await page.evaluate(() => {
200
+ window.__btApiActionInProgress = false;
201
+ });
202
+ } catch {
203
+ }
204
+ }
205
+ };
206
+ }
207
+ for (const method of LOCATOR_RETURNING_METHODS) {
208
+ if (typeof locator[method] !== "function") continue;
209
+ const origMethod = locator[method].bind(locator);
210
+ locator[method] = (...args) => {
211
+ const child = origMethod(...args);
212
+ const childHint = args.length > 0 ? `${hint}.${formatHint(method, args)}` : `${hint}.${method}()`;
213
+ return wrapLocator(child, childHint, session, page, onActivity);
214
+ };
215
+ }
216
+ if (typeof locator.nth === "function") {
217
+ const origNth = locator.nth.bind(locator);
218
+ locator.nth = (index) => {
219
+ const child = origNth(index);
220
+ const childHint = `${hint}.nth(${index})`;
221
+ return wrapLocator(child, childHint, session, page, onActivity);
222
+ };
223
+ }
224
+ if (typeof locator.all === "function") {
225
+ const origAll = locator.all.bind(locator);
226
+ locator.all = async () => {
227
+ const items = await origAll();
228
+ return items.map((item, i) => {
229
+ const childHint = `${hint}.all()[${i}]`;
230
+ return wrapLocator(item, childHint, session, page, onActivity);
231
+ });
232
+ };
233
+ }
234
+ return locator;
235
+ }
236
+ function wrapPageForActionLogging(page, session, onActivity) {
237
+ const PAGE_ACTIONS = [
238
+ "click",
239
+ "dblclick",
240
+ "fill",
241
+ "type",
242
+ "press",
243
+ "check",
244
+ "uncheck",
245
+ "selectOption",
246
+ "hover",
247
+ "focus"
248
+ ];
249
+ const NAV_ACTIONS = ["goto", "reload", "goBack", "goForward"];
250
+ for (const method of PAGE_ACTIONS) {
251
+ const orig = page[method].bind(page);
252
+ page[method] = async (...args) => {
253
+ const start = Date.now();
254
+ try {
255
+ await page.evaluate(() => {
256
+ window.__btApiActionInProgress = true;
257
+ });
258
+ } catch {
259
+ }
260
+ try {
261
+ const result = await orig(...args);
262
+ parentLogAction(session, {
263
+ action: method,
264
+ source: "agent",
265
+ selector: typeof args[0] === "string" ? args[0] : void 0,
266
+ value: args[1] !== void 0 ? String(args[1]).slice(0, 100) : void 0,
267
+ duration: Date.now() - start,
268
+ success: true
269
+ });
270
+ onActivity?.();
271
+ return result;
272
+ } catch (err) {
273
+ parentLogAction(session, {
274
+ action: method,
275
+ source: "agent",
276
+ selector: typeof args[0] === "string" ? args[0] : void 0,
277
+ duration: Date.now() - start,
278
+ success: false,
279
+ error: err.message
280
+ });
281
+ onActivity?.();
282
+ throw err;
283
+ } finally {
284
+ try {
285
+ await page.evaluate(() => {
286
+ window.__btApiActionInProgress = false;
287
+ });
288
+ } catch {
289
+ }
290
+ }
291
+ };
292
+ }
293
+ for (const method of NAV_ACTIONS) {
294
+ const orig = page[method].bind(page);
295
+ page[method] = async (...args) => {
296
+ const start = Date.now();
297
+ try {
298
+ const result = await orig(...args);
299
+ parentLogAction(session, {
300
+ action: method,
301
+ source: "agent",
302
+ url: typeof args[0] === "string" ? args[0] : page.url(),
303
+ duration: Date.now() - start,
304
+ success: true
305
+ });
306
+ onActivity?.();
307
+ return result;
308
+ } catch (err) {
309
+ parentLogAction(session, {
310
+ action: method,
311
+ source: "agent",
312
+ url: typeof args[0] === "string" ? args[0] : void 0,
313
+ duration: Date.now() - start,
314
+ success: false,
315
+ error: err.message
316
+ });
317
+ onActivity?.();
318
+ throw err;
319
+ }
320
+ };
321
+ }
322
+ const LOCATOR_FACTORIES = [
323
+ "locator",
324
+ "getByRole",
325
+ "getByText",
326
+ "getByLabel",
327
+ "getByPlaceholder",
328
+ "getByAltText",
329
+ "getByTitle",
330
+ "getByTestId"
331
+ ];
332
+ for (const factory of LOCATOR_FACTORIES) {
333
+ const orig = page[factory].bind(page);
334
+ page[factory] = (...factoryArgs) => {
335
+ const locator = orig(...factoryArgs);
336
+ const hint = formatHint(factory, factoryArgs);
337
+ return wrapLocator(locator, hint, session, page, onActivity);
338
+ };
339
+ }
340
+ }
341
+ export {
342
+ clearActionLog,
343
+ clearNetworkLog,
344
+ formatActionEntry,
345
+ formatNetworkEntry,
346
+ parentLogAction,
347
+ readActionLog,
348
+ readNetworkLog,
349
+ wrapPageForActionLogging
350
+ };
@@ -0,0 +1,13 @@
1
+ import { runLibrettoCLI } from "./cli.js";
2
+ import {
3
+ maybeConfigureLLMClientFactoryFromEnv,
4
+ setLLMClientFactory
5
+ } from "./core/context.js";
6
+ import { runClose } from "./commands/browser.js";
7
+ maybeConfigureLLMClientFactoryFromEnv();
8
+ void runLibrettoCLI();
9
+ export {
10
+ runClose,
11
+ runLibrettoCLI,
12
+ setLLMClientFactory
13
+ };
@@ -0,0 +1,204 @@
1
+ import { appendFileSync, existsSync } from "node:fs";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { cwd } from "node:process";
4
+ import { isAbsolute, resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import {
7
+ launchBrowser
8
+ } from "../../index.js";
9
+ import { getProfilePath, normalizeDomain } from "../core/browser.js";
10
+ import { getSessionDir } from "../core/context.js";
11
+ import { getPauseSignalPaths, removeSignalIfExists } from "../core/pause-signals.js";
12
+ const LIBRETTO_WORKFLOW_BRAND = /* @__PURE__ */ Symbol.for("libretto.workflow");
13
+ const RESUME_POLL_INTERVAL_MS = 250;
14
+ function mirrorStdoutToFile(filePath) {
15
+ const stdout = process.stdout;
16
+ const originalWrite = stdout.write.bind(stdout);
17
+ stdout.write = ((chunk, ...args) => {
18
+ try {
19
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8");
20
+ appendFileSync(filePath, buffer);
21
+ } catch {
22
+ }
23
+ return originalWrite(chunk, ...args);
24
+ });
25
+ return () => {
26
+ stdout.write = originalWrite;
27
+ };
28
+ }
29
+ async function waitForResumeSignal(args) {
30
+ const { pausedSignalPath, resumeSignalPath } = args.signalPaths;
31
+ await mkdir(getSessionDir(args.session), { recursive: true });
32
+ await removeSignalIfExists(resumeSignalPath);
33
+ await writeFile(
34
+ pausedSignalPath,
35
+ JSON.stringify(args.details, null, 2),
36
+ "utf8"
37
+ );
38
+ await args.onPaused?.(args.details);
39
+ while (!existsSync(resumeSignalPath)) {
40
+ await new Promise(
41
+ (resolveWait) => setTimeout(resolveWait, RESUME_POLL_INTERVAL_MS)
42
+ );
43
+ }
44
+ await removeSignalIfExists(resumeSignalPath);
45
+ await removeSignalIfExists(pausedSignalPath);
46
+ }
47
+ function isLoadedLibrettoWorkflow(value) {
48
+ if (!value || typeof value !== "object") return false;
49
+ const candidate = value;
50
+ return candidate[LIBRETTO_WORKFLOW_BRAND] === true && typeof candidate.run === "function" && !!candidate.metadata && typeof candidate.metadata === "object";
51
+ }
52
+ function resolveLocalAuthProfilePath(domain) {
53
+ return getProfilePath(normalizeDomain(domain));
54
+ }
55
+ function resolveWorkflowStorageStatePath(workflow) {
56
+ const authProfile = workflow.metadata.authProfile;
57
+ if (authProfile?.type !== "local") {
58
+ return void 0;
59
+ }
60
+ return resolveLocalAuthProfilePath(authProfile.domain);
61
+ }
62
+ function getMissingLocalAuthProfileError(args) {
63
+ const normalizedDomain = normalizeDomain(args.domain);
64
+ return [
65
+ `Local auth profile not found for domain "${normalizedDomain}".`,
66
+ `Expected profile file: ${args.profilePath}`,
67
+ "To create it:",
68
+ ` 1. libretto-cli open https://${normalizedDomain} --headed --session ${args.session}`,
69
+ " 2. Log in manually in the browser window.",
70
+ ` 3. libretto-cli save ${normalizedDomain} --session ${args.session}`
71
+ ].join("\n");
72
+ }
73
+ function getAbsoluteIntegrationPath(integrationPath) {
74
+ const absolutePath = isAbsolute(integrationPath) ? integrationPath : resolve(cwd(), integrationPath);
75
+ if (!existsSync(absolutePath)) {
76
+ throw new Error(`Integration file does not exist: ${absolutePath}`);
77
+ }
78
+ return absolutePath;
79
+ }
80
+ async function loadWorkflowExport(absolutePath, exportName) {
81
+ let loadedModule;
82
+ try {
83
+ loadedModule = await import(pathToFileURL(absolutePath).href);
84
+ } catch (error) {
85
+ throw new Error(
86
+ `Failed to import integration module at ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
87
+ );
88
+ }
89
+ const targetExport = loadedModule[exportName];
90
+ if (!targetExport) {
91
+ const availableExports = Object.keys(loadedModule);
92
+ const detail = availableExports.length > 0 ? ` Available exports: ${availableExports.join(", ")}` : " The module has no exports.";
93
+ throw new Error(
94
+ `Export "${exportName}" was not found in ${absolutePath}.${detail}`
95
+ );
96
+ }
97
+ if (!isLoadedLibrettoWorkflow(targetExport)) {
98
+ throw new Error(
99
+ `Export "${exportName}" in ${absolutePath} must be a Libretto workflow instance. Use workflow(...) from "libretto".`
100
+ );
101
+ }
102
+ return targetExport;
103
+ }
104
+ async function runIntegrationInternal(args, options) {
105
+ const { logger } = options;
106
+ const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
107
+ const workflow = await loadWorkflowExport(absolutePath, args.exportName);
108
+ const signalPaths = getPauseSignalPaths(args.session);
109
+ await removeSignalIfExists(signalPaths.pausedSignalPath);
110
+ await removeSignalIfExists(signalPaths.resumeSignalPath);
111
+ await removeSignalIfExists(signalPaths.completedSignalPath);
112
+ await removeSignalIfExists(signalPaths.failedSignalPath);
113
+ await removeSignalIfExists(signalPaths.outputSignalPath);
114
+ const restoreStdout = mirrorStdoutToFile(signalPaths.outputSignalPath);
115
+ console.log(
116
+ `Running integration "${args.exportName}" from ${absolutePath} (${args.headless ? "headless" : "headed"})...`
117
+ );
118
+ const integrationLogger = logger.withScope("integration-run", {
119
+ integrationPath: absolutePath,
120
+ integrationExport: args.exportName,
121
+ session: args.session
122
+ });
123
+ const authProfile = workflow.metadata.authProfile;
124
+ const storageStatePath = resolveWorkflowStorageStatePath(workflow);
125
+ if (authProfile?.type === "local" && storageStatePath && !existsSync(storageStatePath)) {
126
+ throw new Error(
127
+ getMissingLocalAuthProfileError({
128
+ domain: authProfile.domain,
129
+ profilePath: storageStatePath,
130
+ session: args.session
131
+ })
132
+ );
133
+ }
134
+ const browserSession = await launchBrowser({
135
+ sessionName: args.session,
136
+ headless: args.headless,
137
+ storageStatePath
138
+ });
139
+ const workflowContext = {
140
+ logger: integrationLogger,
141
+ page: browserSession.page,
142
+ context: browserSession.context,
143
+ browser: browserSession.browser,
144
+ session: args.session,
145
+ integrationPath: absolutePath,
146
+ exportName: args.exportName,
147
+ headless: args.headless,
148
+ debug: args.debug,
149
+ pause: async () => {
150
+ const details = {
151
+ sessionName: args.session,
152
+ pausedAt: (/* @__PURE__ */ new Date()).toISOString(),
153
+ url: browserSession.page.url()
154
+ };
155
+ console.log(`[pause] Paused at ${details.url}`);
156
+ console.log("[pause] Waiting for resume signal...");
157
+ await waitForResumeSignal({
158
+ signalPaths,
159
+ session: args.session,
160
+ details,
161
+ onPaused: options.onPaused
162
+ });
163
+ console.log("[pause] Resume signal received. Continuing workflow...");
164
+ }
165
+ };
166
+ try {
167
+ try {
168
+ await workflow.run(workflowContext, args.params ?? {});
169
+ } catch (error) {
170
+ await writeFile(
171
+ signalPaths.failedSignalPath,
172
+ JSON.stringify(
173
+ {
174
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
175
+ message: error instanceof Error ? error.message : String(error)
176
+ },
177
+ null,
178
+ 2
179
+ ),
180
+ "utf8"
181
+ );
182
+ throw error;
183
+ }
184
+ await writeFile(
185
+ signalPaths.completedSignalPath,
186
+ JSON.stringify({ completedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
187
+ "utf8"
188
+ );
189
+ console.log("Integration completed.");
190
+ return { status: "completed" };
191
+ } finally {
192
+ restoreStdout();
193
+ await browserSession.close();
194
+ }
195
+ }
196
+ async function runIntegrationFromFileInWorker(args, logger, onPaused) {
197
+ return await runIntegrationInternal(args, {
198
+ logger,
199
+ onPaused
200
+ });
201
+ }
202
+ export {
203
+ runIntegrationFromFileInWorker
204
+ };
@@ -0,0 +1,83 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { runIntegrationFromFileInWorker } from "./run-integration-runtime.js";
3
+ import {
4
+ ensureLibrettoSetup,
5
+ withSessionLogger
6
+ } from "../core/context.js";
7
+ import { getPauseSignalPaths } from "../core/pause-signals.js";
8
+ function sendMessage(message) {
9
+ if (typeof process.send !== "function" || !process.connected) return;
10
+ try {
11
+ process.send(message);
12
+ } catch {
13
+ }
14
+ }
15
+ function parseWorkerRequest(argv) {
16
+ const rawPayload = argv[2];
17
+ if (!rawPayload) {
18
+ throw new Error("Missing worker payload argument.");
19
+ }
20
+ let parsed;
21
+ try {
22
+ parsed = JSON.parse(rawPayload);
23
+ } catch (error) {
24
+ throw new Error(
25
+ `Invalid worker payload JSON: ${error instanceof Error ? error.message : String(error)}`
26
+ );
27
+ }
28
+ if (!parsed || typeof parsed !== "object") {
29
+ throw new Error("Worker payload must be an object.");
30
+ }
31
+ const candidate = parsed;
32
+ if (typeof candidate.integrationPath !== "string" || typeof candidate.exportName !== "string" || typeof candidate.session !== "string" || typeof candidate.headless !== "boolean" || typeof candidate.debug !== "boolean" || !("params" in candidate)) {
33
+ throw new Error("Worker payload is missing required fields.");
34
+ }
35
+ return {
36
+ integrationPath: candidate.integrationPath,
37
+ exportName: candidate.exportName,
38
+ session: candidate.session,
39
+ headless: candidate.headless,
40
+ debug: candidate.debug,
41
+ params: candidate.params
42
+ };
43
+ }
44
+ async function main() {
45
+ let request = null;
46
+ let exitCode = 0;
47
+ try {
48
+ request = parseWorkerRequest(process.argv);
49
+ const workerRequest = request;
50
+ ensureLibrettoSetup();
51
+ await withSessionLogger(workerRequest.session, async (logger) => {
52
+ await runIntegrationFromFileInWorker(
53
+ workerRequest,
54
+ logger,
55
+ async (details) => {
56
+ sendMessage({ type: "paused", details });
57
+ }
58
+ );
59
+ });
60
+ sendMessage({ type: "completed" });
61
+ } catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ if (request) {
64
+ const { failedSignalPath } = getPauseSignalPaths(request.session);
65
+ await writeFile(
66
+ failedSignalPath,
67
+ JSON.stringify(
68
+ {
69
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
70
+ message
71
+ },
72
+ null,
73
+ 2
74
+ ),
75
+ "utf8"
76
+ );
77
+ }
78
+ sendMessage({ type: "failed", message });
79
+ exitCode = 1;
80
+ }
81
+ process.exit(exitCode);
82
+ }
83
+ void main();