libretto 0.2.3 → 0.2.5
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 +78 -184
- package/dist/cli/cli.js +8 -2
- package/dist/cli/commands/browser.js +26 -3
- package/dist/cli/commands/execution.js +50 -11
- package/dist/cli/commands/init.js +95 -0
- package/dist/cli/core/browser.js +131 -6
- package/dist/cli/core/context.js +5 -0
- package/dist/cli/core/session.js +13 -13
- package/dist/cli/workers/run-integration-runtime.js +64 -59
- package/dist/cli/workers/run-integration-worker-protocol.js +12 -0
- package/dist/cli/workers/run-integration-worker.js +13 -30
- package/dist/index.cjs +5 -12
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -15
- package/dist/shared/debug/index.cjs +4 -6
- package/dist/shared/debug/index.d.cts +1 -2
- package/dist/shared/debug/index.d.ts +1 -2
- package/dist/shared/debug/index.js +3 -8
- package/dist/shared/debug/pause.cjs +58 -24
- package/dist/shared/debug/pause.d.cts +13 -20
- package/dist/shared/debug/pause.d.ts +13 -20
- package/dist/shared/debug/pause.js +46 -21
- package/dist/shared/llm/ai-sdk-adapter.cjs +67 -0
- package/dist/shared/llm/ai-sdk-adapter.d.cts +22 -0
- package/dist/shared/llm/ai-sdk-adapter.d.ts +22 -0
- package/dist/shared/llm/ai-sdk-adapter.js +43 -0
- package/dist/shared/llm/index.cjs +5 -2
- package/dist/shared/llm/index.d.cts +2 -0
- package/dist/shared/llm/index.d.ts +2 -0
- package/dist/shared/llm/index.js +3 -1
- package/dist/shared/llm/types.d.cts +32 -0
- package/dist/shared/llm/types.d.ts +32 -0
- package/dist/shared/run/api.cjs +0 -7
- package/dist/shared/run/api.d.cts +0 -1
- package/dist/shared/run/api.d.ts +0 -1
- package/dist/shared/run/api.js +0 -8
- package/dist/shared/workflow/workflow.d.cts +11 -24
- package/dist/shared/workflow/workflow.d.ts +11 -24
- package/package.json +4 -10
- package/skill/SKILL.md +18 -5
- package/skill/code-generation-rules.md +7 -10
package/dist/cli/core/browser.js
CHANGED
|
@@ -12,12 +12,15 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
assertSessionAvailableForStart,
|
|
14
14
|
clearSessionState,
|
|
15
|
+
listSessionsWithStateFile,
|
|
15
16
|
readSessionStateOrThrow,
|
|
16
17
|
logFileForSession,
|
|
17
18
|
readSessionState,
|
|
18
19
|
writeSessionState
|
|
19
20
|
} from "./session.js";
|
|
20
21
|
import { installSessionTelemetry } from "./session-telemetry.js";
|
|
22
|
+
const CLOSE_WAIT_MS = 1500;
|
|
23
|
+
const FORCE_CLOSE_WAIT_MS = 300;
|
|
21
24
|
async function pickFreePort() {
|
|
22
25
|
return await new Promise((resolve2, reject) => {
|
|
23
26
|
const server = createServer();
|
|
@@ -488,16 +491,137 @@ async function runClose(session, logger) {
|
|
|
488
491
|
return;
|
|
489
492
|
}
|
|
490
493
|
logger.info("close-killing", { session, pid: state.pid, port: state.port });
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
} catch (err) {
|
|
494
|
-
logger.warn("close-kill-failed", { error: err, session, pid: state.pid });
|
|
495
|
-
}
|
|
496
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
494
|
+
sendSignalToProcessGroupOrPid(state.pid, "SIGTERM", logger, session);
|
|
495
|
+
await waitForCloseSignalWindow(CLOSE_WAIT_MS);
|
|
497
496
|
clearSessionState(session, logger);
|
|
498
497
|
logger.info("close-success", { session });
|
|
499
498
|
console.log(`Browser closed (session: ${session}).`);
|
|
500
499
|
}
|
|
500
|
+
function waitForCloseSignalWindow(ms) {
|
|
501
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
502
|
+
}
|
|
503
|
+
function isPidRunning(pid) {
|
|
504
|
+
try {
|
|
505
|
+
process.kill(pid, 0);
|
|
506
|
+
return true;
|
|
507
|
+
} catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function sendSignalToProcessGroupOrPid(pid, signal, logger, session) {
|
|
512
|
+
try {
|
|
513
|
+
process.kill(pid, signal);
|
|
514
|
+
logger.info("close-signal-pid", { session, pid, signal });
|
|
515
|
+
} catch (pidErr) {
|
|
516
|
+
const pidCode = pidErr.code;
|
|
517
|
+
if (pidCode !== "ESRCH") {
|
|
518
|
+
logger.warn("close-signal-pid-failed", {
|
|
519
|
+
session,
|
|
520
|
+
pid,
|
|
521
|
+
signal,
|
|
522
|
+
error: pidErr
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function formatSessionList(targets) {
|
|
528
|
+
return targets.map((target) => `"${target.session}"`).join(", ");
|
|
529
|
+
}
|
|
530
|
+
function resolveClosableSessions(logger) {
|
|
531
|
+
const sessions = listSessionsWithStateFile();
|
|
532
|
+
const closable = [];
|
|
533
|
+
let clearedUnreadableStates = 0;
|
|
534
|
+
for (const session of sessions) {
|
|
535
|
+
const state = readSessionState(session, logger);
|
|
536
|
+
if (!state) {
|
|
537
|
+
clearSessionState(session, logger);
|
|
538
|
+
clearedUnreadableStates += 1;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
closable.push({
|
|
542
|
+
session,
|
|
543
|
+
pid: state.pid,
|
|
544
|
+
port: state.port
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return { closable, clearedUnreadableStates };
|
|
548
|
+
}
|
|
549
|
+
function clearStoppedSessionStates(sessions, logger) {
|
|
550
|
+
let cleared = 0;
|
|
551
|
+
for (const session of sessions) {
|
|
552
|
+
if (!isPidRunning(session.pid)) {
|
|
553
|
+
clearSessionState(session.session, logger);
|
|
554
|
+
cleared += 1;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return cleared;
|
|
558
|
+
}
|
|
559
|
+
async function runCloseAll(logger, options) {
|
|
560
|
+
const force = Boolean(options?.force);
|
|
561
|
+
logger.info("close-all-start", { force });
|
|
562
|
+
const { closable, clearedUnreadableStates } = resolveClosableSessions(logger);
|
|
563
|
+
if (closable.length === 0) {
|
|
564
|
+
if (clearedUnreadableStates > 0) {
|
|
565
|
+
console.log(
|
|
566
|
+
`Cleared ${clearedUnreadableStates} unreadable session state file(s).`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
console.log("No browser sessions found.");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
for (const target of closable) {
|
|
573
|
+
logger.info("close-all-sigterm", {
|
|
574
|
+
session: target.session,
|
|
575
|
+
pid: target.pid,
|
|
576
|
+
port: target.port
|
|
577
|
+
});
|
|
578
|
+
sendSignalToProcessGroupOrPid(target.pid, "SIGTERM", logger, target.session);
|
|
579
|
+
}
|
|
580
|
+
await waitForCloseSignalWindow(CLOSE_WAIT_MS);
|
|
581
|
+
let survivors = closable.filter((target) => isPidRunning(target.pid));
|
|
582
|
+
if (survivors.length > 0 && !force) {
|
|
583
|
+
const closed = clearStoppedSessionStates(closable, logger);
|
|
584
|
+
throw new Error(
|
|
585
|
+
[
|
|
586
|
+
`Failed to close ${survivors.length} session(s) gracefully: ${formatSessionList(survivors)}.`,
|
|
587
|
+
`Closed ${closed} session(s).`,
|
|
588
|
+
"Retry with: libretto-cli close --all --force"
|
|
589
|
+
].join("\n")
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
let forceKilled = 0;
|
|
593
|
+
if (survivors.length > 0) {
|
|
594
|
+
for (const survivor of survivors) {
|
|
595
|
+
logger.warn("close-all-sigkill", {
|
|
596
|
+
session: survivor.session,
|
|
597
|
+
pid: survivor.pid
|
|
598
|
+
});
|
|
599
|
+
sendSignalToProcessGroupOrPid(survivor.pid, "SIGKILL", logger, survivor.session);
|
|
600
|
+
forceKilled += 1;
|
|
601
|
+
}
|
|
602
|
+
await waitForCloseSignalWindow(FORCE_CLOSE_WAIT_MS);
|
|
603
|
+
survivors = survivors.filter((target) => isPidRunning(target.pid));
|
|
604
|
+
if (survivors.length > 0) {
|
|
605
|
+
const closed = clearStoppedSessionStates(closable, logger);
|
|
606
|
+
throw new Error(
|
|
607
|
+
[
|
|
608
|
+
`Failed to force-close ${survivors.length} session(s): ${formatSessionList(survivors)}.`,
|
|
609
|
+
`Closed ${closed} session(s).`
|
|
610
|
+
].join("\n")
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
clearStoppedSessionStates(closable, logger);
|
|
615
|
+
if (clearedUnreadableStates > 0) {
|
|
616
|
+
console.log(
|
|
617
|
+
`Cleared ${clearedUnreadableStates} unreadable session state file(s).`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
console.log(`Closed ${closable.length} session(s).`);
|
|
621
|
+
if (forceKilled > 0) {
|
|
622
|
+
console.log(`Force-killed ${forceKilled} session(s).`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
501
625
|
function resolvePath(filePath) {
|
|
502
626
|
return join(process.cwd(), filePath);
|
|
503
627
|
}
|
|
@@ -517,6 +641,7 @@ export {
|
|
|
517
641
|
normalizeUrl,
|
|
518
642
|
resolvePath,
|
|
519
643
|
runClose,
|
|
644
|
+
runCloseAll,
|
|
520
645
|
runOpen,
|
|
521
646
|
runPages,
|
|
522
647
|
runSave
|
package/dist/cli/core/context.js
CHANGED
|
@@ -53,6 +53,11 @@ function ensureLibrettoSetup() {
|
|
|
53
53
|
if (!existsSync(LIBRETTO_GITIGNORE_PATH)) {
|
|
54
54
|
writeFileSync(LIBRETTO_GITIGNORE_PATH, LIBRETTO_GITIGNORE_CONTENT, "utf-8");
|
|
55
55
|
}
|
|
56
|
+
const agentsSkillsDir = join(REPO_ROOT, ".agents", "skills", "libretto");
|
|
57
|
+
const claudeSkillsDir = join(REPO_ROOT, ".claude", "skills", "libretto");
|
|
58
|
+
if (!existsSync(agentsSkillsDir) && !existsSync(claudeSkillsDir)) {
|
|
59
|
+
console.log("[libretto] Skills not installed. Run 'npx libretto init' to complete setup.");
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
62
|
function createLoggerForSession(session) {
|
|
58
63
|
validateSessionName(session);
|
package/dist/cli/core/session.js
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "./context.js";
|
|
15
15
|
import {
|
|
16
16
|
SESSION_STATE_VERSION,
|
|
17
|
-
SessionStatusSchema,
|
|
18
17
|
parseSessionStateContent,
|
|
19
18
|
serializeSessionState
|
|
20
19
|
} from "../../shared/state/index.js";
|
|
@@ -65,11 +64,19 @@ function readSessionState(session, logger) {
|
|
|
65
64
|
return null;
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
|
-
function
|
|
67
|
+
function listSessionsWithStateFile() {
|
|
69
68
|
if (!existsSync(LIBRETTO_SESSIONS_DIR)) return [];
|
|
70
|
-
return readdirSync(LIBRETTO_SESSIONS_DIR).filter(
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
return readdirSync(LIBRETTO_SESSIONS_DIR).filter((session) => {
|
|
70
|
+
try {
|
|
71
|
+
validateSessionName(session);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return existsSync(getSessionStatePath(session));
|
|
76
|
+
}).sort();
|
|
77
|
+
}
|
|
78
|
+
function listActiveSessions() {
|
|
79
|
+
return listSessionsWithStateFile();
|
|
73
80
|
}
|
|
74
81
|
function throwSessionNotFoundError(session) {
|
|
75
82
|
const active = listActiveSessions();
|
|
@@ -128,9 +135,6 @@ function clearSessionState(session, logger) {
|
|
|
128
135
|
unlinkSync(stateFile);
|
|
129
136
|
logger?.info("session-state-cleared", { session, stateFile });
|
|
130
137
|
}
|
|
131
|
-
function isSessionStatus(value) {
|
|
132
|
-
return SessionStatusSchema.safeParse(value).success;
|
|
133
|
-
}
|
|
134
138
|
function isPidRunning(pid) {
|
|
135
139
|
try {
|
|
136
140
|
process.kill(pid, 0);
|
|
@@ -151,11 +155,6 @@ function setSessionStatus(session, status, logger) {
|
|
|
151
155
|
function assertSessionAvailableForStart(session, logger) {
|
|
152
156
|
const existingState = readSessionState(session, logger);
|
|
153
157
|
if (!existingState) return;
|
|
154
|
-
if (isSessionStatus(existingState.status)) {
|
|
155
|
-
if (existingState.status === "completed" || existingState.status === "failed" || existingState.status === "exited") {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
158
|
if (!isPidRunning(existingState.pid)) {
|
|
160
159
|
setSessionStatus(session, "exited", logger);
|
|
161
160
|
return;
|
|
@@ -174,6 +173,7 @@ export {
|
|
|
174
173
|
assertSessionStateExistsOrThrow,
|
|
175
174
|
clearSessionState,
|
|
176
175
|
getStateFilePath,
|
|
176
|
+
listSessionsWithStateFile,
|
|
177
177
|
logFileForSession,
|
|
178
178
|
readSessionState,
|
|
179
179
|
readSessionStateOrThrow,
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import { appendFileSync, existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
3
|
import { cwd } from "node:process";
|
|
4
4
|
import { isAbsolute, resolve } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import {
|
|
7
7
|
launchBrowser
|
|
8
8
|
} from "../../index.js";
|
|
9
|
+
import { setSessionForPause } from "../../shared/debug/pause.js";
|
|
10
|
+
import { parseSessionStateContent } from "../../shared/state/index.js";
|
|
9
11
|
import { getProfilePath, normalizeDomain } from "../core/browser.js";
|
|
10
12
|
import {
|
|
11
13
|
getSessionActionsLogPath,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
getSessionNetworkLogPath,
|
|
15
|
+
getSessionStatePath
|
|
14
16
|
} from "../core/context.js";
|
|
15
17
|
import { getPauseSignalPaths, removeSignalIfExists } from "../core/pause-signals.js";
|
|
16
18
|
import { installSessionTelemetry } from "../core/session-telemetry.js";
|
|
17
19
|
const LIBRETTO_WORKFLOW_BRAND = /* @__PURE__ */ Symbol.for("libretto.workflow");
|
|
18
|
-
const
|
|
20
|
+
const FAILURE_HOLD_POLL_INTERVAL_MS = 250;
|
|
19
21
|
function mirrorStdoutToFile(filePath) {
|
|
20
22
|
const stdout = process.stdout;
|
|
21
23
|
const originalWrite = stdout.write.bind(stdout);
|
|
@@ -31,23 +33,32 @@ function mirrorStdoutToFile(filePath) {
|
|
|
31
33
|
stdout.write = originalWrite;
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
function readSessionStatePid(session) {
|
|
37
|
+
const statePath = getSessionStatePath(session);
|
|
38
|
+
if (!existsSync(statePath)) return null;
|
|
39
|
+
try {
|
|
40
|
+
return parseSessionStateContent(readFileSync(statePath, "utf8"), statePath).pid;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function waitForFailureSessionRelease(args) {
|
|
46
|
+
const { session, expectedPid, logger } = args;
|
|
47
|
+
logger.info("run-failure-session-hold", { session, expectedPid });
|
|
48
|
+
while (true) {
|
|
49
|
+
const currentPid = readSessionStatePid(session);
|
|
50
|
+
if (currentPid !== expectedPid) {
|
|
51
|
+
logger.info("run-failure-session-released", {
|
|
52
|
+
session,
|
|
53
|
+
expectedPid,
|
|
54
|
+
currentPid
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
45
58
|
await new Promise(
|
|
46
|
-
(resolveWait) => setTimeout(resolveWait,
|
|
59
|
+
(resolveWait) => setTimeout(resolveWait, FAILURE_HOLD_POLL_INTERVAL_MS)
|
|
47
60
|
);
|
|
48
61
|
}
|
|
49
|
-
await removeSignalIfExists(resumeSignalPath);
|
|
50
|
-
await removeSignalIfExists(pausedSignalPath);
|
|
51
62
|
}
|
|
52
63
|
function isLoadedLibrettoWorkflow(value) {
|
|
53
64
|
if (!value || typeof value !== "object") return false;
|
|
@@ -57,13 +68,6 @@ function isLoadedLibrettoWorkflow(value) {
|
|
|
57
68
|
function resolveLocalAuthProfilePath(domain) {
|
|
58
69
|
return getProfilePath(normalizeDomain(domain));
|
|
59
70
|
}
|
|
60
|
-
function resolveWorkflowStorageStatePath(workflow) {
|
|
61
|
-
const authProfile = workflow.metadata.authProfile;
|
|
62
|
-
if (authProfile?.type !== "local") {
|
|
63
|
-
return void 0;
|
|
64
|
-
}
|
|
65
|
-
return resolveLocalAuthProfilePath(authProfile.domain);
|
|
66
|
-
}
|
|
67
71
|
function getMissingLocalAuthProfileError(args) {
|
|
68
72
|
const normalizedDomain = normalizeDomain(args.domain);
|
|
69
73
|
return [
|
|
@@ -101,7 +105,24 @@ async function loadWorkflowExport(absolutePath, exportName) {
|
|
|
101
105
|
}
|
|
102
106
|
if (!isLoadedLibrettoWorkflow(targetExport)) {
|
|
103
107
|
throw new Error(
|
|
104
|
-
|
|
108
|
+
[
|
|
109
|
+
`Export "${exportName}" in ${absolutePath} is not a valid Libretto workflow.`,
|
|
110
|
+
"",
|
|
111
|
+
'A workflow must be created using the workflow() function from "libretto":',
|
|
112
|
+
"",
|
|
113
|
+
' import { workflow } from "libretto";',
|
|
114
|
+
"",
|
|
115
|
+
` export const ${exportName} = workflow<InputType, OutputType>(`,
|
|
116
|
+
" {},",
|
|
117
|
+
" async (ctx, input) => {",
|
|
118
|
+
" // ctx.page \u2014 Playwright Page instance",
|
|
119
|
+
" // ctx.logger \u2014 MinimalLogger",
|
|
120
|
+
" // ctx.services \u2014 injected dependencies (generic, default {})",
|
|
121
|
+
" // input \u2014 JSON-serializable input matching InputType",
|
|
122
|
+
" return output; // must match OutputType",
|
|
123
|
+
" },",
|
|
124
|
+
" );"
|
|
125
|
+
].join("\n")
|
|
105
126
|
);
|
|
106
127
|
}
|
|
107
128
|
return targetExport;
|
|
@@ -125,12 +146,12 @@ async function runIntegrationInternal(args, options) {
|
|
|
125
146
|
integrationExport: args.exportName,
|
|
126
147
|
session: args.session
|
|
127
148
|
});
|
|
128
|
-
const
|
|
129
|
-
const storageStatePath =
|
|
130
|
-
if (
|
|
149
|
+
const authProfileDomain = args.authProfileDomain;
|
|
150
|
+
const storageStatePath = authProfileDomain ? resolveLocalAuthProfilePath(authProfileDomain) : void 0;
|
|
151
|
+
if (authProfileDomain && storageStatePath && !existsSync(storageStatePath)) {
|
|
131
152
|
throw new Error(
|
|
132
153
|
getMissingLocalAuthProfileError({
|
|
133
|
-
domain:
|
|
154
|
+
domain: authProfileDomain,
|
|
134
155
|
profilePath: storageStatePath,
|
|
135
156
|
session: args.session
|
|
136
157
|
})
|
|
@@ -154,50 +175,35 @@ async function runIntegrationInternal(args, options) {
|
|
|
154
175
|
appendFileSync(networkLogPath, JSON.stringify(entry) + "\n");
|
|
155
176
|
}
|
|
156
177
|
});
|
|
178
|
+
setSessionForPause(args.session);
|
|
157
179
|
const workflowContext = {
|
|
158
180
|
logger: integrationLogger,
|
|
159
181
|
page: browserSession.page,
|
|
160
|
-
|
|
161
|
-
browser: browserSession.browser,
|
|
162
|
-
session: args.session,
|
|
163
|
-
integrationPath: absolutePath,
|
|
164
|
-
exportName: args.exportName,
|
|
165
|
-
headless: args.headless,
|
|
166
|
-
debug: args.debug,
|
|
167
|
-
pause: async () => {
|
|
168
|
-
const details = {
|
|
169
|
-
sessionName: args.session,
|
|
170
|
-
pausedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
171
|
-
url: browserSession.page.url()
|
|
172
|
-
};
|
|
173
|
-
console.log(`[pause] Paused at ${details.url}`);
|
|
174
|
-
console.log("[pause] Waiting for resume signal...");
|
|
175
|
-
await waitForResumeSignal({
|
|
176
|
-
signalPaths,
|
|
177
|
-
session: args.session,
|
|
178
|
-
details,
|
|
179
|
-
onPaused: options.onPaused
|
|
180
|
-
});
|
|
181
|
-
console.log("[pause] Resume signal received. Continuing workflow...");
|
|
182
|
-
}
|
|
182
|
+
services: {}
|
|
183
183
|
};
|
|
184
184
|
try {
|
|
185
185
|
try {
|
|
186
186
|
await workflow.run(workflowContext, args.params ?? {});
|
|
187
187
|
} catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
188
189
|
await writeFile(
|
|
189
190
|
signalPaths.failedSignalPath,
|
|
190
191
|
JSON.stringify(
|
|
191
192
|
{
|
|
192
193
|
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
-
message:
|
|
194
|
+
message: errorMessage
|
|
194
195
|
},
|
|
195
196
|
null,
|
|
196
197
|
2
|
|
197
198
|
),
|
|
198
199
|
"utf8"
|
|
199
200
|
);
|
|
200
|
-
|
|
201
|
+
await waitForFailureSessionRelease({
|
|
202
|
+
session: args.session,
|
|
203
|
+
expectedPid: process.pid,
|
|
204
|
+
logger
|
|
205
|
+
});
|
|
206
|
+
return { status: "failed-held" };
|
|
201
207
|
}
|
|
202
208
|
await writeFile(
|
|
203
209
|
signalPaths.completedSignalPath,
|
|
@@ -211,10 +217,9 @@ async function runIntegrationInternal(args, options) {
|
|
|
211
217
|
await browserSession.close();
|
|
212
218
|
}
|
|
213
219
|
}
|
|
214
|
-
async function runIntegrationFromFileInWorker(args, logger
|
|
220
|
+
async function runIntegrationFromFileInWorker(args, logger) {
|
|
215
221
|
return await runIntegrationInternal(args, {
|
|
216
|
-
logger
|
|
217
|
-
onPaused
|
|
222
|
+
logger
|
|
218
223
|
});
|
|
219
224
|
}
|
|
220
225
|
export {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const RunIntegrationWorkerRequestSchema = z.object({
|
|
3
|
+
integrationPath: z.string().min(1),
|
|
4
|
+
exportName: z.string().min(1),
|
|
5
|
+
session: z.string().min(1),
|
|
6
|
+
params: z.unknown(),
|
|
7
|
+
headless: z.boolean(),
|
|
8
|
+
authProfileDomain: z.string().optional()
|
|
9
|
+
});
|
|
10
|
+
export {
|
|
11
|
+
RunIntegrationWorkerRequestSchema
|
|
12
|
+
};
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { ZodError } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
RunIntegrationWorkerRequestSchema
|
|
5
|
+
} from "./run-integration-worker-protocol.js";
|
|
2
6
|
import { runIntegrationFromFileInWorker } from "./run-integration-runtime.js";
|
|
3
7
|
import {
|
|
4
8
|
ensureLibrettoSetup,
|
|
5
9
|
withSessionLogger
|
|
6
10
|
} from "../core/context.js";
|
|
7
11
|
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
12
|
function parseWorkerRequest(argv) {
|
|
16
13
|
const rawPayload = argv[2];
|
|
17
14
|
if (!rawPayload) {
|
|
@@ -25,21 +22,15 @@ function parseWorkerRequest(argv) {
|
|
|
25
22
|
`Invalid worker payload JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
26
23
|
);
|
|
27
24
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
try {
|
|
26
|
+
return RunIntegrationWorkerRequestSchema.parse(parsed);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof ZodError) {
|
|
29
|
+
const details = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
|
|
30
|
+
throw new Error(`Worker payload is invalid: ${details}`);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
34
33
|
}
|
|
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
34
|
}
|
|
44
35
|
async function main() {
|
|
45
36
|
let request = null;
|
|
@@ -49,15 +40,8 @@ async function main() {
|
|
|
49
40
|
const workerRequest = request;
|
|
50
41
|
ensureLibrettoSetup();
|
|
51
42
|
await withSessionLogger(workerRequest.session, async (logger) => {
|
|
52
|
-
await runIntegrationFromFileInWorker(
|
|
53
|
-
workerRequest,
|
|
54
|
-
logger,
|
|
55
|
-
async (details) => {
|
|
56
|
-
sendMessage({ type: "paused", details });
|
|
57
|
-
}
|
|
58
|
-
);
|
|
43
|
+
await runIntegrationFromFileInWorker(workerRequest, logger);
|
|
59
44
|
});
|
|
60
|
-
sendMessage({ type: "completed" });
|
|
61
45
|
} catch (error) {
|
|
62
46
|
const message = error instanceof Error ? error.message : String(error);
|
|
63
47
|
if (request) {
|
|
@@ -75,7 +59,6 @@ async function main() {
|
|
|
75
59
|
"utf8"
|
|
76
60
|
);
|
|
77
61
|
}
|
|
78
|
-
sendMessage({ type: "failed", message });
|
|
79
62
|
exitCode = 1;
|
|
80
63
|
}
|
|
81
64
|
process.exit(exitCode);
|
package/dist/index.cjs
CHANGED
|
@@ -18,18 +18,16 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var index_exports = {};
|
|
20
20
|
__export(index_exports, {
|
|
21
|
-
DebugPauseSignal: () => import_pause.DebugPauseSignal,
|
|
22
21
|
LIBRETTO_WORKFLOW_BRAND: () => import_workflow.LIBRETTO_WORKFLOW_BRAND,
|
|
23
22
|
LibrettoWorkflow: () => import_workflow.LibrettoWorkflow,
|
|
24
23
|
Logger: () => import_logger.Logger,
|
|
25
|
-
RunDebugPauseSignal: () => import_api.DebugPauseSignal,
|
|
26
24
|
SESSION_STATE_VERSION: () => import_state.SESSION_STATE_VERSION,
|
|
27
25
|
SessionStateFileSchema: () => import_state.SessionStateFileSchema,
|
|
28
26
|
SessionStatusSchema: () => import_state.SessionStatusSchema,
|
|
29
27
|
attemptWithRecovery: () => import_recovery.attemptWithRecovery,
|
|
30
28
|
clearHighlights: () => import_highlight.clearHighlights,
|
|
31
29
|
createFileLogSink: () => import_sinks.createFileLogSink,
|
|
32
|
-
|
|
30
|
+
createLLMClientFromModel: () => import_ai_sdk_adapter.createLLMClientFromModel,
|
|
33
31
|
defaultLogger: () => import_logger.defaultLogger,
|
|
34
32
|
detectSubmissionError: () => import_errors.detectSubmissionError,
|
|
35
33
|
downloadAndSave: () => import_download.downloadAndSave,
|
|
@@ -44,17 +42,15 @@ __export(index_exports, {
|
|
|
44
42
|
instrumentContext: () => import_instrument.instrumentContext,
|
|
45
43
|
instrumentPage: () => import_instrument.instrumentPage,
|
|
46
44
|
isDebugMode: () => import_config.isDebugMode,
|
|
47
|
-
isDebugPauseSignal: () => import_pause.isDebugPauseSignal,
|
|
48
45
|
isDryRun: () => import_config.isDryRun,
|
|
49
|
-
isRunDebugPauseSignal: () => import_api.isDebugPauseSignal,
|
|
50
46
|
jsonlConsoleSink: () => import_sinks.jsonlConsoleSink,
|
|
51
47
|
launchBrowser: () => import_api.launchBrowser,
|
|
52
48
|
moveGhostCursor: () => import_ghost_cursor.moveGhostCursor,
|
|
53
49
|
pageRequest: () => import_network.pageRequest,
|
|
54
50
|
parseSessionStateContent: () => import_state.parseSessionStateContent,
|
|
55
51
|
parseSessionStateData: () => import_state.parseSessionStateData,
|
|
52
|
+
pause: () => import_pause.pause,
|
|
56
53
|
prettyConsoleSink: () => import_sinks.prettyConsoleSink,
|
|
57
|
-
runDebugPause: () => import_api.debugPause,
|
|
58
54
|
serializeSessionState: () => import_state.serializeSessionState,
|
|
59
55
|
shouldPauseBeforeMutation: () => import_config.shouldPauseBeforeMutation,
|
|
60
56
|
showHighlight: () => import_highlight.showHighlight,
|
|
@@ -63,6 +59,7 @@ __export(index_exports, {
|
|
|
63
59
|
module.exports = __toCommonJS(index_exports);
|
|
64
60
|
var import_logger = require("./shared/logger/logger.js");
|
|
65
61
|
var import_sinks = require("./shared/logger/sinks.js");
|
|
62
|
+
var import_ai_sdk_adapter = require("./shared/llm/ai-sdk-adapter.js");
|
|
66
63
|
var import_state = require("./shared/state/index.js");
|
|
67
64
|
var import_agent = require("./runtime/recovery/agent.js");
|
|
68
65
|
var import_recovery = require("./runtime/recovery/recovery.js");
|
|
@@ -79,18 +76,16 @@ var import_api = require("./shared/run/api.js");
|
|
|
79
76
|
var import_workflow = require("./shared/workflow/workflow.js");
|
|
80
77
|
// Annotate the CommonJS export names for ESM import in node:
|
|
81
78
|
0 && (module.exports = {
|
|
82
|
-
DebugPauseSignal,
|
|
83
79
|
LIBRETTO_WORKFLOW_BRAND,
|
|
84
80
|
LibrettoWorkflow,
|
|
85
81
|
Logger,
|
|
86
|
-
RunDebugPauseSignal,
|
|
87
82
|
SESSION_STATE_VERSION,
|
|
88
83
|
SessionStateFileSchema,
|
|
89
84
|
SessionStatusSchema,
|
|
90
85
|
attemptWithRecovery,
|
|
91
86
|
clearHighlights,
|
|
92
87
|
createFileLogSink,
|
|
93
|
-
|
|
88
|
+
createLLMClientFromModel,
|
|
94
89
|
defaultLogger,
|
|
95
90
|
detectSubmissionError,
|
|
96
91
|
downloadAndSave,
|
|
@@ -105,17 +100,15 @@ var import_workflow = require("./shared/workflow/workflow.js");
|
|
|
105
100
|
instrumentContext,
|
|
106
101
|
instrumentPage,
|
|
107
102
|
isDebugMode,
|
|
108
|
-
isDebugPauseSignal,
|
|
109
103
|
isDryRun,
|
|
110
|
-
isRunDebugPauseSignal,
|
|
111
104
|
jsonlConsoleSink,
|
|
112
105
|
launchBrowser,
|
|
113
106
|
moveGhostCursor,
|
|
114
107
|
pageRequest,
|
|
115
108
|
parseSessionStateContent,
|
|
116
109
|
parseSessionStateData,
|
|
110
|
+
pause,
|
|
117
111
|
prettyConsoleSink,
|
|
118
|
-
runDebugPause,
|
|
119
112
|
serializeSessionState,
|
|
120
113
|
shouldPauseBeforeMutation,
|
|
121
114
|
showHighlight,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { LogOptions, Logger, LoggerApi, LoggerSink, MinimalLogger, defaultLogger } from './shared/logger/logger.cjs';
|
|
2
2
|
export { createFileLogSink, jsonlConsoleSink, prettyConsoleSink } from './shared/logger/sinks.cjs';
|
|
3
3
|
export { LLMClient, Message, MessageContentPart } from './shared/llm/types.cjs';
|
|
4
|
+
export { createLLMClientFromModel } from './shared/llm/ai-sdk-adapter.cjs';
|
|
4
5
|
export { SESSION_STATE_VERSION, SessionState, SessionStateFile, SessionStateFileSchema, SessionStatus, SessionStatusSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState } from './shared/state/session-state.cjs';
|
|
5
6
|
export { executeRecoveryAgent } from './runtime/recovery/agent.cjs';
|
|
6
7
|
export { attemptWithRecovery } from './runtime/recovery/recovery.cjs';
|
|
@@ -8,12 +9,13 @@ export { DetectedSubmissionError, KnownSubmissionError, detectSubmissionError }
|
|
|
8
9
|
export { ExtractOptions, extractFromPage } from './runtime/extract/extract.cjs';
|
|
9
10
|
export { PageRequestOptions, RequestConfig, pageRequest } from './runtime/network/network.cjs';
|
|
10
11
|
export { DownloadResult, DownloadViaClickOptions, SaveDownloadOptions, downloadAndSave, downloadViaClick } from './runtime/download/download.cjs';
|
|
11
|
-
export {
|
|
12
|
+
export { pause } from './shared/debug/pause.cjs';
|
|
12
13
|
export { isDebugMode, isDryRun, shouldPauseBeforeMutation } from './shared/config/config.cjs';
|
|
13
14
|
export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage } from './shared/instrumentation/instrument.cjs';
|
|
14
15
|
export { GhostCursorOptions, ensureGhostCursor, ghostClick, hideGhostCursor, moveGhostCursor } from './shared/visualization/ghost-cursor.cjs';
|
|
15
16
|
export { HighlightOptions, clearHighlights, ensureHighlightLayer, showHighlight } from './shared/visualization/highlight.cjs';
|
|
16
17
|
export { BrowserSession, LaunchBrowserArgs, launchBrowser } from './shared/run/browser.cjs';
|
|
17
|
-
export { LIBRETTO_WORKFLOW_BRAND,
|
|
18
|
+
export { LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, LibrettoWorkflowMetadata, workflow } from './shared/workflow/workflow.cjs';
|
|
18
19
|
import 'zod';
|
|
20
|
+
import 'ai';
|
|
19
21
|
import 'playwright';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { LogOptions, Logger, LoggerApi, LoggerSink, MinimalLogger, defaultLogger } from './shared/logger/logger.js';
|
|
2
2
|
export { createFileLogSink, jsonlConsoleSink, prettyConsoleSink } from './shared/logger/sinks.js';
|
|
3
3
|
export { LLMClient, Message, MessageContentPart } from './shared/llm/types.js';
|
|
4
|
+
export { createLLMClientFromModel } from './shared/llm/ai-sdk-adapter.js';
|
|
4
5
|
export { SESSION_STATE_VERSION, SessionState, SessionStateFile, SessionStateFileSchema, SessionStatus, SessionStatusSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState } from './shared/state/session-state.js';
|
|
5
6
|
export { executeRecoveryAgent } from './runtime/recovery/agent.js';
|
|
6
7
|
export { attemptWithRecovery } from './runtime/recovery/recovery.js';
|
|
@@ -8,12 +9,13 @@ export { DetectedSubmissionError, KnownSubmissionError, detectSubmissionError }
|
|
|
8
9
|
export { ExtractOptions, extractFromPage } from './runtime/extract/extract.js';
|
|
9
10
|
export { PageRequestOptions, RequestConfig, pageRequest } from './runtime/network/network.js';
|
|
10
11
|
export { DownloadResult, DownloadViaClickOptions, SaveDownloadOptions, downloadAndSave, downloadViaClick } from './runtime/download/download.js';
|
|
11
|
-
export {
|
|
12
|
+
export { pause } from './shared/debug/pause.js';
|
|
12
13
|
export { isDebugMode, isDryRun, shouldPauseBeforeMutation } from './shared/config/config.js';
|
|
13
14
|
export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage } from './shared/instrumentation/instrument.js';
|
|
14
15
|
export { GhostCursorOptions, ensureGhostCursor, ghostClick, hideGhostCursor, moveGhostCursor } from './shared/visualization/ghost-cursor.js';
|
|
15
16
|
export { HighlightOptions, clearHighlights, ensureHighlightLayer, showHighlight } from './shared/visualization/highlight.js';
|
|
16
17
|
export { BrowserSession, LaunchBrowserArgs, launchBrowser } from './shared/run/browser.js';
|
|
17
|
-
export { LIBRETTO_WORKFLOW_BRAND,
|
|
18
|
+
export { LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, LibrettoWorkflowMetadata, workflow } from './shared/workflow/workflow.js';
|
|
18
19
|
import 'zod';
|
|
20
|
+
import 'ai';
|
|
19
21
|
import 'playwright';
|