libretto 0.5.5 → 0.6.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 +23 -10
- package/README.template.md +23 -10
- package/dist/cli/cli.js +10 -0
- package/dist/cli/commands/ai.js +77 -2
- package/dist/cli/commands/browser.js +98 -8
- package/dist/cli/commands/execution.js +152 -56
- package/dist/cli/commands/setup.js +390 -0
- package/dist/cli/commands/snapshot.js +2 -2
- package/dist/cli/commands/status.js +62 -0
- package/dist/cli/core/{snapshot-api-config.js → ai-model.js} +81 -7
- package/dist/cli/core/api-snapshot-analyzer.js +7 -5
- package/dist/cli/core/browser.js +202 -36
- package/dist/cli/core/{ai-config.js → config.js} +14 -79
- package/dist/cli/core/context.js +1 -25
- package/dist/cli/core/deploy-artifact.js +121 -61
- package/dist/cli/core/providers/browserbase.js +53 -0
- package/dist/cli/core/providers/index.js +48 -0
- package/dist/cli/core/providers/kernel.js +46 -0
- package/dist/cli/core/providers/libretto-cloud.js +58 -0
- package/dist/cli/core/readonly-exec.js +231 -0
- package/dist/{shared/llm/client.js → cli/core/resolve-model.js} +4 -68
- package/dist/cli/core/session.js +53 -0
- package/dist/cli/core/skill-version.js +73 -0
- package/dist/cli/core/telemetry.js +1 -54
- package/dist/cli/index.js +1 -7
- package/dist/cli/router.js +4 -4
- package/dist/cli/workers/run-integration-runtime.js +19 -13
- package/dist/cli/workers/run-integration-worker-protocol.js +5 -2
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -2
- package/dist/runtime/extract/extract.d.ts +2 -2
- package/dist/runtime/extract/extract.js +4 -2
- package/dist/runtime/extract/index.d.ts +1 -1
- package/dist/runtime/recovery/agent.d.ts +2 -3
- package/dist/runtime/recovery/agent.js +5 -3
- package/dist/runtime/recovery/errors.d.ts +2 -3
- package/dist/runtime/recovery/errors.js +4 -2
- package/dist/runtime/recovery/index.d.ts +1 -2
- package/dist/runtime/recovery/recovery.d.ts +2 -3
- package/dist/runtime/recovery/recovery.js +3 -3
- package/dist/shared/debug/pause.js +4 -21
- package/dist/shared/run/api.d.ts +2 -0
- package/dist/shared/run/browser.d.ts +9 -1
- package/dist/shared/run/browser.js +43 -3
- package/dist/shared/state/index.d.ts +1 -1
- package/dist/shared/state/index.js +2 -0
- package/dist/shared/state/session-state.d.ts +20 -1
- package/dist/shared/state/session-state.js +12 -2
- package/dist/shared/workflow/workflow.d.ts +2 -1
- package/dist/shared/workflow/workflow.js +16 -9
- package/package.json +17 -16
- package/scripts/postinstall.mjs +13 -11
- package/scripts/skills-libretto.mjs +14 -4
- package/skills/AGENTS.md +11 -0
- package/skills/libretto/SKILL.md +30 -9
- package/skills/libretto/references/auth-profiles.md +1 -1
- package/skills/libretto/references/code-generation-rules.md +3 -3
- package/skills/libretto/references/configuration-file-reference.md +11 -6
- package/skills/libretto-readonly/SKILL.md +95 -0
- package/src/cli/cli.ts +10 -0
- package/src/cli/commands/ai.ts +111 -1
- package/src/cli/commands/browser.ts +111 -9
- package/src/cli/commands/execution.ts +181 -74
- package/src/cli/commands/setup.ts +516 -0
- package/src/cli/commands/snapshot.ts +2 -2
- package/src/cli/commands/status.ts +79 -0
- package/src/cli/core/{snapshot-api-config.ts → ai-model.ts} +154 -14
- package/src/cli/core/api-snapshot-analyzer.ts +7 -5
- package/src/cli/core/browser.ts +242 -35
- package/src/cli/core/{ai-config.ts → config.ts} +14 -108
- package/src/cli/core/context.ts +1 -45
- package/src/cli/core/deploy-artifact.ts +141 -71
- package/src/cli/core/providers/browserbase.ts +57 -0
- package/src/cli/core/providers/index.ts +62 -0
- package/src/cli/core/providers/kernel.ts +49 -0
- package/src/cli/core/providers/libretto-cloud.ts +61 -0
- package/src/cli/core/providers/types.ts +9 -0
- package/src/cli/core/readonly-exec.ts +284 -0
- package/src/{shared/llm/client.ts → cli/core/resolve-model.ts} +3 -85
- package/src/cli/core/session.ts +75 -2
- package/src/cli/core/skill-version.ts +93 -0
- package/src/cli/core/telemetry.ts +0 -52
- package/src/cli/index.ts +0 -6
- package/src/cli/router.ts +4 -4
- package/src/cli/workers/run-integration-runtime.ts +18 -16
- package/src/cli/workers/run-integration-worker-protocol.ts +4 -1
- package/src/index.ts +1 -7
- package/src/runtime/extract/extract.ts +6 -5
- package/src/runtime/recovery/agent.ts +5 -4
- package/src/runtime/recovery/errors.ts +4 -3
- package/src/runtime/recovery/recovery.ts +4 -4
- package/src/shared/debug/pause.ts +4 -23
- package/src/shared/run/browser.ts +50 -1
- package/src/shared/state/index.ts +2 -0
- package/src/shared/state/session-state.ts +10 -0
- package/src/shared/workflow/workflow.ts +24 -13
- package/dist/cli/commands/init.js +0 -286
- package/dist/cli/commands/logs.js +0 -117
- package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
- package/dist/shared/llm/ai-sdk-adapter.js +0 -49
- package/dist/shared/llm/client.d.ts +0 -13
- package/dist/shared/llm/index.d.ts +0 -5
- package/dist/shared/llm/index.js +0 -6
- package/dist/shared/llm/types.d.ts +0 -67
- package/src/cli/commands/init.ts +0 -331
- package/src/cli/commands/logs.ts +0 -128
- package/src/shared/llm/ai-sdk-adapter.ts +0 -81
- package/src/shared/llm/index.ts +0 -3
- package/src/shared/llm/types.ts +0 -63
- /package/dist/{shared/llm → cli/core/providers}/types.js +0 -0
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
appendFileSync,
|
|
3
3
|
existsSync,
|
|
4
4
|
readFileSync,
|
|
5
|
-
writeFileSync,
|
|
6
5
|
} from "node:fs";
|
|
7
6
|
import type { Page } from "playwright";
|
|
8
7
|
import {
|
|
@@ -65,31 +64,6 @@ export function readNetworkLog(
|
|
|
65
64
|
return entries;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
export function formatNetworkEntry(e: NetworkLogEntry): string {
|
|
69
|
-
const time = e.ts.replace(/.*T/, "").replace(/\.\d+Z$/, "");
|
|
70
|
-
const duration = e.durationMs != null ? `${e.durationMs}ms` : "?ms";
|
|
71
|
-
const size = e.size != null ? `${e.size}B` : "";
|
|
72
|
-
const parts = [
|
|
73
|
-
`[${time}]`,
|
|
74
|
-
`${e.status}`,
|
|
75
|
-
`${e.method.padEnd(6)}`,
|
|
76
|
-
e.url,
|
|
77
|
-
duration,
|
|
78
|
-
size,
|
|
79
|
-
].filter(Boolean);
|
|
80
|
-
let line = parts.join(" ");
|
|
81
|
-
if (e.postData) {
|
|
82
|
-
line += `\n body: ${e.postData.substring(0, 120)}${e.postData.length > 120 ? "..." : ""}`;
|
|
83
|
-
}
|
|
84
|
-
return line;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function clearNetworkLog(session: string): void {
|
|
88
|
-
assertSessionStateExistsOrThrow(session);
|
|
89
|
-
const logPath = getSessionNetworkLogPath(session);
|
|
90
|
-
writeFileSync(logPath, "");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
67
|
export type ActionLogEntry = {
|
|
94
68
|
ts: string;
|
|
95
69
|
pageId?: string;
|
|
@@ -182,32 +156,6 @@ export function readActionLog(
|
|
|
182
156
|
return entries;
|
|
183
157
|
}
|
|
184
158
|
|
|
185
|
-
export function formatActionEntry(e: ActionLogEntry): string {
|
|
186
|
-
const time = e.ts.replace(/.*T/, "").replace(/\.\d+Z$/, "");
|
|
187
|
-
const src = e.source.toUpperCase().padEnd(5);
|
|
188
|
-
const displaySelector = e.bestSemanticSelector || e.selector;
|
|
189
|
-
const parts = [`[${time}]`, `[${src}]`, e.action];
|
|
190
|
-
if (displaySelector) parts.push(displaySelector);
|
|
191
|
-
if (e.targetSelector && e.targetSelector !== displaySelector) {
|
|
192
|
-
parts.push(`target=${e.targetSelector}`);
|
|
193
|
-
}
|
|
194
|
-
if (e.nearbyText) parts.push(`text="${e.nearbyText}"`);
|
|
195
|
-
if (e.coordinates) {
|
|
196
|
-
parts.push(`@(${e.coordinates.x},${e.coordinates.y})`);
|
|
197
|
-
}
|
|
198
|
-
if (e.value) parts.push(`"${e.value}"`);
|
|
199
|
-
if (e.url) parts.push(e.url);
|
|
200
|
-
if (e.duration != null) parts.push(`${e.duration}ms`);
|
|
201
|
-
if (!e.success) parts.push(`ERROR: ${e.error || "unknown"}`);
|
|
202
|
-
return parts.join(" ");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function clearActionLog(session: string): void {
|
|
206
|
-
assertSessionStateExistsOrThrow(session);
|
|
207
|
-
const logPath = getSessionActionsLogPath(session);
|
|
208
|
-
writeFileSync(logPath, "");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
159
|
const LOCATOR_ACTION_METHODS = [
|
|
212
160
|
"click",
|
|
213
161
|
"dblclick",
|
package/src/cli/index.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runLibrettoCLI } from "./cli.js";
|
|
3
|
-
import {
|
|
4
|
-
maybeConfigureLLMClientFactoryFromEnv,
|
|
5
|
-
setLLMClientFactory,
|
|
6
|
-
} from "./core/context.js";
|
|
7
3
|
|
|
8
|
-
export { setLLMClientFactory };
|
|
9
4
|
export { runClose } from "./commands/browser.js";
|
|
10
5
|
export { runLibrettoCLI };
|
|
11
6
|
|
|
12
|
-
maybeConfigureLLMClientFactoryFromEnv();
|
|
13
7
|
void runLibrettoCLI();
|
package/src/cli/router.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { aiCommands } from "./commands/ai.js";
|
|
|
2
2
|
import { browserCommands } from "./commands/browser.js";
|
|
3
3
|
import { deployCommand } from "./commands/deploy.js";
|
|
4
4
|
import { executionCommands } from "./commands/execution.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { setupCommand } from "./commands/setup.js";
|
|
6
|
+
import { statusCommand } from "./commands/status.js";
|
|
7
7
|
import { snapshotCommand } from "./commands/snapshot.js";
|
|
8
8
|
import { SimpleCLI } from "./framework/simple-cli.js";
|
|
9
9
|
|
|
@@ -11,9 +11,9 @@ export const cliRoutes = {
|
|
|
11
11
|
...browserCommands,
|
|
12
12
|
deploy: deployCommand,
|
|
13
13
|
...executionCommands,
|
|
14
|
-
...logCommands,
|
|
15
14
|
ai: aiCommands,
|
|
16
|
-
|
|
15
|
+
setup: setupCommand,
|
|
16
|
+
status: statusCommand,
|
|
17
17
|
snapshot: snapshotCommand,
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -5,7 +5,7 @@ import { cwd } from "node:process";
|
|
|
5
5
|
import { isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
getDefaultWorkflowFromModuleExports,
|
|
9
9
|
getWorkflowsFromModuleExports,
|
|
10
10
|
instrumentContext,
|
|
11
11
|
launchBrowser,
|
|
@@ -138,9 +138,8 @@ function getAbsoluteIntegrationPath(integrationPath: string): string {
|
|
|
138
138
|
return absolutePath;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
async function
|
|
141
|
+
async function loadDefaultWorkflow(
|
|
142
142
|
absolutePath: string,
|
|
143
|
-
workflowName: string,
|
|
144
143
|
): Promise<LoadedLibrettoWorkflow> {
|
|
145
144
|
let loadedModule: Record<string, unknown>;
|
|
146
145
|
try {
|
|
@@ -156,22 +155,23 @@ async function loadWorkflowByName(
|
|
|
156
155
|
);
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
return
|
|
158
|
+
const defaultWorkflow = getDefaultWorkflowFromModuleExports(loadedModule);
|
|
159
|
+
if (defaultWorkflow) {
|
|
160
|
+
return defaultWorkflow as LoadedLibrettoWorkflow;
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
const
|
|
163
|
+
const availableWorkflowNames = getWorkflowsFromModuleExports(loadedModule).map(
|
|
165
164
|
(candidate) => candidate.name,
|
|
166
165
|
);
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
if (availableWorkflowNames.length === 0) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`No default-exported workflow found in ${absolutePath}. Export the workflow with \`export default workflow("name", handler)\`.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
172
|
|
|
173
173
|
throw new Error(
|
|
174
|
-
`
|
|
174
|
+
`No default-exported workflow found in ${absolutePath}. libretto run only uses the file's default export. Available named workflows: ${availableWorkflowNames.join(", ")}`,
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -194,7 +194,7 @@ async function runIntegrationInternal(
|
|
|
194
194
|
): Promise<RunIntegrationOutcome> {
|
|
195
195
|
const { logger } = options;
|
|
196
196
|
const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
|
|
197
|
-
const workflow = await
|
|
197
|
+
const workflow = await loadDefaultWorkflow(absolutePath);
|
|
198
198
|
const signalPaths = getPauseSignalPaths(args.session);
|
|
199
199
|
await removeSignalIfExists(signalPaths.pausedSignalPath);
|
|
200
200
|
await removeSignalIfExists(signalPaths.resumeSignalPath);
|
|
@@ -203,12 +203,12 @@ async function runIntegrationInternal(
|
|
|
203
203
|
const restoreStdout = mirrorStdoutToFile(signalPaths.outputSignalPath);
|
|
204
204
|
|
|
205
205
|
console.log(
|
|
206
|
-
`Running workflow "${
|
|
206
|
+
`Running workflow "${workflow.name}" from ${absolutePath} (${args.headless ? "headless" : "headed"})...`,
|
|
207
207
|
);
|
|
208
208
|
|
|
209
209
|
const integrationLogger = logger.withScope("integration-run", {
|
|
210
210
|
integrationPath: absolutePath,
|
|
211
|
-
workflowName:
|
|
211
|
+
workflowName: workflow.name,
|
|
212
212
|
session: args.session,
|
|
213
213
|
});
|
|
214
214
|
|
|
@@ -238,6 +238,9 @@ async function runIntegrationInternal(
|
|
|
238
238
|
headless: args.headless,
|
|
239
239
|
storageStatePath,
|
|
240
240
|
viewport: args.viewport,
|
|
241
|
+
accessMode: args.accessMode,
|
|
242
|
+
cdpEndpoint: args.cdpEndpoint,
|
|
243
|
+
provider: args.provider,
|
|
241
244
|
});
|
|
242
245
|
if (!args.headless && args.visualize !== false) {
|
|
243
246
|
await installHeadedWorkflowVisualization({
|
|
@@ -295,7 +298,6 @@ async function runIntegrationInternal(
|
|
|
295
298
|
JSON.stringify({ completedAt: new Date().toISOString() }, null, 2),
|
|
296
299
|
"utf8",
|
|
297
300
|
);
|
|
298
|
-
console.log("Integration completed.");
|
|
299
301
|
return { status: "completed" };
|
|
300
302
|
} finally {
|
|
301
303
|
restoreStdout();
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { SessionAccessModeSchema } from "../../shared/state/index.js";
|
|
2
3
|
|
|
3
4
|
export const RunIntegrationWorkerRequestSchema = z.object({
|
|
4
5
|
integrationPath: z.string().min(1),
|
|
5
|
-
workflowName: z.string().min(1),
|
|
6
6
|
session: z.string().min(1),
|
|
7
7
|
params: z.unknown(),
|
|
8
8
|
headless: z.boolean(),
|
|
9
9
|
visualize: z.boolean().default(true),
|
|
10
10
|
authProfileDomain: z.string().optional(),
|
|
11
11
|
viewport: z.object({ width: z.number(), height: z.number() }).optional(),
|
|
12
|
+
accessMode: SessionAccessModeSchema.default("write-access"),
|
|
13
|
+
cdpEndpoint: z.string().optional(),
|
|
14
|
+
provider: z.object({ name: z.string(), sessionId: z.string() }).optional(),
|
|
12
15
|
});
|
|
13
16
|
|
|
14
17
|
export type RunIntegrationWorkerRequest = z.infer<
|
package/src/index.ts
CHANGED
|
@@ -16,13 +16,6 @@ export {
|
|
|
16
16
|
jsonlConsoleSink,
|
|
17
17
|
} from "./shared/logger/sinks.js";
|
|
18
18
|
|
|
19
|
-
// LLM client interface
|
|
20
|
-
export type {
|
|
21
|
-
LLMClient,
|
|
22
|
-
Message,
|
|
23
|
-
MessageContentPart,
|
|
24
|
-
} from "./shared/llm/types.js";
|
|
25
|
-
export { createLLMClientFromModel } from "./shared/llm/ai-sdk-adapter.js";
|
|
26
19
|
export {
|
|
27
20
|
SESSION_STATE_VERSION,
|
|
28
21
|
SessionStatusSchema,
|
|
@@ -102,6 +95,7 @@ export {
|
|
|
102
95
|
|
|
103
96
|
// Workflow helpers
|
|
104
97
|
export {
|
|
98
|
+
getDefaultWorkflowFromModuleExports,
|
|
105
99
|
getWorkflowFromModuleExports,
|
|
106
100
|
getWorkflowsFromModuleExports,
|
|
107
101
|
isLibrettoWorkflow,
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
type MinimalLogger,
|
|
5
5
|
defaultLogger,
|
|
6
6
|
} from "../../shared/logger/logger.js";
|
|
7
|
-
import type
|
|
7
|
+
import { generateObject, type LanguageModel } from "ai";
|
|
8
8
|
|
|
9
9
|
export type ExtractOptions<T extends z.ZodType> = {
|
|
10
10
|
page: Page;
|
|
11
11
|
instruction: string;
|
|
12
12
|
schema: T;
|
|
13
|
-
|
|
13
|
+
model: LanguageModel;
|
|
14
14
|
logger?: MinimalLogger;
|
|
15
15
|
/** Optional CSS selector to scope extraction to a specific element. */
|
|
16
16
|
selector?: string;
|
|
@@ -31,7 +31,7 @@ export async function extractFromPage<T extends z.ZodType>(
|
|
|
31
31
|
schema,
|
|
32
32
|
selector,
|
|
33
33
|
logger = defaultLogger,
|
|
34
|
-
|
|
34
|
+
model,
|
|
35
35
|
} = options;
|
|
36
36
|
|
|
37
37
|
let screenshot: string;
|
|
@@ -79,7 +79,8 @@ ${domContent ? `Here is the HTML content for additional context:\n<html>\n${domC
|
|
|
79
79
|
|
|
80
80
|
Extract the requested information from the screenshot and return it in the specified format. Be precise and only extract what is visible.`;
|
|
81
81
|
|
|
82
|
-
const result = await
|
|
82
|
+
const { object: result } = await generateObject({
|
|
83
|
+
model,
|
|
83
84
|
schema,
|
|
84
85
|
messages: [
|
|
85
86
|
{
|
|
@@ -98,5 +99,5 @@ Extract the requested information from the screenshot and return it in the speci
|
|
|
98
99
|
instruction: instruction.slice(0, 100),
|
|
99
100
|
});
|
|
100
101
|
|
|
101
|
-
return result
|
|
102
|
+
return result as z.infer<T>;
|
|
102
103
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type MinimalLogger,
|
|
4
4
|
defaultLogger,
|
|
5
5
|
} from "../../shared/logger/logger.js";
|
|
6
|
-
import type
|
|
6
|
+
import { generateObject, type LanguageModel } from "ai";
|
|
7
7
|
|
|
8
8
|
type BrowserAction =
|
|
9
9
|
| { type: "click"; x: number; y: number; button?: string }
|
|
@@ -183,9 +183,9 @@ export async function executeRecoveryAgent(
|
|
|
183
183
|
page: Page,
|
|
184
184
|
instruction: string,
|
|
185
185
|
logger?: MinimalLogger,
|
|
186
|
-
|
|
186
|
+
model?: LanguageModel,
|
|
187
187
|
): Promise<void> {
|
|
188
|
-
if (!
|
|
188
|
+
if (!model) {
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
const log = logger ?? defaultLogger;
|
|
@@ -213,7 +213,8 @@ export async function executeRecoveryAgent(
|
|
|
213
213
|
|
|
214
214
|
const maxSteps = 3;
|
|
215
215
|
for (let step = 1; step <= maxSteps; step++) {
|
|
216
|
-
const result = await
|
|
216
|
+
const { object: result } = await generateObject({
|
|
217
|
+
model,
|
|
217
218
|
schema: recoveryActionSchema,
|
|
218
219
|
messages: [
|
|
219
220
|
{
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type MinimalLogger,
|
|
4
4
|
defaultLogger,
|
|
5
5
|
} from "../../shared/logger/logger.js";
|
|
6
|
-
import type
|
|
6
|
+
import { generateObject, type LanguageModel } from "ai";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -47,7 +47,7 @@ export async function detectSubmissionError(
|
|
|
47
47
|
page: Page,
|
|
48
48
|
error: unknown,
|
|
49
49
|
logContext: string,
|
|
50
|
-
|
|
50
|
+
model: LanguageModel,
|
|
51
51
|
knownErrors: KnownSubmissionError[] = [],
|
|
52
52
|
logger?: MinimalLogger,
|
|
53
53
|
): Promise<DetectedSubmissionError> {
|
|
@@ -109,7 +109,8 @@ IMPORTANT:
|
|
|
109
109
|
|
|
110
110
|
${domSnapshot ? `<dom_snapshot>\n${domSnapshot}\n</dom_snapshot>` : ""}`;
|
|
111
111
|
|
|
112
|
-
const result = await
|
|
112
|
+
const { object: result } = await generateObject({
|
|
113
|
+
model,
|
|
113
114
|
schema: detectSubmissionErrorSchema,
|
|
114
115
|
messages: [
|
|
115
116
|
{
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type MinimalLogger,
|
|
4
4
|
defaultLogger,
|
|
5
5
|
} from "../../shared/logger/logger.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { LanguageModel } from "ai";
|
|
7
7
|
import { executeRecoveryAgent } from "./agent.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -14,7 +14,7 @@ export async function attemptWithRecovery<T>(
|
|
|
14
14
|
page: Page,
|
|
15
15
|
fn: () => Promise<T>,
|
|
16
16
|
logger?: MinimalLogger,
|
|
17
|
-
|
|
17
|
+
model?: LanguageModel,
|
|
18
18
|
): Promise<T> {
|
|
19
19
|
const log = logger ?? defaultLogger;
|
|
20
20
|
try {
|
|
@@ -33,7 +33,7 @@ export async function attemptWithRecovery<T>(
|
|
|
33
33
|
throw error;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
if (!
|
|
36
|
+
if (!model) {
|
|
37
37
|
throw error;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -45,7 +45,7 @@ export async function attemptWithRecovery<T>(
|
|
|
45
45
|
page,
|
|
46
46
|
"Look at the page to see if there is a popup blocking the screen. If so, close the popup.",
|
|
47
47
|
log,
|
|
48
|
-
|
|
48
|
+
model,
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
return await fn();
|
|
@@ -5,35 +5,16 @@ import {
|
|
|
5
5
|
getPauseSignalPaths,
|
|
6
6
|
removeSignalIfExists,
|
|
7
7
|
} from "../../cli/core/pause-signals.js";
|
|
8
|
-
import {
|
|
9
|
-
listSessionsWithStateFile,
|
|
10
|
-
readSessionState,
|
|
11
|
-
} from "../../cli/core/session.js";
|
|
12
|
-
|
|
13
|
-
function isPidRunning(pid: number): boolean {
|
|
14
|
-
try {
|
|
15
|
-
process.kill(pid, 0);
|
|
16
|
-
return true;
|
|
17
|
-
} catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getRunningSessions(): string[] {
|
|
23
|
-
return listSessionsWithStateFile().filter((candidate) => {
|
|
24
|
-
const state = readSessionState(candidate);
|
|
25
|
-
return state !== null && state.pid != null && isPidRunning(state.pid);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
8
|
+
import { listRunningSessions } from "../../cli/core/session.js";
|
|
28
9
|
|
|
29
10
|
function throwMissingSessionError(): never {
|
|
30
|
-
const runningSessions =
|
|
11
|
+
const runningSessions = listRunningSessions();
|
|
31
12
|
const lines = ["pause(session) requires a non-empty session ID."];
|
|
32
13
|
|
|
33
14
|
if (runningSessions.length > 0) {
|
|
34
15
|
lines.push("", "Running sessions:");
|
|
35
|
-
for (const
|
|
36
|
-
lines.push(` ${
|
|
16
|
+
for (const s of runningSessions) {
|
|
17
|
+
lines.push(` ${s.session}`);
|
|
37
18
|
}
|
|
38
19
|
}
|
|
39
20
|
|
|
@@ -9,9 +9,10 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { ensureLibrettoSessionStatePath } from "../paths/paths.js";
|
|
10
10
|
import {
|
|
11
11
|
SESSION_STATE_VERSION,
|
|
12
|
+
type SessionAccessMode,
|
|
12
13
|
SessionStateFileSchema,
|
|
13
14
|
} from "../state/session-state.js";
|
|
14
|
-
import { readLibrettoConfig } from "../../cli/core/
|
|
15
|
+
import { readLibrettoConfig } from "../../cli/core/config.js";
|
|
15
16
|
|
|
16
17
|
async function pickFreePort(): Promise<number> {
|
|
17
18
|
return await new Promise((resolve, reject) => {
|
|
@@ -34,6 +35,9 @@ export type LaunchBrowserArgs = {
|
|
|
34
35
|
headless?: boolean;
|
|
35
36
|
viewport?: { width: number; height: number };
|
|
36
37
|
storageStatePath?: string;
|
|
38
|
+
accessMode?: SessionAccessMode;
|
|
39
|
+
cdpEndpoint?: string;
|
|
40
|
+
provider?: { name: string; sessionId: string };
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
export type BrowserSession = {
|
|
@@ -97,7 +101,51 @@ export async function launchBrowser({
|
|
|
97
101
|
headless = false,
|
|
98
102
|
viewport = { width: 1366, height: 768 },
|
|
99
103
|
storageStatePath,
|
|
104
|
+
accessMode = "write-access",
|
|
105
|
+
cdpEndpoint,
|
|
106
|
+
provider,
|
|
100
107
|
}: LaunchBrowserArgs): Promise<BrowserSession> {
|
|
108
|
+
// Cloud/remote mode: connect to an existing CDP endpoint instead of launching locally
|
|
109
|
+
if (cdpEndpoint) {
|
|
110
|
+
const browser = await chromium.connectOverCDP(cdpEndpoint);
|
|
111
|
+
const context =
|
|
112
|
+
browser.contexts()[0] ?? (await browser.newContext({ viewport }));
|
|
113
|
+
const page = context.pages()[0] ?? (await context.newPage());
|
|
114
|
+
page.setDefaultTimeout(30_000);
|
|
115
|
+
page.setDefaultNavigationTimeout(45_000);
|
|
116
|
+
|
|
117
|
+
const metadataPath = ensureLibrettoSessionStatePath(sessionName);
|
|
118
|
+
writeFileSync(
|
|
119
|
+
metadataPath,
|
|
120
|
+
JSON.stringify(
|
|
121
|
+
{
|
|
122
|
+
version: SESSION_STATE_VERSION,
|
|
123
|
+
session: sessionName,
|
|
124
|
+
port: 0,
|
|
125
|
+
cdpEndpoint,
|
|
126
|
+
pid: process.pid,
|
|
127
|
+
startedAt: new Date().toISOString(),
|
|
128
|
+
status: "active",
|
|
129
|
+
mode: accessMode,
|
|
130
|
+
...(provider ? { provider } : {}),
|
|
131
|
+
},
|
|
132
|
+
null,
|
|
133
|
+
2,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
browser,
|
|
139
|
+
context,
|
|
140
|
+
page,
|
|
141
|
+
debugPort: 0,
|
|
142
|
+
metadataPath,
|
|
143
|
+
close: async () => {
|
|
144
|
+
await browser.close();
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
101
149
|
const debugPort = await pickFreePort();
|
|
102
150
|
const windowPosition = headless ? undefined : resolveWindowPosition();
|
|
103
151
|
const browser = await chromium.launch({
|
|
@@ -141,6 +189,7 @@ export async function launchBrowser({
|
|
|
141
189
|
pid: process.pid,
|
|
142
190
|
startedAt: new Date().toISOString(),
|
|
143
191
|
status: "active",
|
|
192
|
+
mode: accessMode,
|
|
144
193
|
},
|
|
145
194
|
null,
|
|
146
195
|
2,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export {
|
|
2
|
+
SessionAccessModeSchema,
|
|
2
3
|
SESSION_STATE_VERSION,
|
|
3
4
|
SessionStatusSchema,
|
|
4
5
|
SessionStateFileSchema,
|
|
5
6
|
parseSessionStateData,
|
|
6
7
|
parseSessionStateContent,
|
|
7
8
|
serializeSessionState,
|
|
9
|
+
type SessionAccessMode,
|
|
8
10
|
type SessionStatus,
|
|
9
11
|
type SessionState,
|
|
10
12
|
type SessionStateFile,
|
|
@@ -8,12 +8,19 @@ export const SessionStatusSchema = z.enum([
|
|
|
8
8
|
"completed",
|
|
9
9
|
"failed",
|
|
10
10
|
"exited",
|
|
11
|
+
"cleanup-failed",
|
|
11
12
|
]);
|
|
13
|
+
export const SessionAccessModeSchema = z.enum(["read-only", "write-access"]);
|
|
12
14
|
export const SessionViewportSchema = z.object({
|
|
13
15
|
width: z.number().int().min(1),
|
|
14
16
|
height: z.number().int().min(1),
|
|
15
17
|
});
|
|
16
18
|
|
|
19
|
+
export const ProviderStateSchema = z.object({
|
|
20
|
+
name: z.string(),
|
|
21
|
+
sessionId: z.string(),
|
|
22
|
+
});
|
|
23
|
+
|
|
17
24
|
export const SessionStateFileSchema = z.object({
|
|
18
25
|
version: z.literal(SESSION_STATE_VERSION),
|
|
19
26
|
port: z.number().int().min(0).max(65535),
|
|
@@ -22,10 +29,13 @@ export const SessionStateFileSchema = z.object({
|
|
|
22
29
|
session: z.string().min(1),
|
|
23
30
|
startedAt: z.string().datetime({ offset: true }),
|
|
24
31
|
status: SessionStatusSchema.optional(),
|
|
32
|
+
mode: SessionAccessModeSchema.default("write-access"),
|
|
25
33
|
viewport: SessionViewportSchema.optional(),
|
|
34
|
+
provider: ProviderStateSchema.optional(),
|
|
26
35
|
});
|
|
27
36
|
|
|
28
37
|
export type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
38
|
+
export type SessionAccessMode = z.infer<typeof SessionAccessModeSchema>;
|
|
29
39
|
export type SessionStateFile = z.infer<typeof SessionStateFileSchema>;
|
|
30
40
|
export type SessionState = Omit<SessionStateFile, "version">;
|
|
31
41
|
|
|
@@ -17,10 +17,7 @@ export class LibrettoWorkflow<Input = unknown, Output = unknown> {
|
|
|
17
17
|
public readonly name: string;
|
|
18
18
|
private readonly handler: LibrettoWorkflowHandler<Input, Output>;
|
|
19
19
|
|
|
20
|
-
constructor(
|
|
21
|
-
name: string,
|
|
22
|
-
handler: LibrettoWorkflowHandler<Input, Output>,
|
|
23
|
-
) {
|
|
20
|
+
constructor(name: string, handler: LibrettoWorkflowHandler<Input, Output>) {
|
|
24
21
|
this.name = name;
|
|
25
22
|
this.handler = handler;
|
|
26
23
|
}
|
|
@@ -70,31 +67,45 @@ function addWorkflowOrThrow(
|
|
|
70
67
|
workflowsByName.set(value.name, value);
|
|
71
68
|
}
|
|
72
69
|
|
|
70
|
+
function collectWorkflowsOrThrow(
|
|
71
|
+
values: Iterable<unknown>,
|
|
72
|
+
): ExportedLibrettoWorkflow[] {
|
|
73
|
+
const workflowsByName = new Map<string, ExportedLibrettoWorkflow>();
|
|
74
|
+
|
|
75
|
+
for (const value of values) {
|
|
76
|
+
addWorkflowOrThrow(workflowsByName, value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [...workflowsByName.values()];
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
export function getWorkflowsFromModuleExports(
|
|
74
83
|
moduleExports: WorkflowModuleExports,
|
|
75
84
|
): ExportedLibrettoWorkflow[] {
|
|
76
|
-
const
|
|
85
|
+
const discoveredValues: unknown[] = [];
|
|
77
86
|
|
|
78
87
|
for (const [exportName, value] of Object.entries(moduleExports)) {
|
|
79
88
|
if (exportName === "workflows" && value && typeof value === "object") {
|
|
80
89
|
// Support both `export const workflows = workflow(...)` and
|
|
81
90
|
// `export const workflows = { myWorkflow }`.
|
|
82
91
|
if (isLibrettoWorkflow(value)) {
|
|
83
|
-
|
|
92
|
+
discoveredValues.push(value);
|
|
84
93
|
} else {
|
|
85
|
-
|
|
86
|
-
value as Record<string, unknown>,
|
|
87
|
-
)) {
|
|
88
|
-
addWorkflowOrThrow(workflowsByName, nestedValue);
|
|
89
|
-
}
|
|
94
|
+
discoveredValues.push(...Object.values(value as Record<string, unknown>));
|
|
90
95
|
}
|
|
91
96
|
continue;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
discoveredValues.push(value);
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
return
|
|
102
|
+
return collectWorkflowsOrThrow(discoveredValues);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getDefaultWorkflowFromModuleExports(
|
|
106
|
+
moduleExports: WorkflowModuleExports,
|
|
107
|
+
): ExportedLibrettoWorkflow | null {
|
|
108
|
+
return isLibrettoWorkflow(moduleExports.default) ? moduleExports.default : null;
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
export function getWorkflowFromModuleExports(
|