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.
- package/README.md +213 -17
- package/bin/libretto.mjs +18 -0
- package/dist/cli/cli.js +201 -0
- package/dist/cli/commands/ai.js +21 -0
- package/dist/cli/commands/browser.js +56 -0
- package/dist/cli/commands/execution.js +407 -0
- package/dist/cli/commands/logs.js +65 -0
- package/dist/cli/commands/snapshot.js +99 -0
- package/dist/cli/core/ai-config.js +149 -0
- package/dist/cli/core/browser.js +687 -0
- package/dist/cli/core/context.js +113 -0
- package/dist/cli/core/pause-signals.js +29 -0
- package/dist/cli/core/session.js +183 -0
- package/dist/cli/core/snapshot-analyzer.js +492 -0
- package/dist/cli/core/telemetry.js +350 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/workers/run-integration-runtime.js +204 -0
- package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
- package/dist/cli/workers/run-integration-worker.js +83 -0
- package/dist/index.cjs +127 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +110 -0
- package/dist/runtime/download/download.cjs +70 -0
- package/dist/runtime/download/download.d.cts +35 -0
- package/dist/runtime/download/download.d.ts +35 -0
- package/dist/runtime/download/download.js +45 -0
- package/dist/runtime/download/index.cjs +30 -0
- package/dist/runtime/download/index.d.cts +3 -0
- package/dist/runtime/download/index.d.ts +3 -0
- package/dist/runtime/download/index.js +8 -0
- package/dist/runtime/extract/extract.cjs +87 -0
- package/dist/runtime/extract/extract.d.cts +23 -0
- package/dist/runtime/extract/extract.d.ts +23 -0
- package/dist/runtime/extract/extract.js +63 -0
- package/dist/runtime/extract/index.cjs +28 -0
- package/dist/runtime/extract/index.d.cts +5 -0
- package/dist/runtime/extract/index.d.ts +5 -0
- package/dist/runtime/extract/index.js +4 -0
- package/dist/runtime/network/index.cjs +28 -0
- package/dist/runtime/network/index.d.cts +4 -0
- package/dist/runtime/network/index.d.ts +4 -0
- package/dist/runtime/network/index.js +6 -0
- package/dist/runtime/network/network.cjs +91 -0
- package/dist/runtime/network/network.d.cts +28 -0
- package/dist/runtime/network/network.d.ts +28 -0
- package/dist/runtime/network/network.js +67 -0
- package/dist/runtime/recovery/agent.cjs +218 -0
- package/dist/runtime/recovery/agent.d.cts +13 -0
- package/dist/runtime/recovery/agent.d.ts +13 -0
- package/dist/runtime/recovery/agent.js +194 -0
- package/dist/runtime/recovery/errors.cjs +122 -0
- package/dist/runtime/recovery/errors.d.cts +31 -0
- package/dist/runtime/recovery/errors.d.ts +31 -0
- package/dist/runtime/recovery/errors.js +98 -0
- package/dist/runtime/recovery/index.cjs +34 -0
- package/dist/runtime/recovery/index.d.cts +7 -0
- package/dist/runtime/recovery/index.d.ts +7 -0
- package/dist/runtime/recovery/index.js +10 -0
- package/dist/runtime/recovery/recovery.cjs +53 -0
- package/dist/runtime/recovery/recovery.d.cts +12 -0
- package/dist/runtime/recovery/recovery.d.ts +12 -0
- package/dist/runtime/recovery/recovery.js +29 -0
- package/dist/runtime/step/index.cjs +31 -0
- package/dist/runtime/step/index.d.cts +7 -0
- package/dist/runtime/step/index.d.ts +7 -0
- package/dist/runtime/step/index.js +6 -0
- package/dist/runtime/step/runner.cjs +208 -0
- package/dist/runtime/step/runner.d.cts +16 -0
- package/dist/runtime/step/runner.d.ts +16 -0
- package/dist/runtime/step/runner.js +187 -0
- package/dist/runtime/step/step.cjs +67 -0
- package/dist/runtime/step/step.d.cts +23 -0
- package/dist/runtime/step/step.d.ts +23 -0
- package/dist/runtime/step/step.js +43 -0
- package/dist/runtime/step/types.cjs +16 -0
- package/dist/runtime/step/types.d.cts +72 -0
- package/dist/runtime/step/types.d.ts +72 -0
- package/dist/runtime/step/types.js +0 -0
- package/dist/shared/config/config.cjs +44 -0
- package/dist/shared/config/config.d.cts +10 -0
- package/dist/shared/config/config.d.ts +10 -0
- package/dist/shared/config/config.js +18 -0
- package/dist/shared/config/index.cjs +32 -0
- package/dist/shared/config/index.d.cts +1 -0
- package/dist/shared/config/index.d.ts +1 -0
- package/dist/shared/config/index.js +10 -0
- package/dist/shared/debug/index.cjs +32 -0
- package/dist/shared/debug/index.d.cts +2 -0
- package/dist/shared/debug/index.d.ts +2 -0
- package/dist/shared/debug/index.js +10 -0
- package/dist/shared/debug/pause.cjs +56 -0
- package/dist/shared/debug/pause.d.cts +23 -0
- package/dist/shared/debug/pause.d.ts +23 -0
- package/dist/shared/debug/pause.js +30 -0
- package/dist/shared/instrumentation/errors.cjs +81 -0
- package/dist/shared/instrumentation/errors.d.cts +12 -0
- package/dist/shared/instrumentation/errors.d.ts +12 -0
- package/dist/shared/instrumentation/errors.js +57 -0
- package/dist/shared/instrumentation/index.cjs +35 -0
- package/dist/shared/instrumentation/index.d.cts +6 -0
- package/dist/shared/instrumentation/index.d.ts +6 -0
- package/dist/shared/instrumentation/index.js +12 -0
- package/dist/shared/instrumentation/instrument.cjs +206 -0
- package/dist/shared/instrumentation/instrument.d.cts +32 -0
- package/dist/shared/instrumentation/instrument.d.ts +32 -0
- package/dist/shared/instrumentation/instrument.js +190 -0
- package/dist/shared/llm/client.cjs +139 -0
- package/dist/shared/llm/client.d.cts +6 -0
- package/dist/shared/llm/client.d.ts +6 -0
- package/dist/shared/llm/client.js +115 -0
- package/dist/shared/llm/index.cjs +28 -0
- package/dist/shared/llm/index.d.cts +3 -0
- package/dist/shared/llm/index.d.ts +3 -0
- package/dist/shared/llm/index.js +4 -0
- package/dist/shared/llm/types.cjs +16 -0
- package/dist/shared/llm/types.d.cts +34 -0
- package/dist/shared/llm/types.d.ts +34 -0
- package/dist/shared/llm/types.js +0 -0
- package/dist/shared/logger/index.cjs +35 -0
- package/dist/shared/logger/index.d.cts +2 -0
- package/dist/shared/logger/index.d.ts +2 -0
- package/dist/shared/logger/index.js +12 -0
- package/dist/shared/logger/logger.cjs +200 -0
- package/dist/shared/logger/logger.d.cts +70 -0
- package/dist/shared/logger/logger.d.ts +70 -0
- package/dist/shared/logger/logger.js +176 -0
- package/dist/shared/logger/sinks.cjs +160 -0
- package/dist/shared/logger/sinks.d.cts +9 -0
- package/dist/shared/logger/sinks.d.ts +9 -0
- package/dist/shared/logger/sinks.js +124 -0
- package/dist/shared/paths/paths.cjs +104 -0
- package/dist/shared/paths/paths.d.cts +10 -0
- package/dist/shared/paths/paths.d.ts +10 -0
- package/dist/shared/paths/paths.js +73 -0
- package/dist/shared/run/api.cjs +35 -0
- package/dist/shared/run/api.d.cts +3 -0
- package/dist/shared/run/api.d.ts +3 -0
- package/dist/shared/run/api.js +12 -0
- package/dist/shared/run/browser.cjs +98 -0
- package/dist/shared/run/browser.d.cts +22 -0
- package/dist/shared/run/browser.d.ts +22 -0
- package/dist/shared/run/browser.js +74 -0
- package/dist/shared/state/index.cjs +38 -0
- package/dist/shared/state/index.d.cts +2 -0
- package/dist/shared/state/index.d.ts +2 -0
- package/dist/shared/state/index.js +16 -0
- package/dist/shared/state/session-state.cjs +85 -0
- package/dist/shared/state/session-state.d.cts +34 -0
- package/dist/shared/state/session-state.d.ts +34 -0
- package/dist/shared/state/session-state.js +56 -0
- package/dist/shared/visualization/ghost-cursor.cjs +174 -0
- package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
- package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
- package/dist/shared/visualization/ghost-cursor.js +145 -0
- package/dist/shared/visualization/highlight.cjs +134 -0
- package/dist/shared/visualization/highlight.d.cts +22 -0
- package/dist/shared/visualization/highlight.d.ts +22 -0
- package/dist/shared/visualization/highlight.js +108 -0
- package/dist/shared/visualization/index.cjs +45 -0
- package/dist/shared/visualization/index.d.cts +3 -0
- package/dist/shared/visualization/index.d.ts +3 -0
- package/dist/shared/visualization/index.js +24 -0
- package/dist/shared/workflow/workflow.cjs +47 -0
- package/dist/shared/workflow/workflow.d.cts +33 -0
- package/dist/shared/workflow/workflow.d.ts +33 -0
- package/dist/shared/workflow/workflow.js +21 -0
- package/package.json +123 -26
- package/.npmignore +0 -2
- package/bin/libretto +0 -31
- package/lib/connect.js +0 -34
- package/lib/export.js +0 -224
- package/lib/import.js +0 -166
- package/lib/index.js +0 -8
- package/lib/log.js +0 -9
- package/lib/validate.js +0 -20
- package/makefile +0 -8
- package/src/connect.coffee +0 -25
- package/src/export.coffee +0 -222
- package/src/import.coffee +0 -166
- package/src/index.coffee +0 -3
- package/src/log.coffee +0 -3
- package/src/validate.coffee +0 -10
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { step } from './step.cjs';
|
|
2
|
+
export { Runner, createRunner } from './runner.cjs';
|
|
3
|
+
export { DebugBundle, RecoveryHandler, RunnerConfig, Step, StepContext, StepHandler, StepHistoryEntry, StepOptions } from './types.cjs';
|
|
4
|
+
import 'playwright';
|
|
5
|
+
import '../../shared/logger/logger.cjs';
|
|
6
|
+
import '../../shared/llm/types.cjs';
|
|
7
|
+
import 'zod';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { step } from './step.js';
|
|
2
|
+
export { Runner, createRunner } from './runner.js';
|
|
3
|
+
export { DebugBundle, RecoveryHandler, RunnerConfig, Step, StepContext, StepHandler, StepHistoryEntry, StepOptions } from './types.js';
|
|
4
|
+
import 'playwright';
|
|
5
|
+
import '../../shared/logger/logger.js';
|
|
6
|
+
import '../../shared/llm/types.js';
|
|
7
|
+
import 'zod';
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var runner_exports = {};
|
|
20
|
+
__export(runner_exports, {
|
|
21
|
+
createRunner: () => createRunner
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(runner_exports);
|
|
24
|
+
var import_node_fs = require("node:fs");
|
|
25
|
+
var import_node_path = require("node:path");
|
|
26
|
+
var import_logger = require("../../shared/logger/logger.js");
|
|
27
|
+
var import_sinks = require("../../shared/logger/sinks.js");
|
|
28
|
+
var import_config = require("../../shared/config/config.js");
|
|
29
|
+
var import_pause = require("../../shared/debug/pause.js");
|
|
30
|
+
var import_recovery = require("../recovery/recovery.js");
|
|
31
|
+
var import_paths = require("../../shared/paths/paths.js");
|
|
32
|
+
function createRunner(config = {}) {
|
|
33
|
+
const {
|
|
34
|
+
llmClient,
|
|
35
|
+
dryRun: dryRunOption,
|
|
36
|
+
debug: debugOption,
|
|
37
|
+
sessionName = "libretto",
|
|
38
|
+
logDir: configuredLogDir
|
|
39
|
+
} = config;
|
|
40
|
+
const dryRun = dryRunOption ?? (0, import_config.isDryRun)();
|
|
41
|
+
const debug = debugOption ?? false;
|
|
42
|
+
const logDir = configuredLogDir ?? (0, import_paths.ensureLibrettoRunnerLogDir)(sessionName);
|
|
43
|
+
return {
|
|
44
|
+
run: async (page, steps) => {
|
|
45
|
+
(0, import_node_fs.mkdirSync)(logDir, { recursive: true });
|
|
46
|
+
const logPath = (0, import_paths.getRunnerLogPathForDir)(logDir);
|
|
47
|
+
const logger = new import_logger.Logger().withSink((0, import_sinks.createFileLogSink)({ filePath: logPath })).withSink(import_sinks.prettyConsoleSink);
|
|
48
|
+
const stepHistory = [];
|
|
49
|
+
logger.info("runner:start", {
|
|
50
|
+
totalSteps: steps.length,
|
|
51
|
+
dryRun,
|
|
52
|
+
debug,
|
|
53
|
+
logDir
|
|
54
|
+
});
|
|
55
|
+
for (const step of steps) {
|
|
56
|
+
const stepLogger = logger.withScope(`step:${step.name}`);
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
if (dryRun && step.options.dryRun !== "execute") {
|
|
59
|
+
if (step.options.dryRun === "skip") {
|
|
60
|
+
stepLogger.info("skipped (dry-run)");
|
|
61
|
+
stepHistory.push({
|
|
62
|
+
name: step.name,
|
|
63
|
+
status: "skipped",
|
|
64
|
+
duration: 0
|
|
65
|
+
});
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (step.options.dryRun === "simulate" && step.options.simulate) {
|
|
69
|
+
stepLogger.info("simulating (dry-run)");
|
|
70
|
+
try {
|
|
71
|
+
await step.options.simulate({ logger: stepLogger });
|
|
72
|
+
} catch (simError) {
|
|
73
|
+
stepLogger.warn("simulate failed", { error: simError });
|
|
74
|
+
}
|
|
75
|
+
stepHistory.push({
|
|
76
|
+
name: step.name,
|
|
77
|
+
status: "simulated",
|
|
78
|
+
duration: Date.now() - startTime
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
stepLogger.info("skipped (dry-run, no simulate fn)");
|
|
83
|
+
stepHistory.push({
|
|
84
|
+
name: step.name,
|
|
85
|
+
status: "skipped",
|
|
86
|
+
duration: 0
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
await captureScreenshot(page, (0, import_node_path.join)(logDir, `${step.name}-start.png`), stepLogger);
|
|
91
|
+
stepLogger.info("start");
|
|
92
|
+
try {
|
|
93
|
+
await (0, import_recovery.attemptWithRecovery)(
|
|
94
|
+
page,
|
|
95
|
+
() => step.handler({ page, logger: stepLogger, config: { dryRun, debug, logDir } }),
|
|
96
|
+
stepLogger,
|
|
97
|
+
llmClient
|
|
98
|
+
);
|
|
99
|
+
stepHistory.push({
|
|
100
|
+
name: step.name,
|
|
101
|
+
status: "completed",
|
|
102
|
+
duration: Date.now() - startTime
|
|
103
|
+
});
|
|
104
|
+
stepLogger.info("end", { status: "completed", duration: Date.now() - startTime });
|
|
105
|
+
} catch (firstError) {
|
|
106
|
+
let recovered = false;
|
|
107
|
+
const customRecovery = step.options.recovery ?? {};
|
|
108
|
+
for (const [recoveryName, recoveryHandler] of Object.entries(customRecovery)) {
|
|
109
|
+
try {
|
|
110
|
+
stepLogger.info(`trying custom recovery: ${recoveryName}`);
|
|
111
|
+
await recoveryHandler({ page, logger: stepLogger });
|
|
112
|
+
await step.handler({ page, logger: stepLogger, config: { dryRun, debug, logDir } });
|
|
113
|
+
recovered = true;
|
|
114
|
+
stepHistory.push({
|
|
115
|
+
name: step.name,
|
|
116
|
+
status: "completed",
|
|
117
|
+
duration: Date.now() - startTime
|
|
118
|
+
});
|
|
119
|
+
stepLogger.info("end", {
|
|
120
|
+
status: "completed",
|
|
121
|
+
recoveredBy: recoveryName,
|
|
122
|
+
duration: Date.now() - startTime
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
} catch {
|
|
126
|
+
stepLogger.warn(`custom recovery "${recoveryName}" failed`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!recovered) {
|
|
130
|
+
stepHistory.push({
|
|
131
|
+
name: step.name,
|
|
132
|
+
status: "failed",
|
|
133
|
+
duration: Date.now() - startTime
|
|
134
|
+
});
|
|
135
|
+
const bundle = await generateDebugBundle(
|
|
136
|
+
page,
|
|
137
|
+
step.name,
|
|
138
|
+
firstError,
|
|
139
|
+
logDir,
|
|
140
|
+
logPath,
|
|
141
|
+
stepHistory,
|
|
142
|
+
stepLogger
|
|
143
|
+
);
|
|
144
|
+
stepLogger.info("step:debug-bundle", { path: bundle.bundlePath });
|
|
145
|
+
if (debug) {
|
|
146
|
+
await (0, import_pause.debugPause)({
|
|
147
|
+
page,
|
|
148
|
+
session: sessionName
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
throw firstError;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
await captureScreenshot(page, (0, import_node_path.join)(logDir, `${step.name}-end.png`), stepLogger);
|
|
155
|
+
}
|
|
156
|
+
logger.info("runner:complete", {
|
|
157
|
+
totalSteps: steps.length,
|
|
158
|
+
completed: stepHistory.filter((s) => s.status === "completed").length,
|
|
159
|
+
skipped: stepHistory.filter((s) => s.status === "skipped").length,
|
|
160
|
+
simulated: stepHistory.filter((s) => s.status === "simulated").length
|
|
161
|
+
});
|
|
162
|
+
await logger.close();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function captureScreenshot(page, filePath, logger) {
|
|
167
|
+
try {
|
|
168
|
+
const buffer = await page.screenshot({ fullPage: false, timeout: 5e3 });
|
|
169
|
+
(0, import_node_fs.writeFileSync)(filePath, buffer);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logger.warn("Failed to capture screenshot", {
|
|
172
|
+
path: filePath,
|
|
173
|
+
error: err instanceof Error ? err.message : String(err)
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function generateDebugBundle(page, stepName, error, logDir, logPath, stepHistory, logger) {
|
|
178
|
+
const screenshotPath = (0, import_node_path.join)(logDir, `${stepName}-error.png`);
|
|
179
|
+
const domPath = (0, import_node_path.join)(logDir, `${stepName}-error.html`);
|
|
180
|
+
const bundlePath = (0, import_node_path.join)(logDir, `${stepName}-debug-bundle.json`);
|
|
181
|
+
await captureScreenshot(page, screenshotPath, logger);
|
|
182
|
+
try {
|
|
183
|
+
const html = await page.content();
|
|
184
|
+
(0, import_node_fs.writeFileSync)(domPath, html);
|
|
185
|
+
} catch (domErr) {
|
|
186
|
+
logger.warn("Failed to capture DOM for debug bundle", {
|
|
187
|
+
error: domErr instanceof Error ? domErr.message : String(domErr)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const pageUrl = page.isClosed() ? "" : page.url();
|
|
191
|
+
const bundle = {
|
|
192
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
+
step: stepName,
|
|
194
|
+
error: error instanceof Error ? error.message : String(error),
|
|
195
|
+
stacktrace: error instanceof Error ? error.stack ?? "" : "",
|
|
196
|
+
screenshotPath,
|
|
197
|
+
domPath,
|
|
198
|
+
logPath,
|
|
199
|
+
stepHistory,
|
|
200
|
+
pageUrl
|
|
201
|
+
};
|
|
202
|
+
(0, import_node_fs.writeFileSync)(bundlePath, JSON.stringify(bundle, null, 2));
|
|
203
|
+
return { bundlePath };
|
|
204
|
+
}
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
createRunner
|
|
208
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { Step, RunnerConfig } from './types.cjs';
|
|
3
|
+
import '../../shared/logger/logger.cjs';
|
|
4
|
+
import '../../shared/llm/types.cjs';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
type Runner = {
|
|
8
|
+
run: (page: Page, steps: Step[]) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Creates a step runner that executes a sequence of steps with logging,
|
|
12
|
+
* recovery, dry-run support, and debug bundle generation.
|
|
13
|
+
*/
|
|
14
|
+
declare function createRunner(config?: RunnerConfig): Runner;
|
|
15
|
+
|
|
16
|
+
export { type Runner, createRunner };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { Step, RunnerConfig } from './types.js';
|
|
3
|
+
import '../../shared/logger/logger.js';
|
|
4
|
+
import '../../shared/llm/types.js';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
type Runner = {
|
|
8
|
+
run: (page: Page, steps: Step[]) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Creates a step runner that executes a sequence of steps with logging,
|
|
12
|
+
* recovery, dry-run support, and debug bundle generation.
|
|
13
|
+
*/
|
|
14
|
+
declare function createRunner(config?: RunnerConfig): Runner;
|
|
15
|
+
|
|
16
|
+
export { type Runner, createRunner };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Logger } from "../../shared/logger/logger.js";
|
|
4
|
+
import { createFileLogSink, prettyConsoleSink } from "../../shared/logger/sinks.js";
|
|
5
|
+
import { isDryRun } from "../../shared/config/config.js";
|
|
6
|
+
import { debugPause } from "../../shared/debug/pause.js";
|
|
7
|
+
import { attemptWithRecovery } from "../recovery/recovery.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureLibrettoRunnerLogDir,
|
|
10
|
+
getRunnerLogPathForDir
|
|
11
|
+
} from "../../shared/paths/paths.js";
|
|
12
|
+
function createRunner(config = {}) {
|
|
13
|
+
const {
|
|
14
|
+
llmClient,
|
|
15
|
+
dryRun: dryRunOption,
|
|
16
|
+
debug: debugOption,
|
|
17
|
+
sessionName = "libretto",
|
|
18
|
+
logDir: configuredLogDir
|
|
19
|
+
} = config;
|
|
20
|
+
const dryRun = dryRunOption ?? isDryRun();
|
|
21
|
+
const debug = debugOption ?? false;
|
|
22
|
+
const logDir = configuredLogDir ?? ensureLibrettoRunnerLogDir(sessionName);
|
|
23
|
+
return {
|
|
24
|
+
run: async (page, steps) => {
|
|
25
|
+
mkdirSync(logDir, { recursive: true });
|
|
26
|
+
const logPath = getRunnerLogPathForDir(logDir);
|
|
27
|
+
const logger = new Logger().withSink(createFileLogSink({ filePath: logPath })).withSink(prettyConsoleSink);
|
|
28
|
+
const stepHistory = [];
|
|
29
|
+
logger.info("runner:start", {
|
|
30
|
+
totalSteps: steps.length,
|
|
31
|
+
dryRun,
|
|
32
|
+
debug,
|
|
33
|
+
logDir
|
|
34
|
+
});
|
|
35
|
+
for (const step of steps) {
|
|
36
|
+
const stepLogger = logger.withScope(`step:${step.name}`);
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
if (dryRun && step.options.dryRun !== "execute") {
|
|
39
|
+
if (step.options.dryRun === "skip") {
|
|
40
|
+
stepLogger.info("skipped (dry-run)");
|
|
41
|
+
stepHistory.push({
|
|
42
|
+
name: step.name,
|
|
43
|
+
status: "skipped",
|
|
44
|
+
duration: 0
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (step.options.dryRun === "simulate" && step.options.simulate) {
|
|
49
|
+
stepLogger.info("simulating (dry-run)");
|
|
50
|
+
try {
|
|
51
|
+
await step.options.simulate({ logger: stepLogger });
|
|
52
|
+
} catch (simError) {
|
|
53
|
+
stepLogger.warn("simulate failed", { error: simError });
|
|
54
|
+
}
|
|
55
|
+
stepHistory.push({
|
|
56
|
+
name: step.name,
|
|
57
|
+
status: "simulated",
|
|
58
|
+
duration: Date.now() - startTime
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
stepLogger.info("skipped (dry-run, no simulate fn)");
|
|
63
|
+
stepHistory.push({
|
|
64
|
+
name: step.name,
|
|
65
|
+
status: "skipped",
|
|
66
|
+
duration: 0
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
await captureScreenshot(page, join(logDir, `${step.name}-start.png`), stepLogger);
|
|
71
|
+
stepLogger.info("start");
|
|
72
|
+
try {
|
|
73
|
+
await attemptWithRecovery(
|
|
74
|
+
page,
|
|
75
|
+
() => step.handler({ page, logger: stepLogger, config: { dryRun, debug, logDir } }),
|
|
76
|
+
stepLogger,
|
|
77
|
+
llmClient
|
|
78
|
+
);
|
|
79
|
+
stepHistory.push({
|
|
80
|
+
name: step.name,
|
|
81
|
+
status: "completed",
|
|
82
|
+
duration: Date.now() - startTime
|
|
83
|
+
});
|
|
84
|
+
stepLogger.info("end", { status: "completed", duration: Date.now() - startTime });
|
|
85
|
+
} catch (firstError) {
|
|
86
|
+
let recovered = false;
|
|
87
|
+
const customRecovery = step.options.recovery ?? {};
|
|
88
|
+
for (const [recoveryName, recoveryHandler] of Object.entries(customRecovery)) {
|
|
89
|
+
try {
|
|
90
|
+
stepLogger.info(`trying custom recovery: ${recoveryName}`);
|
|
91
|
+
await recoveryHandler({ page, logger: stepLogger });
|
|
92
|
+
await step.handler({ page, logger: stepLogger, config: { dryRun, debug, logDir } });
|
|
93
|
+
recovered = true;
|
|
94
|
+
stepHistory.push({
|
|
95
|
+
name: step.name,
|
|
96
|
+
status: "completed",
|
|
97
|
+
duration: Date.now() - startTime
|
|
98
|
+
});
|
|
99
|
+
stepLogger.info("end", {
|
|
100
|
+
status: "completed",
|
|
101
|
+
recoveredBy: recoveryName,
|
|
102
|
+
duration: Date.now() - startTime
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
} catch {
|
|
106
|
+
stepLogger.warn(`custom recovery "${recoveryName}" failed`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!recovered) {
|
|
110
|
+
stepHistory.push({
|
|
111
|
+
name: step.name,
|
|
112
|
+
status: "failed",
|
|
113
|
+
duration: Date.now() - startTime
|
|
114
|
+
});
|
|
115
|
+
const bundle = await generateDebugBundle(
|
|
116
|
+
page,
|
|
117
|
+
step.name,
|
|
118
|
+
firstError,
|
|
119
|
+
logDir,
|
|
120
|
+
logPath,
|
|
121
|
+
stepHistory,
|
|
122
|
+
stepLogger
|
|
123
|
+
);
|
|
124
|
+
stepLogger.info("step:debug-bundle", { path: bundle.bundlePath });
|
|
125
|
+
if (debug) {
|
|
126
|
+
await debugPause({
|
|
127
|
+
page,
|
|
128
|
+
session: sessionName
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
throw firstError;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
await captureScreenshot(page, join(logDir, `${step.name}-end.png`), stepLogger);
|
|
135
|
+
}
|
|
136
|
+
logger.info("runner:complete", {
|
|
137
|
+
totalSteps: steps.length,
|
|
138
|
+
completed: stepHistory.filter((s) => s.status === "completed").length,
|
|
139
|
+
skipped: stepHistory.filter((s) => s.status === "skipped").length,
|
|
140
|
+
simulated: stepHistory.filter((s) => s.status === "simulated").length
|
|
141
|
+
});
|
|
142
|
+
await logger.close();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function captureScreenshot(page, filePath, logger) {
|
|
147
|
+
try {
|
|
148
|
+
const buffer = await page.screenshot({ fullPage: false, timeout: 5e3 });
|
|
149
|
+
writeFileSync(filePath, buffer);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
logger.warn("Failed to capture screenshot", {
|
|
152
|
+
path: filePath,
|
|
153
|
+
error: err instanceof Error ? err.message : String(err)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function generateDebugBundle(page, stepName, error, logDir, logPath, stepHistory, logger) {
|
|
158
|
+
const screenshotPath = join(logDir, `${stepName}-error.png`);
|
|
159
|
+
const domPath = join(logDir, `${stepName}-error.html`);
|
|
160
|
+
const bundlePath = join(logDir, `${stepName}-debug-bundle.json`);
|
|
161
|
+
await captureScreenshot(page, screenshotPath, logger);
|
|
162
|
+
try {
|
|
163
|
+
const html = await page.content();
|
|
164
|
+
writeFileSync(domPath, html);
|
|
165
|
+
} catch (domErr) {
|
|
166
|
+
logger.warn("Failed to capture DOM for debug bundle", {
|
|
167
|
+
error: domErr instanceof Error ? domErr.message : String(domErr)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const pageUrl = page.isClosed() ? "" : page.url();
|
|
171
|
+
const bundle = {
|
|
172
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
173
|
+
step: stepName,
|
|
174
|
+
error: error instanceof Error ? error.message : String(error),
|
|
175
|
+
stacktrace: error instanceof Error ? error.stack ?? "" : "",
|
|
176
|
+
screenshotPath,
|
|
177
|
+
domPath,
|
|
178
|
+
logPath,
|
|
179
|
+
stepHistory,
|
|
180
|
+
pageUrl
|
|
181
|
+
};
|
|
182
|
+
writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
|
|
183
|
+
return { bundlePath };
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
createRunner
|
|
187
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var step_exports = {};
|
|
20
|
+
__export(step_exports, {
|
|
21
|
+
step: () => step
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(step_exports);
|
|
24
|
+
function step(nameOrHandler, handlerOrOptions, maybeOptions) {
|
|
25
|
+
let name;
|
|
26
|
+
let handler;
|
|
27
|
+
let options;
|
|
28
|
+
if (typeof nameOrHandler === "string") {
|
|
29
|
+
name = nameOrHandler;
|
|
30
|
+
handler = handlerOrOptions;
|
|
31
|
+
options = maybeOptions ?? {};
|
|
32
|
+
} else {
|
|
33
|
+
name = `step-${_autoNameCounter++}`;
|
|
34
|
+
handler = nameOrHandler;
|
|
35
|
+
options = handlerOrOptions ?? {};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
handler,
|
|
40
|
+
options: {
|
|
41
|
+
dryRun: options.dryRun ?? "skip",
|
|
42
|
+
simulate: options.simulate,
|
|
43
|
+
recovery: options.recovery
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
let _autoNameCounter = 1;
|
|
48
|
+
step.extend = function extend(extendOptions) {
|
|
49
|
+
return function extendedStep(nameOrHandler, handlerOrOptions, maybeOptions) {
|
|
50
|
+
const baseStep = step(nameOrHandler, handlerOrOptions, maybeOptions);
|
|
51
|
+
const mergedRecovery = {
|
|
52
|
+
...extendOptions.recovery,
|
|
53
|
+
...baseStep.options.recovery
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
...baseStep,
|
|
57
|
+
options: {
|
|
58
|
+
...baseStep.options,
|
|
59
|
+
recovery: mergedRecovery
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
65
|
+
0 && (module.exports = {
|
|
66
|
+
step
|
|
67
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StepHandler, StepOptions, Step, RecoveryHandler } from './types.cjs';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
import '../../shared/logger/logger.cjs';
|
|
4
|
+
import '../../shared/llm/types.cjs';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a Step object from a name, handler, and options.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* step("login", async ({ page }) => { ... })
|
|
12
|
+
* step("login", async ({ page }) => { ... }, { dryRun: "execute" })
|
|
13
|
+
* step(async ({ page }) => { ... }) // auto-named
|
|
14
|
+
*/
|
|
15
|
+
declare function step(nameOrHandler: string | StepHandler, handlerOrOptions?: StepHandler | StepOptions, maybeOptions?: StepOptions): Step;
|
|
16
|
+
declare namespace step {
|
|
17
|
+
var extend: (extendOptions: ExtendOptions) => (nameOrHandler: string | StepHandler, handlerOrOptions?: StepHandler | StepOptions, maybeOptions?: StepOptions) => Step;
|
|
18
|
+
}
|
|
19
|
+
type ExtendOptions = {
|
|
20
|
+
recovery: Record<string, RecoveryHandler>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { type ExtendOptions, step };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StepHandler, StepOptions, Step, RecoveryHandler } from './types.js';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
import '../../shared/logger/logger.js';
|
|
4
|
+
import '../../shared/llm/types.js';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a Step object from a name, handler, and options.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* step("login", async ({ page }) => { ... })
|
|
12
|
+
* step("login", async ({ page }) => { ... }, { dryRun: "execute" })
|
|
13
|
+
* step(async ({ page }) => { ... }) // auto-named
|
|
14
|
+
*/
|
|
15
|
+
declare function step(nameOrHandler: string | StepHandler, handlerOrOptions?: StepHandler | StepOptions, maybeOptions?: StepOptions): Step;
|
|
16
|
+
declare namespace step {
|
|
17
|
+
var extend: (extendOptions: ExtendOptions) => (nameOrHandler: string | StepHandler, handlerOrOptions?: StepHandler | StepOptions, maybeOptions?: StepOptions) => Step;
|
|
18
|
+
}
|
|
19
|
+
type ExtendOptions = {
|
|
20
|
+
recovery: Record<string, RecoveryHandler>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { type ExtendOptions, step };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function step(nameOrHandler, handlerOrOptions, maybeOptions) {
|
|
2
|
+
let name;
|
|
3
|
+
let handler;
|
|
4
|
+
let options;
|
|
5
|
+
if (typeof nameOrHandler === "string") {
|
|
6
|
+
name = nameOrHandler;
|
|
7
|
+
handler = handlerOrOptions;
|
|
8
|
+
options = maybeOptions ?? {};
|
|
9
|
+
} else {
|
|
10
|
+
name = `step-${_autoNameCounter++}`;
|
|
11
|
+
handler = nameOrHandler;
|
|
12
|
+
options = handlerOrOptions ?? {};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
name,
|
|
16
|
+
handler,
|
|
17
|
+
options: {
|
|
18
|
+
dryRun: options.dryRun ?? "skip",
|
|
19
|
+
simulate: options.simulate,
|
|
20
|
+
recovery: options.recovery
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let _autoNameCounter = 1;
|
|
25
|
+
step.extend = function extend(extendOptions) {
|
|
26
|
+
return function extendedStep(nameOrHandler, handlerOrOptions, maybeOptions) {
|
|
27
|
+
const baseStep = step(nameOrHandler, handlerOrOptions, maybeOptions);
|
|
28
|
+
const mergedRecovery = {
|
|
29
|
+
...extendOptions.recovery,
|
|
30
|
+
...baseStep.options.recovery
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
...baseStep,
|
|
34
|
+
options: {
|
|
35
|
+
...baseStep.options,
|
|
36
|
+
recovery: mergedRecovery
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export {
|
|
42
|
+
step
|
|
43
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var types_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(types_exports);
|