libretto 0.4.4 → 0.5.1
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 +106 -36
- package/dist/cli/cli.js +39 -113
- package/dist/cli/commands/ai.js +1 -1
- package/dist/cli/commands/browser.js +87 -60
- package/dist/cli/commands/execution.js +201 -88
- package/dist/cli/commands/init.js +30 -8
- package/dist/cli/commands/logs.js +5 -6
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +9 -2
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +141 -33
- package/dist/cli/core/context.js +7 -18
- package/dist/cli/core/session-telemetry.js +5 -2
- package/dist/cli/core/session.js +23 -10
- package/dist/cli/core/snapshot-analyzer.js +16 -33
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +10 -2
- package/dist/cli/framework/simple-cli.js +45 -25
- package/dist/cli/router.js +14 -21
- package/dist/cli/workers/run-integration-runtime.js +26 -7
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +7 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +6 -13
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/paths/paths.js +2 -1
- package/dist/shared/paths/repo-root.d.ts +3 -0
- package/dist/shared/paths/repo-root.js +24 -0
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +7 -2
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +19 -10
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +4 -5
- package/dist/shared/workflow/workflow.js +3 -5
- package/package.json +11 -8
- package/scripts/check-skills-sync.mjs +25 -0
- package/scripts/compare-eval-summary.mjs +47 -0
- package/scripts/postinstall.mjs +26 -17
- package/scripts/prepare-release.sh +97 -0
- package/scripts/skills-libretto.mjs +103 -0
- package/scripts/summarize-evals.mjs +135 -0
- package/scripts/sync-skills.mjs +12 -0
- package/skills/libretto/SKILL.md +130 -377
- package/skills/libretto/references/auth-profiles.md +30 -0
- package/skills/libretto/{code-generation-rules.md → references/code-generation-rules.md} +27 -42
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +29 -0
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +86 -0
- package/src/cli/commands/ai.ts +35 -0
- package/src/cli/commands/browser.ts +189 -0
- package/src/cli/commands/execution.ts +822 -0
- package/src/cli/commands/init.ts +350 -0
- package/src/cli/commands/logs.ts +128 -0
- package/src/cli/commands/shared.ts +69 -0
- package/src/cli/commands/snapshot.ts +312 -0
- package/src/cli/core/ai-config.ts +264 -0
- package/src/cli/core/api-snapshot-analyzer.ts +108 -0
- package/src/cli/core/browser.ts +976 -0
- package/src/cli/core/context.ts +127 -0
- package/src/cli/core/pause-signals.ts +35 -0
- package/src/cli/core/session-telemetry.ts +564 -0
- package/src/cli/core/session.ts +223 -0
- package/src/cli/core/snapshot-analyzer.ts +855 -0
- package/src/cli/core/snapshot-api-config.ts +231 -0
- package/src/cli/core/telemetry.ts +459 -0
- package/src/cli/framework/simple-cli.ts +1340 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/router.ts +20 -0
- package/src/cli/workers/run-integration-runtime.ts +338 -0
- package/src/cli/workers/run-integration-worker-protocol.ts +16 -0
- package/src/cli/workers/run-integration-worker.ts +72 -0
- package/src/index.ts +127 -0
- package/src/runtime/download/download.ts +104 -0
- package/src/runtime/download/index.ts +7 -0
- package/src/runtime/extract/extract.ts +102 -0
- package/src/runtime/extract/index.ts +1 -0
- package/src/runtime/network/index.ts +5 -0
- package/src/runtime/network/network.ts +119 -0
- package/{dist/runtime/recovery/agent.cjs → src/runtime/recovery/agent.ts} +114 -76
- package/src/runtime/recovery/errors.ts +155 -0
- package/src/runtime/recovery/index.ts +7 -0
- package/src/runtime/recovery/recovery.ts +53 -0
- package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +249 -124
- package/src/shared/config/config.ts +3 -0
- package/src/shared/config/index.ts +0 -0
- package/src/shared/debug/index.ts +1 -0
- package/src/shared/debug/pause.ts +91 -0
- package/src/shared/instrumentation/errors.ts +84 -0
- package/src/shared/instrumentation/index.ts +9 -0
- package/src/shared/instrumentation/instrument.ts +406 -0
- package/src/shared/llm/ai-sdk-adapter.ts +81 -0
- package/{dist/shared/llm/client.cjs → src/shared/llm/client.ts} +86 -80
- package/src/shared/llm/index.ts +3 -0
- package/src/shared/llm/types.ts +63 -0
- package/src/shared/logger/index.ts +13 -0
- package/src/shared/logger/logger.ts +358 -0
- package/src/shared/logger/sinks.ts +148 -0
- package/src/shared/paths/paths.ts +110 -0
- package/src/shared/paths/repo-root.ts +27 -0
- package/src/shared/run/api.ts +6 -0
- package/src/shared/run/browser.ts +107 -0
- package/src/shared/state/index.ts +11 -0
- package/src/shared/state/session-state.ts +77 -0
- package/src/shared/visualization/ghost-cursor.ts +213 -0
- package/src/shared/visualization/highlight.ts +149 -0
- package/src/shared/visualization/index.ts +18 -0
- package/src/shared/workflow/workflow.ts +36 -0
- package/dist/index.cjs +0 -144
- package/dist/index.d.cts +0 -21
- package/dist/runtime/download/download.cjs +0 -70
- package/dist/runtime/download/download.d.cts +0 -35
- package/dist/runtime/download/index.cjs +0 -30
- package/dist/runtime/download/index.d.cts +0 -3
- package/dist/runtime/extract/extract.cjs +0 -88
- package/dist/runtime/extract/extract.d.cts +0 -23
- package/dist/runtime/extract/index.cjs +0 -28
- package/dist/runtime/extract/index.d.cts +0 -5
- package/dist/runtime/network/index.cjs +0 -28
- package/dist/runtime/network/index.d.cts +0 -4
- package/dist/runtime/network/network.cjs +0 -91
- package/dist/runtime/network/network.d.cts +0 -28
- package/dist/runtime/recovery/agent.d.cts +0 -13
- package/dist/runtime/recovery/errors.cjs +0 -124
- package/dist/runtime/recovery/errors.d.cts +0 -31
- package/dist/runtime/recovery/index.cjs +0 -34
- package/dist/runtime/recovery/index.d.cts +0 -7
- package/dist/runtime/recovery/recovery.cjs +0 -55
- package/dist/runtime/recovery/recovery.d.cts +0 -12
- package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
- package/dist/shared/config/config.cjs +0 -44
- package/dist/shared/config/config.d.cts +0 -10
- package/dist/shared/config/index.cjs +0 -32
- package/dist/shared/config/index.d.cts +0 -1
- package/dist/shared/debug/index.cjs +0 -28
- package/dist/shared/debug/index.d.cts +0 -1
- package/dist/shared/debug/pause.cjs +0 -86
- package/dist/shared/debug/pause.d.cts +0 -12
- package/dist/shared/instrumentation/errors.cjs +0 -81
- package/dist/shared/instrumentation/errors.d.cts +0 -12
- package/dist/shared/instrumentation/index.cjs +0 -35
- package/dist/shared/instrumentation/index.d.cts +0 -6
- package/dist/shared/instrumentation/instrument.cjs +0 -206
- package/dist/shared/instrumentation/instrument.d.cts +0 -32
- package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
- package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
- package/dist/shared/llm/client.d.cts +0 -13
- package/dist/shared/llm/index.cjs +0 -31
- package/dist/shared/llm/index.d.cts +0 -5
- package/dist/shared/llm/types.cjs +0 -16
- package/dist/shared/llm/types.d.cts +0 -67
- package/dist/shared/logger/index.cjs +0 -37
- package/dist/shared/logger/index.d.cts +0 -2
- package/dist/shared/logger/logger.cjs +0 -232
- package/dist/shared/logger/logger.d.cts +0 -86
- package/dist/shared/logger/sinks.cjs +0 -160
- package/dist/shared/logger/sinks.d.cts +0 -9
- package/dist/shared/paths/paths.cjs +0 -104
- package/dist/shared/paths/paths.d.cts +0 -10
- package/dist/shared/run/api.cjs +0 -28
- package/dist/shared/run/api.d.cts +0 -2
- package/dist/shared/run/browser.cjs +0 -98
- package/dist/shared/run/browser.d.cts +0 -22
- package/dist/shared/state/index.cjs +0 -38
- package/dist/shared/state/index.d.cts +0 -2
- package/dist/shared/state/session-state.cjs +0 -92
- package/dist/shared/state/session-state.d.cts +0 -40
- package/dist/shared/visualization/ghost-cursor.cjs +0 -174
- package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
- package/dist/shared/visualization/highlight.cjs +0 -134
- package/dist/shared/visualization/highlight.d.cts +0 -22
- package/dist/shared/visualization/index.cjs +0 -45
- package/dist/shared/visualization/index.d.cts +0 -3
- package/dist/shared/workflow/workflow.cjs +0 -47
- package/dist/shared/workflow/workflow.d.cts +0 -21
- package/skills/libretto/integration-approach-selection.md +0 -174
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import * as moduleBuiltin from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { installInstrumentation } from "../../shared/instrumentation/index.js";
|
|
7
|
+
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
8
|
+
import {
|
|
9
|
+
connect,
|
|
10
|
+
disconnectBrowser,
|
|
11
|
+
resolveViewport,
|
|
12
|
+
} from "../core/browser.js";
|
|
13
|
+
import { parseViewportArg } from "./browser.js";
|
|
14
|
+
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
15
|
+
import {
|
|
16
|
+
assertSessionAvailableForStart,
|
|
17
|
+
clearSessionState,
|
|
18
|
+
readSessionState,
|
|
19
|
+
setSessionStatus,
|
|
20
|
+
type SessionState,
|
|
21
|
+
} from "../core/session.js";
|
|
22
|
+
import {
|
|
23
|
+
readActionLog,
|
|
24
|
+
readNetworkLog,
|
|
25
|
+
wrapPageForActionLogging,
|
|
26
|
+
} from "../core/telemetry.js";
|
|
27
|
+
import type { RunIntegrationWorkerRequest } from "../workers/run-integration-worker-protocol.js";
|
|
28
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
29
|
+
import {
|
|
30
|
+
pageOption,
|
|
31
|
+
sessionOption,
|
|
32
|
+
withAutoSession,
|
|
33
|
+
withRequiredSession,
|
|
34
|
+
} from "./shared.js";
|
|
35
|
+
|
|
36
|
+
type ExecFunction = (...args: unknown[]) => Promise<unknown>;
|
|
37
|
+
type RunIntegrationCommandRequest = RunIntegrationWorkerRequest & {
|
|
38
|
+
tsconfigPath?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type StripTypeScriptTypesFn = (
|
|
42
|
+
code: string,
|
|
43
|
+
options?: { mode?: "strip" | "transform" },
|
|
44
|
+
) => string;
|
|
45
|
+
|
|
46
|
+
const stripTypeScriptTypes = (
|
|
47
|
+
moduleBuiltin as { stripTypeScriptTypes?: StripTypeScriptTypesFn }
|
|
48
|
+
).stripTypeScriptTypes;
|
|
49
|
+
const require = moduleBuiltin.createRequire(import.meta.url);
|
|
50
|
+
const tsxCliPath = require.resolve("tsx/cli");
|
|
51
|
+
|
|
52
|
+
function withSuppressedStripTypeScriptWarning<T>(action: () => T): T {
|
|
53
|
+
type EmitWarningFn = (...args: unknown[]) => void;
|
|
54
|
+
const mutableProcess = process as unknown as { emitWarning: EmitWarningFn };
|
|
55
|
+
const originalEmitWarning = mutableProcess.emitWarning;
|
|
56
|
+
|
|
57
|
+
mutableProcess.emitWarning = (...args: unknown[]) => {
|
|
58
|
+
const warning = args[0];
|
|
59
|
+
const typeOrOptions = args[1];
|
|
60
|
+
const warningMessage =
|
|
61
|
+
typeof warning === "string"
|
|
62
|
+
? warning
|
|
63
|
+
: warning instanceof Error
|
|
64
|
+
? warning.message
|
|
65
|
+
: "";
|
|
66
|
+
const warningType =
|
|
67
|
+
typeof typeOrOptions === "string"
|
|
68
|
+
? typeOrOptions
|
|
69
|
+
: typeof typeOrOptions === "object" &&
|
|
70
|
+
typeOrOptions !== null &&
|
|
71
|
+
"type" in typeOrOptions &&
|
|
72
|
+
typeof (typeOrOptions as { type?: unknown }).type === "string"
|
|
73
|
+
? ((typeOrOptions as { type?: string }).type ?? "")
|
|
74
|
+
: "";
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
warningType === "ExperimentalWarning" &&
|
|
78
|
+
warningMessage.includes("stripTypeScriptTypes")
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
originalEmitWarning(...args);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
return action();
|
|
87
|
+
} finally {
|
|
88
|
+
mutableProcess.emitWarning = originalEmitWarning;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function compileTypeScriptExecFunction(
|
|
93
|
+
code: string,
|
|
94
|
+
helperNames: string[],
|
|
95
|
+
): ExecFunction | null {
|
|
96
|
+
if (!stripTypeScriptTypes) return null;
|
|
97
|
+
|
|
98
|
+
const wrappedSource = `(async function __librettoExec(${helperNames.join(", ")}) {\n${code}\n})`;
|
|
99
|
+
const jsSource = withSuppressedStripTypeScriptWarning(() =>
|
|
100
|
+
stripTypeScriptTypes(wrappedSource, { mode: "strip" }),
|
|
101
|
+
);
|
|
102
|
+
const createFunction = new Function(
|
|
103
|
+
`return ${jsSource}`,
|
|
104
|
+
) as () => ExecFunction;
|
|
105
|
+
return createFunction();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function compileExecFunction(
|
|
109
|
+
code: string,
|
|
110
|
+
helperNames: string[],
|
|
111
|
+
): ExecFunction {
|
|
112
|
+
const typeStripped = compileTypeScriptExecFunction(code, helperNames);
|
|
113
|
+
if (typeStripped) return typeStripped;
|
|
114
|
+
|
|
115
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {})
|
|
116
|
+
.constructor as new (...args: string[]) => ExecFunction;
|
|
117
|
+
return new AsyncFunction(...helperNames, code);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Strip `.catch(() => {})` / `?.catch(() => {})` from executable code,
|
|
122
|
+
* skipping occurrences inside string literals (single, double, backtick)
|
|
123
|
+
* and single-line / multi-line comments so we never corrupt non-code text.
|
|
124
|
+
*/
|
|
125
|
+
function stripEmptyCatchHandlers(code: string): {
|
|
126
|
+
cleaned: string;
|
|
127
|
+
strippedCount: number;
|
|
128
|
+
} {
|
|
129
|
+
const catchRe = /\??\s*\.catch\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/g;
|
|
130
|
+
let strippedCount = 0;
|
|
131
|
+
let result = "";
|
|
132
|
+
let i = 0;
|
|
133
|
+
|
|
134
|
+
while (i < code.length) {
|
|
135
|
+
// Single-line comment
|
|
136
|
+
if (code[i] === "/" && code[i + 1] === "/") {
|
|
137
|
+
const end = code.indexOf("\n", i);
|
|
138
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 1);
|
|
139
|
+
result += slice;
|
|
140
|
+
i += slice.length;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Multi-line comment
|
|
144
|
+
if (code[i] === "/" && code[i + 1] === "*") {
|
|
145
|
+
const end = code.indexOf("*/", i + 2);
|
|
146
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 2);
|
|
147
|
+
result += slice;
|
|
148
|
+
i += slice.length;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// String literals
|
|
152
|
+
if (code[i] === '"' || code[i] === "'" || code[i] === "`") {
|
|
153
|
+
const quote = code[i];
|
|
154
|
+
let j = i + 1;
|
|
155
|
+
while (j < code.length) {
|
|
156
|
+
if (code[j] === "\\" && quote !== "`") {
|
|
157
|
+
j += 2;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (code[j] === "\\" && quote === "`") {
|
|
161
|
+
j += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (code[j] === quote) {
|
|
165
|
+
j++;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
// Template literal interpolation — skip nested braces
|
|
169
|
+
if (quote === "`" && code[j] === "$" && code[j + 1] === "{") {
|
|
170
|
+
let depth = 1;
|
|
171
|
+
j += 2;
|
|
172
|
+
while (j < code.length && depth > 0) {
|
|
173
|
+
if (code[j] === "{") depth++;
|
|
174
|
+
else if (code[j] === "}") depth--;
|
|
175
|
+
j++;
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
j++;
|
|
180
|
+
}
|
|
181
|
+
result += code.slice(i, j);
|
|
182
|
+
i = j;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Try to match the catch pattern at the current position
|
|
186
|
+
catchRe.lastIndex = i;
|
|
187
|
+
const match = catchRe.exec(code);
|
|
188
|
+
if (match && match.index === i) {
|
|
189
|
+
strippedCount++;
|
|
190
|
+
i += match[0].length;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Regular character
|
|
194
|
+
result += code[i];
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { cleaned: result, strippedCount };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function runExec(
|
|
202
|
+
code: string,
|
|
203
|
+
session: string,
|
|
204
|
+
logger: LoggerApi,
|
|
205
|
+
visualize = false,
|
|
206
|
+
pageId?: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
|
|
209
|
+
if (strippedCount > 0) {
|
|
210
|
+
console.log("(Stripped `.catch(() => {})` — letting errors bubble up)");
|
|
211
|
+
}
|
|
212
|
+
logger.info("exec-start", {
|
|
213
|
+
session,
|
|
214
|
+
codeLength: cleanedCode.length,
|
|
215
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
216
|
+
visualize,
|
|
217
|
+
pageId,
|
|
218
|
+
});
|
|
219
|
+
const {
|
|
220
|
+
browser,
|
|
221
|
+
context,
|
|
222
|
+
page,
|
|
223
|
+
pageId: resolvedPageId,
|
|
224
|
+
} = await connect(session, logger, 10000, {
|
|
225
|
+
pageId,
|
|
226
|
+
requireSinglePage: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const STALL_THRESHOLD_MS = 60_000;
|
|
230
|
+
let lastActivityTs = Date.now();
|
|
231
|
+
const onActivity = () => {
|
|
232
|
+
lastActivityTs = Date.now();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const stallInterval = setInterval(() => {
|
|
236
|
+
const silenceMs = Date.now() - lastActivityTs;
|
|
237
|
+
if (silenceMs >= STALL_THRESHOLD_MS) {
|
|
238
|
+
logger.warn("exec-stall-warning", {
|
|
239
|
+
session,
|
|
240
|
+
silenceMs,
|
|
241
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
242
|
+
});
|
|
243
|
+
console.warn(
|
|
244
|
+
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — exec may be hung (code: ${cleanedCode.slice(0, 100)}...)`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}, STALL_THRESHOLD_MS);
|
|
248
|
+
|
|
249
|
+
const execStartTs = Date.now();
|
|
250
|
+
const sigintHandler = () => {
|
|
251
|
+
logger.info("exec-interrupted", {
|
|
252
|
+
session,
|
|
253
|
+
duration: Date.now() - execStartTs,
|
|
254
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
process.on("SIGINT", sigintHandler);
|
|
258
|
+
|
|
259
|
+
wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
|
|
260
|
+
|
|
261
|
+
if (visualize) {
|
|
262
|
+
await installInstrumentation(page, { visualize: true, logger });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const execState: Record<string, unknown> = {};
|
|
267
|
+
|
|
268
|
+
const networkLog = (
|
|
269
|
+
opts: {
|
|
270
|
+
last?: number;
|
|
271
|
+
filter?: string;
|
|
272
|
+
method?: string;
|
|
273
|
+
pageId?: string;
|
|
274
|
+
} = {},
|
|
275
|
+
) => {
|
|
276
|
+
return readNetworkLog(session, opts);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const actionLog = (
|
|
280
|
+
opts: {
|
|
281
|
+
last?: number;
|
|
282
|
+
filter?: string;
|
|
283
|
+
action?: string;
|
|
284
|
+
source?: string;
|
|
285
|
+
pageId?: string;
|
|
286
|
+
} = {},
|
|
287
|
+
) => {
|
|
288
|
+
return readActionLog(session, opts);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const helpers = {
|
|
292
|
+
page,
|
|
293
|
+
context,
|
|
294
|
+
state: execState,
|
|
295
|
+
browser,
|
|
296
|
+
networkLog,
|
|
297
|
+
actionLog,
|
|
298
|
+
console,
|
|
299
|
+
setTimeout,
|
|
300
|
+
setInterval,
|
|
301
|
+
clearTimeout,
|
|
302
|
+
clearInterval,
|
|
303
|
+
fetch,
|
|
304
|
+
URL,
|
|
305
|
+
Buffer,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const helperNames = Object.keys(helpers);
|
|
309
|
+
const fn = compileExecFunction(cleanedCode, helperNames);
|
|
310
|
+
|
|
311
|
+
const result = await fn(...Object.values(helpers));
|
|
312
|
+
logger.info("exec-success", { session, hasResult: result !== undefined });
|
|
313
|
+
if (result !== undefined) {
|
|
314
|
+
console.log(
|
|
315
|
+
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
316
|
+
);
|
|
317
|
+
} else {
|
|
318
|
+
console.log("Executed successfully");
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
logger.error("exec-error", {
|
|
322
|
+
error: err,
|
|
323
|
+
session,
|
|
324
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
325
|
+
});
|
|
326
|
+
throw err;
|
|
327
|
+
} finally {
|
|
328
|
+
clearInterval(stallInterval);
|
|
329
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
330
|
+
disconnectBrowser(browser, logger, session);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function parseJsonArg(label: string, raw: string): unknown {
|
|
335
|
+
try {
|
|
336
|
+
return JSON.parse(raw);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Invalid JSON in ${label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isProcessRunning(pid: number): boolean {
|
|
345
|
+
try {
|
|
346
|
+
process.kill(pid, 0);
|
|
347
|
+
return true;
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function stopExistingFailedRunSession(
|
|
354
|
+
session: string,
|
|
355
|
+
logger: LoggerApi,
|
|
356
|
+
): Promise<void> {
|
|
357
|
+
const existingState = readSessionState(session, logger);
|
|
358
|
+
if (!existingState || existingState.status !== "failed") {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
logger.info("run-release-existing-failed-session", {
|
|
362
|
+
session,
|
|
363
|
+
pid: existingState.pid,
|
|
364
|
+
port: existingState.port,
|
|
365
|
+
});
|
|
366
|
+
clearSessionState(session, logger);
|
|
367
|
+
|
|
368
|
+
if (existingState.pid == null) return;
|
|
369
|
+
|
|
370
|
+
const stopDeadline = Date.now() + 3_000;
|
|
371
|
+
while (isProcessRunning(existingState.pid) && Date.now() < stopDeadline) {
|
|
372
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
373
|
+
}
|
|
374
|
+
if (isProcessRunning(existingState.pid)) {
|
|
375
|
+
logger.warn("run-release-existing-failed-session-timeout", {
|
|
376
|
+
session,
|
|
377
|
+
pid: existingState.pid,
|
|
378
|
+
});
|
|
379
|
+
console.warn(
|
|
380
|
+
`Existing failed workflow process for session "${session}" (pid ${existingState.pid}) is still shutting down; continuing.`,
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
console.log(
|
|
385
|
+
`Closed existing failed workflow process for session "${session}" (pid ${existingState.pid}).`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function readJsonFileIfExists(path: string): unknown {
|
|
390
|
+
if (!existsSync(path)) return null;
|
|
391
|
+
try {
|
|
392
|
+
return JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function readFailureDetails(path: string): {
|
|
399
|
+
message?: string;
|
|
400
|
+
phase?: "setup" | "workflow";
|
|
401
|
+
} | null {
|
|
402
|
+
const raw = readJsonFileIfExists(path);
|
|
403
|
+
if (!raw || typeof raw !== "object") return null;
|
|
404
|
+
|
|
405
|
+
const message = (raw as { message?: unknown }).message;
|
|
406
|
+
const phase = (raw as { phase?: unknown }).phase;
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
message: typeof message === "string" ? message : undefined,
|
|
410
|
+
phase: phase === "setup" || phase === "workflow" ? phase : undefined,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function waitForFailureDetails(
|
|
415
|
+
path: string,
|
|
416
|
+
timeoutMs = 1_000,
|
|
417
|
+
): Promise<{
|
|
418
|
+
message?: string;
|
|
419
|
+
phase?: "setup" | "workflow";
|
|
420
|
+
} | null> {
|
|
421
|
+
const deadline = Date.now() + timeoutMs;
|
|
422
|
+
while (Date.now() < deadline) {
|
|
423
|
+
const details = readFailureDetails(path);
|
|
424
|
+
if (details?.message) return details;
|
|
425
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 25));
|
|
426
|
+
}
|
|
427
|
+
return readFailureDetails(path);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function streamOutputSince(path: string, offset: number): number {
|
|
431
|
+
if (!existsSync(path)) return offset;
|
|
432
|
+
const output = readFileSync(path);
|
|
433
|
+
if (output.length <= offset) return output.length;
|
|
434
|
+
process.stdout.write(output.subarray(offset));
|
|
435
|
+
return output.length;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
type WaitForWorkflowOutcomeArgs = {
|
|
439
|
+
session: string;
|
|
440
|
+
pid: number;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
type WorkflowOutcome = {
|
|
444
|
+
status: "completed" | "paused" | "failed" | "exited";
|
|
445
|
+
message?: string;
|
|
446
|
+
phase?: "setup" | "workflow";
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
function clearSignalIfExists(path: string): void {
|
|
450
|
+
if (!existsSync(path)) return;
|
|
451
|
+
try {
|
|
452
|
+
unlinkSync(path);
|
|
453
|
+
} catch {
|
|
454
|
+
// Ignore cleanup failures; next checks still validate actual state.
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function waitForWorkflowOutcome(
|
|
459
|
+
args: WaitForWorkflowOutcomeArgs,
|
|
460
|
+
): Promise<WorkflowOutcome> {
|
|
461
|
+
const signalPaths = getPauseSignalPaths(args.session);
|
|
462
|
+
if (args.pid <= 0) {
|
|
463
|
+
return { status: "exited" };
|
|
464
|
+
}
|
|
465
|
+
let outputOffset = 0;
|
|
466
|
+
|
|
467
|
+
while (true) {
|
|
468
|
+
outputOffset = streamOutputSince(
|
|
469
|
+
signalPaths.outputSignalPath,
|
|
470
|
+
outputOffset,
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
if (existsSync(signalPaths.failedSignalPath)) {
|
|
474
|
+
outputOffset = streamOutputSince(
|
|
475
|
+
signalPaths.outputSignalPath,
|
|
476
|
+
outputOffset,
|
|
477
|
+
);
|
|
478
|
+
const failureDetails = await waitForFailureDetails(
|
|
479
|
+
signalPaths.failedSignalPath,
|
|
480
|
+
);
|
|
481
|
+
return {
|
|
482
|
+
status: "failed",
|
|
483
|
+
message: failureDetails?.message,
|
|
484
|
+
phase: failureDetails?.phase,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (existsSync(signalPaths.completedSignalPath)) {
|
|
489
|
+
outputOffset = streamOutputSince(
|
|
490
|
+
signalPaths.outputSignalPath,
|
|
491
|
+
outputOffset,
|
|
492
|
+
);
|
|
493
|
+
return { status: "completed" };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (existsSync(signalPaths.pausedSignalPath)) {
|
|
497
|
+
outputOffset = streamOutputSince(
|
|
498
|
+
signalPaths.outputSignalPath,
|
|
499
|
+
outputOffset,
|
|
500
|
+
);
|
|
501
|
+
return { status: "paused" };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!isProcessRunning(args.pid)) {
|
|
505
|
+
outputOffset = streamOutputSince(
|
|
506
|
+
signalPaths.outputSignalPath,
|
|
507
|
+
outputOffset,
|
|
508
|
+
);
|
|
509
|
+
return { status: "exited" };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function runResume(
|
|
517
|
+
session: string,
|
|
518
|
+
logger: LoggerApi,
|
|
519
|
+
sessionState: SessionState,
|
|
520
|
+
): Promise<void> {
|
|
521
|
+
const {
|
|
522
|
+
pausedSignalPath,
|
|
523
|
+
resumeSignalPath,
|
|
524
|
+
completedSignalPath,
|
|
525
|
+
failedSignalPath,
|
|
526
|
+
outputSignalPath,
|
|
527
|
+
} = getPauseSignalPaths(session);
|
|
528
|
+
|
|
529
|
+
if (!existsSync(pausedSignalPath)) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
`Session "${session}" is not paused. Run "libretto run ... --session ${session}" and call pause("${session}") first.`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (sessionState.pid == null || !isProcessRunning(sessionState.pid)) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`No active paused workflow found for session "${session}" (worker pid ${sessionState.pid ?? "unknown"} is not running).`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Clear stale pause/output markers before signaling resume so we always wait
|
|
542
|
+
// for the next pause/completion and only stream post-resume logs.
|
|
543
|
+
clearSignalIfExists(pausedSignalPath);
|
|
544
|
+
clearSignalIfExists(outputSignalPath);
|
|
545
|
+
clearSignalIfExists(completedSignalPath);
|
|
546
|
+
clearSignalIfExists(failedSignalPath);
|
|
547
|
+
setSessionStatus(session, "active", logger);
|
|
548
|
+
|
|
549
|
+
writeFileSync(
|
|
550
|
+
resumeSignalPath,
|
|
551
|
+
JSON.stringify(
|
|
552
|
+
{
|
|
553
|
+
resumedAt: new Date().toISOString(),
|
|
554
|
+
sourcePid: process.pid,
|
|
555
|
+
},
|
|
556
|
+
null,
|
|
557
|
+
2,
|
|
558
|
+
),
|
|
559
|
+
"utf8",
|
|
560
|
+
);
|
|
561
|
+
console.log(`Resume signal sent for session "${session}".`);
|
|
562
|
+
|
|
563
|
+
const outcome = await waitForWorkflowOutcome({
|
|
564
|
+
session,
|
|
565
|
+
pid: sessionState.pid!,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
if (outcome.status === "completed") {
|
|
569
|
+
setSessionStatus(session, "completed", logger);
|
|
570
|
+
console.log("Integration completed.");
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (outcome.status === "failed") {
|
|
574
|
+
setSessionStatus(session, "failed", logger);
|
|
575
|
+
throw new Error(
|
|
576
|
+
outcome.message
|
|
577
|
+
? `Workflow failed after resume: ${outcome.message}`
|
|
578
|
+
: "Workflow failed after resume.",
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (outcome.status === "exited") {
|
|
582
|
+
setSessionStatus(session, "exited", logger);
|
|
583
|
+
throw new Error(
|
|
584
|
+
`Workflow process for session "${session}" exited before reporting completion or pause.`,
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
setSessionStatus(session, "paused", logger);
|
|
588
|
+
console.log("Workflow paused.");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async function runIntegrationFromFile(
|
|
592
|
+
args: RunIntegrationCommandRequest,
|
|
593
|
+
logger: LoggerApi,
|
|
594
|
+
): Promise<void> {
|
|
595
|
+
await stopExistingFailedRunSession(args.session, logger);
|
|
596
|
+
const signalPaths = getPauseSignalPaths(args.session);
|
|
597
|
+
clearSignalIfExists(signalPaths.pausedSignalPath);
|
|
598
|
+
clearSignalIfExists(signalPaths.resumeSignalPath);
|
|
599
|
+
clearSignalIfExists(signalPaths.completedSignalPath);
|
|
600
|
+
clearSignalIfExists(signalPaths.failedSignalPath);
|
|
601
|
+
clearSignalIfExists(signalPaths.outputSignalPath);
|
|
602
|
+
|
|
603
|
+
const workerEntryPath = fileURLToPath(
|
|
604
|
+
new URL("../workers/run-integration-worker.js", import.meta.url),
|
|
605
|
+
);
|
|
606
|
+
const payload = JSON.stringify({
|
|
607
|
+
integrationPath: args.integrationPath,
|
|
608
|
+
exportName: args.exportName,
|
|
609
|
+
session: args.session,
|
|
610
|
+
params: args.params,
|
|
611
|
+
headless: args.headless,
|
|
612
|
+
visualize: args.visualize,
|
|
613
|
+
authProfileDomain: args.authProfileDomain,
|
|
614
|
+
viewport: args.viewport,
|
|
615
|
+
} satisfies RunIntegrationWorkerRequest);
|
|
616
|
+
const worker = spawn(
|
|
617
|
+
process.execPath,
|
|
618
|
+
[
|
|
619
|
+
tsxCliPath,
|
|
620
|
+
...(args.tsconfigPath ? ["--tsconfig", args.tsconfigPath] : []),
|
|
621
|
+
workerEntryPath,
|
|
622
|
+
payload,
|
|
623
|
+
],
|
|
624
|
+
{
|
|
625
|
+
detached: true,
|
|
626
|
+
stdio: "ignore",
|
|
627
|
+
env: process.env,
|
|
628
|
+
},
|
|
629
|
+
);
|
|
630
|
+
worker.unref();
|
|
631
|
+
const outcome = await waitForWorkflowOutcome({
|
|
632
|
+
session: args.session,
|
|
633
|
+
pid: worker.pid ?? 0,
|
|
634
|
+
});
|
|
635
|
+
if (outcome.status === "paused") {
|
|
636
|
+
setSessionStatus(args.session, "paused", logger);
|
|
637
|
+
console.log("Workflow paused.");
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (outcome.status === "failed") {
|
|
641
|
+
setSessionStatus(args.session, "failed", logger);
|
|
642
|
+
if (outcome.phase === "workflow") {
|
|
643
|
+
throw new Error(
|
|
644
|
+
`${outcome.message ?? "Workflow failed during run."}\nBrowser is still open. You can use \`exec\` to inspect it. Call \`run\` to re-run the workflow.`,
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
throw new Error(outcome.message ?? "Workflow failed during run.");
|
|
648
|
+
}
|
|
649
|
+
if (outcome.status === "exited") {
|
|
650
|
+
setSessionStatus(args.session, "exited", logger);
|
|
651
|
+
throw new Error(
|
|
652
|
+
"Workflow process exited before reporting completion or pause during run.",
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
setSessionStatus(args.session, "completed", logger);
|
|
656
|
+
console.log("Integration completed.");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export const execInput = SimpleCLI.input({
|
|
660
|
+
positionals: [
|
|
661
|
+
SimpleCLI.positional("codeParts", z.array(z.string()).default([]), {
|
|
662
|
+
help: "Playwright TypeScript code to execute",
|
|
663
|
+
variadic: true,
|
|
664
|
+
}),
|
|
665
|
+
],
|
|
666
|
+
named: {
|
|
667
|
+
session: sessionOption(),
|
|
668
|
+
visualize: SimpleCLI.flag({
|
|
669
|
+
help: "Enable ghost cursor + highlight visualization",
|
|
670
|
+
}),
|
|
671
|
+
page: pageOption(),
|
|
672
|
+
},
|
|
673
|
+
}).refine(
|
|
674
|
+
(input) => input.codeParts.length > 0,
|
|
675
|
+
`Usage: libretto exec <code> [--session <name>] [--visualize]`,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
export const execCommand = SimpleCLI.command({
|
|
679
|
+
description: "Execute Playwright TypeScript code",
|
|
680
|
+
})
|
|
681
|
+
.input(execInput)
|
|
682
|
+
.use(withRequiredSession())
|
|
683
|
+
.handle(async ({ input, ctx }) => {
|
|
684
|
+
await runExec(
|
|
685
|
+
input.codeParts.join(" "),
|
|
686
|
+
ctx.session,
|
|
687
|
+
ctx.logger,
|
|
688
|
+
input.visualize,
|
|
689
|
+
input.page,
|
|
690
|
+
);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const runUsage = `Usage: libretto run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
|
|
694
|
+
|
|
695
|
+
export const runInput = SimpleCLI.input({
|
|
696
|
+
positionals: [
|
|
697
|
+
SimpleCLI.positional("integrationFile", z.string().optional(), {
|
|
698
|
+
help: "Path to the integration file",
|
|
699
|
+
}),
|
|
700
|
+
SimpleCLI.positional("integrationExport", z.string().optional(), {
|
|
701
|
+
help: "Named workflow export to run",
|
|
702
|
+
}),
|
|
703
|
+
],
|
|
704
|
+
named: {
|
|
705
|
+
session: sessionOption(),
|
|
706
|
+
params: SimpleCLI.option(z.string().optional(), {
|
|
707
|
+
help: "Inline JSON params",
|
|
708
|
+
}),
|
|
709
|
+
paramsFile: SimpleCLI.option(z.string().optional(), {
|
|
710
|
+
name: "params-file",
|
|
711
|
+
help: "Path to a JSON params file",
|
|
712
|
+
}),
|
|
713
|
+
tsconfig: SimpleCLI.option(z.string().optional(), {
|
|
714
|
+
help: "Path to a tsconfig used for workflow module resolution",
|
|
715
|
+
}),
|
|
716
|
+
headed: SimpleCLI.flag({ help: "Run in headed mode" }),
|
|
717
|
+
headless: SimpleCLI.flag({ help: "Run in headless mode" }),
|
|
718
|
+
noVisualize: SimpleCLI.flag({
|
|
719
|
+
name: "no-visualize",
|
|
720
|
+
help: "Disable ghost cursor + highlight visualization in headed mode",
|
|
721
|
+
}),
|
|
722
|
+
authProfile: SimpleCLI.option(z.string().optional(), {
|
|
723
|
+
name: "auth-profile",
|
|
724
|
+
help: "Domain for local auth profile (e.g. apps.example.com)",
|
|
725
|
+
}),
|
|
726
|
+
viewport: SimpleCLI.option(z.string().optional(), {
|
|
727
|
+
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
|
|
728
|
+
}),
|
|
729
|
+
},
|
|
730
|
+
})
|
|
731
|
+
.refine(
|
|
732
|
+
(input) => Boolean(input.integrationFile && input.integrationExport),
|
|
733
|
+
runUsage,
|
|
734
|
+
)
|
|
735
|
+
.refine(
|
|
736
|
+
(input) => !(input.params && input.paramsFile),
|
|
737
|
+
"Pass either --params or --params-file, not both.",
|
|
738
|
+
)
|
|
739
|
+
.refine(
|
|
740
|
+
(input) => !(input.headed && input.headless),
|
|
741
|
+
"Cannot pass both --headed and --headless.",
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
function resolveRunParams(
|
|
745
|
+
rawInlineParams: string | undefined,
|
|
746
|
+
paramsFile: string | undefined,
|
|
747
|
+
): unknown {
|
|
748
|
+
if (paramsFile) {
|
|
749
|
+
let content: string;
|
|
750
|
+
try {
|
|
751
|
+
content = readFileSync(paramsFile, "utf8");
|
|
752
|
+
} catch {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`Could not read --params-file "${paramsFile}". Ensure the file exists and is readable.`,
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
return parseJsonArg("--params-file", content);
|
|
758
|
+
}
|
|
759
|
+
if (rawInlineParams) {
|
|
760
|
+
return parseJsonArg("--params", rawInlineParams);
|
|
761
|
+
}
|
|
762
|
+
return {};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export const runCommand = SimpleCLI.command({
|
|
766
|
+
description: "Run an exported Libretto workflow from a file",
|
|
767
|
+
})
|
|
768
|
+
.input(runInput)
|
|
769
|
+
.use(withAutoSession())
|
|
770
|
+
.handle(async ({ input, ctx }) => {
|
|
771
|
+
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
772
|
+
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
773
|
+
|
|
774
|
+
const params = resolveRunParams(input.params, input.paramsFile);
|
|
775
|
+
const headlessMode = input.headed
|
|
776
|
+
? false
|
|
777
|
+
: input.headless
|
|
778
|
+
? true
|
|
779
|
+
: undefined;
|
|
780
|
+
const visualize = !input.noVisualize;
|
|
781
|
+
const viewport = resolveViewport(
|
|
782
|
+
parseViewportArg(input.viewport),
|
|
783
|
+
ctx.logger,
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
await runIntegrationFromFile(
|
|
787
|
+
{
|
|
788
|
+
integrationPath: input.integrationFile!,
|
|
789
|
+
exportName: input.integrationExport!,
|
|
790
|
+
session: ctx.session,
|
|
791
|
+
params,
|
|
792
|
+
tsconfigPath: input.tsconfig,
|
|
793
|
+
headless: headlessMode ?? false,
|
|
794
|
+
visualize,
|
|
795
|
+
authProfileDomain: input.authProfile,
|
|
796
|
+
viewport,
|
|
797
|
+
},
|
|
798
|
+
ctx.logger,
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
export const resumeInput = SimpleCLI.input({
|
|
803
|
+
positionals: [],
|
|
804
|
+
named: {
|
|
805
|
+
session: sessionOption(),
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
export const resumeCommand = SimpleCLI.command({
|
|
810
|
+
description: "Resume a paused workflow for the current session",
|
|
811
|
+
})
|
|
812
|
+
.input(resumeInput)
|
|
813
|
+
.use(withRequiredSession())
|
|
814
|
+
.handle(async ({ ctx }) => {
|
|
815
|
+
await runResume(ctx.session, ctx.logger, ctx.sessionState);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
export const executionCommands = {
|
|
819
|
+
exec: execCommand,
|
|
820
|
+
run: runCommand,
|
|
821
|
+
resume: resumeCommand,
|
|
822
|
+
};
|