libretto 0.5.6 → 0.6.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.
Files changed (38) hide show
  1. package/dist/cli/commands/browser.js +31 -6
  2. package/dist/cli/commands/execution.js +54 -15
  3. package/dist/cli/commands/setup.js +78 -64
  4. package/dist/cli/commands/status.js +1 -1
  5. package/dist/cli/core/browser.js +163 -10
  6. package/dist/cli/core/config.js +1 -0
  7. package/dist/cli/core/providers/browserbase.js +53 -0
  8. package/dist/cli/core/providers/index.js +48 -0
  9. package/dist/cli/core/providers/kernel.js +46 -0
  10. package/dist/cli/core/providers/libretto-cloud.js +58 -0
  11. package/dist/cli/core/providers/types.js +0 -0
  12. package/dist/cli/core/session.js +9 -0
  13. package/dist/cli/workers/run-integration-runtime.js +3 -1
  14. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  15. package/dist/shared/run/browser.d.ts +6 -1
  16. package/dist/shared/run/browser.js +39 -1
  17. package/dist/shared/state/session-state.d.ts +11 -1
  18. package/dist/shared/state/session-state.js +9 -2
  19. package/package.json +1 -1
  20. package/skills/AGENTS.md +1 -0
  21. package/skills/libretto/SKILL.md +1 -1
  22. package/skills/libretto-readonly/SKILL.md +1 -1
  23. package/src/cli/commands/browser.ts +35 -7
  24. package/src/cli/commands/execution.ts +54 -14
  25. package/src/cli/commands/setup.ts +81 -64
  26. package/src/cli/commands/status.ts +3 -1
  27. package/src/cli/core/browser.ts +197 -9
  28. package/src/cli/core/config.ts +1 -0
  29. package/src/cli/core/providers/browserbase.ts +57 -0
  30. package/src/cli/core/providers/index.ts +62 -0
  31. package/src/cli/core/providers/kernel.ts +49 -0
  32. package/src/cli/core/providers/libretto-cloud.ts +61 -0
  33. package/src/cli/core/providers/types.ts +9 -0
  34. package/src/cli/core/session.ts +13 -0
  35. package/src/cli/workers/run-integration-runtime.ts +2 -0
  36. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  37. package/src/shared/run/browser.ts +45 -0
  38. package/src/shared/state/session-state.ts +7 -0
@@ -30,6 +30,7 @@ import {
30
30
  readSessionState,
31
31
  writeSessionState
32
32
  } from "./session.js";
33
+ import { getCloudProviderApi } from "./providers/index.js";
33
34
  import { installSessionTelemetry } from "./session-telemetry.js";
34
35
  const CLOSE_WAIT_MS = 1500;
35
36
  const FORCE_CLOSE_WAIT_MS = 300;
@@ -179,6 +180,11 @@ async function connect(session, logger, timeoutMs = 1e4, options) {
179
180
  endpoint,
180
181
  pid: state.pid
181
182
  });
183
+ if (state.provider) {
184
+ throw new Error(
185
+ `Could not connect to ${state.provider.name} session for "${session}" at ${endpoint}. The remote session may still be active. Try again, or close with: libretto close --session ${session}`
186
+ );
187
+ }
182
188
  if (state.pid == null || !isPidRunning(state.pid)) {
183
189
  clearSessionState(session, logger);
184
190
  throw new Error(
@@ -559,6 +565,86 @@ await new Promise(() => {});
559
565
  `Failed to connect to browser after ${Math.ceil(cdpStartupTimeoutMs / 1e3)}s. Check startup logs: ${runLogPath}`
560
566
  );
561
567
  }
568
+ async function runOpenWithProvider(rawUrl, providerName, provider, session, logger, accessMode = "write-access") {
569
+ const parsedUrl = normalizeUrl(rawUrl);
570
+ const url = parsedUrl.href;
571
+ logger.info("open-provider-start", { url, provider: providerName, session });
572
+ console.log(
573
+ `Creating ${providerName} browser session (session: ${session})...`
574
+ );
575
+ const providerSession = await provider.createSession();
576
+ logger.info("open-provider-session-created", {
577
+ provider: providerName,
578
+ sessionId: providerSession.sessionId,
579
+ cdpEndpoint: providerSession.cdpEndpoint
580
+ });
581
+ console.log(`Connecting to ${providerName} browser...`);
582
+ let browser = null;
583
+ try {
584
+ browser = await tryConnectToCDP(
585
+ providerSession.cdpEndpoint,
586
+ logger,
587
+ 3e4
588
+ );
589
+ if (!browser) {
590
+ throw new Error(
591
+ `Could not connect to ${providerName} browser at ${providerSession.cdpEndpoint}. The remote session was created but CDP connection failed.`
592
+ );
593
+ }
594
+ const contexts = browser.contexts();
595
+ let page;
596
+ if (contexts.length > 0 && contexts[0].pages().length > 0) {
597
+ page = contexts[0].pages()[0];
598
+ } else {
599
+ const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
600
+ page = await context.newPage();
601
+ }
602
+ await page.goto(url);
603
+ logger.info("open-provider-navigated", { url, session });
604
+ writeSessionState(
605
+ {
606
+ port: 0,
607
+ cdpEndpoint: providerSession.cdpEndpoint,
608
+ session,
609
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
610
+ status: "active",
611
+ mode: accessMode,
612
+ provider: {
613
+ name: providerName,
614
+ sessionId: providerSession.sessionId
615
+ }
616
+ },
617
+ logger
618
+ );
619
+ disconnectBrowser(browser, logger, session);
620
+ } catch (err) {
621
+ if (browser) {
622
+ disconnectBrowser(browser, logger, session);
623
+ }
624
+ logger.warn("open-provider-cleanup-after-error", {
625
+ provider: providerName,
626
+ sessionId: providerSession.sessionId,
627
+ error: err
628
+ });
629
+ try {
630
+ await provider.closeSession(providerSession.sessionId);
631
+ } catch (cleanupErr) {
632
+ logger.warn("open-provider-cleanup-failed", {
633
+ provider: providerName,
634
+ sessionId: providerSession.sessionId,
635
+ error: cleanupErr
636
+ });
637
+ }
638
+ throw err;
639
+ }
640
+ logger.info("open-provider-success", {
641
+ url,
642
+ provider: providerName,
643
+ session,
644
+ sessionId: providerSession.sessionId
645
+ });
646
+ console.log(`Browser open (${providerName}): ${url}`);
647
+ }
562
648
  async function runSave(urlOrDomain, session, logger) {
563
649
  logger.info("save-start", { urlOrDomain, session });
564
650
  const { browser, context, page } = await connect(session, logger);
@@ -631,10 +717,33 @@ async function runClose(session, logger) {
631
717
  console.log(`No browser running for session "${session}".`);
632
718
  return;
633
719
  }
634
- logger.info("close-killing", { session, pid: state.pid, port: state.port });
635
- if (state.pid != null) {
636
- sendSignalToProcessGroupOrPid(state.pid, "SIGTERM", logger, session);
637
- await waitForCloseSignalWindow(CLOSE_WAIT_MS);
720
+ if (state.provider) {
721
+ logger.info("close-provider", {
722
+ session,
723
+ provider: state.provider.name,
724
+ sessionId: state.provider.sessionId
725
+ });
726
+ try {
727
+ const provider = getCloudProviderApi(state.provider.name);
728
+ await provider.closeSession(state.provider.sessionId);
729
+ } catch (err) {
730
+ logger.warn("close-provider-error", {
731
+ session,
732
+ provider: state.provider.name,
733
+ sessionId: state.provider.sessionId,
734
+ error: err
735
+ });
736
+ writeSessionState({ ...state, status: "cleanup-failed" }, logger);
737
+ throw new Error(
738
+ `Failed to close remote ${state.provider.name} session "${state.provider.sessionId}" for session "${session}". State preserved with status "cleanup-failed". Retry with: libretto close --session ${session}`
739
+ );
740
+ }
741
+ } else {
742
+ logger.info("close-killing", { session, pid: state.pid, port: state.port });
743
+ if (state.pid != null) {
744
+ sendSignalToProcessGroupOrPid(state.pid, "SIGTERM", logger, session);
745
+ await waitForCloseSignalWindow(CLOSE_WAIT_MS);
746
+ }
638
747
  }
639
748
  clearSessionState(session, logger);
640
749
  logger.info("close-success", { session });
@@ -676,14 +785,16 @@ function resolveClosableSessions(logger) {
676
785
  closable.push({
677
786
  session,
678
787
  pid: state.pid,
679
- port: state.port
788
+ port: state.port,
789
+ provider: state.provider
680
790
  });
681
791
  }
682
792
  return { closable, clearedUnreadableStates };
683
793
  }
684
- function clearStoppedSessionStates(sessions, logger) {
794
+ function clearStoppedSessionStates(sessions, logger, skip) {
685
795
  let cleared = 0;
686
796
  for (const session of sessions) {
797
+ if (skip?.has(session.session)) continue;
687
798
  if (session.pid == null || !isPidRunning(session.pid)) {
688
799
  clearSessionState(session.session, logger);
689
800
  cleared += 1;
@@ -704,7 +815,34 @@ async function runCloseAll(logger, options) {
704
815
  console.log("No browser sessions found.");
705
816
  return;
706
817
  }
818
+ const failedProviderSessions = /* @__PURE__ */ new Set();
819
+ for (const target of closable) {
820
+ if (target.provider) {
821
+ logger.info("close-all-provider", {
822
+ session: target.session,
823
+ provider: target.provider.name,
824
+ sessionId: target.provider.sessionId
825
+ });
826
+ try {
827
+ const provider = getCloudProviderApi(target.provider.name);
828
+ await provider.closeSession(target.provider.sessionId);
829
+ } catch (err) {
830
+ logger.warn("close-all-provider-error", {
831
+ session: target.session,
832
+ provider: target.provider.name,
833
+ sessionId: target.provider.sessionId,
834
+ error: err
835
+ });
836
+ failedProviderSessions.add(target.session);
837
+ const state = readSessionState(target.session, logger);
838
+ if (state) {
839
+ writeSessionState({ ...state, status: "cleanup-failed" }, logger);
840
+ }
841
+ }
842
+ }
843
+ }
707
844
  for (const target of closable) {
845
+ if (target.provider) continue;
708
846
  logger.info("close-all-sigterm", {
709
847
  session: target.session,
710
848
  pid: target.pid,
@@ -724,7 +862,11 @@ async function runCloseAll(logger, options) {
724
862
  (target) => target.pid != null && isPidRunning(target.pid)
725
863
  );
726
864
  if (survivors.length > 0 && !force) {
727
- const closed = clearStoppedSessionStates(closable, logger);
865
+ const closed = clearStoppedSessionStates(
866
+ closable,
867
+ logger,
868
+ failedProviderSessions
869
+ );
728
870
  throw new Error(
729
871
  [
730
872
  `Failed to close ${survivors.length} session(s) gracefully: ${formatSessionList(survivors)}.`,
@@ -755,7 +897,11 @@ async function runCloseAll(logger, options) {
755
897
  (target) => target.pid != null && isPidRunning(target.pid)
756
898
  );
757
899
  if (survivors.length > 0) {
758
- const closed = clearStoppedSessionStates(closable, logger);
900
+ const closed = clearStoppedSessionStates(
901
+ closable,
902
+ logger,
903
+ failedProviderSessions
904
+ );
759
905
  throw new Error(
760
906
  [
761
907
  `Failed to force-close ${survivors.length} session(s): ${formatSessionList(survivors)}.`,
@@ -764,13 +910,19 @@ async function runCloseAll(logger, options) {
764
910
  );
765
911
  }
766
912
  }
767
- clearStoppedSessionStates(closable, logger);
913
+ clearStoppedSessionStates(closable, logger, failedProviderSessions);
914
+ if (failedProviderSessions.size > 0) {
915
+ console.log(
916
+ `Warning: ${failedProviderSessions.size} provider session(s) failed remote cleanup and were preserved with status "cleanup-failed".`
917
+ );
918
+ }
768
919
  if (clearedUnreadableStates > 0) {
769
920
  console.log(
770
921
  `Cleared ${clearedUnreadableStates} unreadable session state file(s).`
771
922
  );
772
923
  }
773
- console.log(`Closed ${closable.length} session(s).`);
924
+ const closedCount = closable.length - failedProviderSessions.size;
925
+ console.log(`Closed ${closedCount} session(s).`);
774
926
  if (forceKilled > 0) {
775
927
  console.log(`Force-killed ${forceKilled} session(s).`);
776
928
  }
@@ -878,6 +1030,7 @@ export {
878
1030
  runCloseAll,
879
1031
  runConnect,
880
1032
  runOpen,
1033
+ runOpenWithProvider,
881
1034
  runPages,
882
1035
  runSave
883
1036
  };
@@ -21,6 +21,7 @@ const LibrettoConfigSchema = z.object({
21
21
  ai: AiConfigSchema.optional(),
22
22
  viewport: ViewportConfigSchema.optional(),
23
23
  windowPosition: WindowPositionConfigSchema.optional(),
24
+ provider: z.string().optional(),
24
25
  sessionMode: SessionAccessModeSchema.optional()
25
26
  }).passthrough();
26
27
  function formatConfigIssues(error) {
@@ -0,0 +1,53 @@
1
+ function createBrowserbaseProvider() {
2
+ const apiKey = process.env.BROWSERBASE_API_KEY;
3
+ if (!apiKey)
4
+ throw new Error(
5
+ "BROWSERBASE_API_KEY is required for Browserbase provider."
6
+ );
7
+ const projectId = process.env.BROWSERBASE_PROJECT_ID;
8
+ if (!projectId)
9
+ throw new Error(
10
+ "BROWSERBASE_PROJECT_ID is required for Browserbase provider."
11
+ );
12
+ const endpoint = process.env.BROWSERBASE_ENDPOINT ?? "https://api.browserbase.com";
13
+ return {
14
+ async createSession() {
15
+ const resp = await fetch(`${endpoint}/v1/sessions`, {
16
+ method: "POST",
17
+ headers: {
18
+ "X-BB-API-Key": apiKey,
19
+ "Content-Type": "application/json"
20
+ },
21
+ body: JSON.stringify({ projectId })
22
+ });
23
+ if (!resp.ok) {
24
+ const body = await resp.text();
25
+ throw new Error(`Browserbase API error (${resp.status}): ${body}`);
26
+ }
27
+ const json = await resp.json();
28
+ return {
29
+ sessionId: json.id,
30
+ cdpEndpoint: json.connectUrl
31
+ };
32
+ },
33
+ async closeSession(sessionId) {
34
+ const resp = await fetch(`${endpoint}/v1/sessions/${sessionId}`, {
35
+ method: "POST",
36
+ headers: {
37
+ "X-BB-API-Key": apiKey,
38
+ "Content-Type": "application/json"
39
+ },
40
+ body: JSON.stringify({ status: "REQUEST_RELEASE" })
41
+ });
42
+ if (!resp.ok) {
43
+ const body = await resp.text();
44
+ throw new Error(
45
+ `Browserbase API error closing session ${sessionId} (${resp.status}): ${body}`
46
+ );
47
+ }
48
+ }
49
+ };
50
+ }
51
+ export {
52
+ createBrowserbaseProvider
53
+ };
@@ -0,0 +1,48 @@
1
+ import { readLibrettoConfig } from "../config.js";
2
+ import { createBrowserbaseProvider } from "./browserbase.js";
3
+ import { createKernelProvider } from "./kernel.js";
4
+ import { createLibrettoCloudProvider } from "./libretto-cloud.js";
5
+ const VALID_PROVIDERS = /* @__PURE__ */ new Set(["local", "kernel", "browserbase", "libretto-cloud"]);
6
+ function assertValidProviderName(value, source) {
7
+ if (!VALID_PROVIDERS.has(value)) {
8
+ throw new Error(
9
+ `Invalid provider "${value}" from ${source}. Valid providers: ${[...VALID_PROVIDERS].join(", ")}`
10
+ );
11
+ }
12
+ return value;
13
+ }
14
+ function resolveProviderName(cliFlag) {
15
+ if (cliFlag) {
16
+ return assertValidProviderName(cliFlag, "--provider flag");
17
+ }
18
+ const envVar = process.env.LIBRETTO_PROVIDER;
19
+ if (envVar) {
20
+ return assertValidProviderName(envVar, "LIBRETTO_PROVIDER env var");
21
+ }
22
+ const config = readLibrettoConfig();
23
+ if (config.provider) {
24
+ return assertValidProviderName(config.provider, "config file");
25
+ }
26
+ return "local";
27
+ }
28
+ function getCloudProviderApi(name) {
29
+ switch (name) {
30
+ case "kernel":
31
+ return createKernelProvider();
32
+ case "browserbase":
33
+ return createBrowserbaseProvider();
34
+ case "libretto-cloud":
35
+ console.warn(
36
+ "Note: The libretto-cloud provider is in alpha."
37
+ );
38
+ return createLibrettoCloudProvider();
39
+ default:
40
+ throw new Error(
41
+ `Unknown provider "${name}". Valid cloud providers: kernel, browserbase`
42
+ );
43
+ }
44
+ }
45
+ export {
46
+ getCloudProviderApi,
47
+ resolveProviderName
48
+ };
@@ -0,0 +1,46 @@
1
+ function createKernelProvider() {
2
+ const apiKey = process.env.KERNEL_API_KEY;
3
+ if (!apiKey)
4
+ throw new Error("KERNEL_API_KEY is required for Kernel provider.");
5
+ const endpoint = process.env.KERNEL_ENDPOINT ?? "https://api.onkernel.com";
6
+ return {
7
+ async createSession() {
8
+ const resp = await fetch(`${endpoint}/browsers`, {
9
+ method: "POST",
10
+ headers: {
11
+ Authorization: `Bearer ${apiKey}`,
12
+ "Content-Type": "application/json"
13
+ },
14
+ body: JSON.stringify({
15
+ headless: process.env.KERNEL_HEADLESS !== "false",
16
+ stealth: process.env.KERNEL_STEALTH === "true",
17
+ timeout_seconds: Number(process.env.KERNEL_TIMEOUT_SECONDS ?? 300)
18
+ })
19
+ });
20
+ if (!resp.ok) {
21
+ const body = await resp.text();
22
+ throw new Error(`Kernel API error (${resp.status}): ${body}`);
23
+ }
24
+ const json = await resp.json();
25
+ return {
26
+ sessionId: json.session_id,
27
+ cdpEndpoint: json.cdp_ws_url
28
+ };
29
+ },
30
+ async closeSession(sessionId) {
31
+ const resp = await fetch(`${endpoint}/browsers/${sessionId}`, {
32
+ method: "DELETE",
33
+ headers: { Authorization: `Bearer ${apiKey}` }
34
+ });
35
+ if (!resp.ok) {
36
+ const body = await resp.text();
37
+ throw new Error(
38
+ `Kernel API error closing session ${sessionId} (${resp.status}): ${body}`
39
+ );
40
+ }
41
+ }
42
+ };
43
+ }
44
+ export {
45
+ createKernelProvider
46
+ };
@@ -0,0 +1,58 @@
1
+ function createLibrettoCloudProvider() {
2
+ const apiKey = process.env.LIBRETTO_API_KEY;
3
+ if (!apiKey)
4
+ throw new Error(
5
+ "LIBRETTO_API_KEY is required for the Libretto Cloud provider."
6
+ );
7
+ const apiUrl = process.env.LIBRETTO_API_URL;
8
+ if (!apiUrl)
9
+ throw new Error(
10
+ "LIBRETTO_API_URL is required for the Libretto Cloud provider."
11
+ );
12
+ const endpoint = apiUrl.replace(/\/$/, "");
13
+ return {
14
+ async createSession() {
15
+ const timeoutSeconds = Number(
16
+ process.env.LIBRETTO_TIMEOUT_SECONDS ?? 7200
17
+ );
18
+ const resp = await fetch(`${endpoint}/v1/sessions/create`, {
19
+ method: "POST",
20
+ headers: {
21
+ "x-api-key": apiKey,
22
+ "Content-Type": "application/json"
23
+ },
24
+ body: JSON.stringify({ timeout_seconds: timeoutSeconds })
25
+ });
26
+ if (!resp.ok) {
27
+ const body = await resp.text();
28
+ throw new Error(
29
+ `Libretto Cloud API error (${resp.status}): ${body}`
30
+ );
31
+ }
32
+ const json = await resp.json();
33
+ return {
34
+ sessionId: json.session_id,
35
+ cdpEndpoint: json.cdp_url
36
+ };
37
+ },
38
+ async closeSession(sessionId) {
39
+ const resp = await fetch(`${endpoint}/v1/sessions/close`, {
40
+ method: "POST",
41
+ headers: {
42
+ "x-api-key": apiKey,
43
+ "Content-Type": "application/json"
44
+ },
45
+ body: JSON.stringify({ session_id: sessionId })
46
+ });
47
+ if (!resp.ok) {
48
+ const body = await resp.text();
49
+ throw new Error(
50
+ `Libretto Cloud API error closing session ${sessionId} (${resp.status}): ${body}`
51
+ );
52
+ }
53
+ }
54
+ };
55
+ }
56
+ export {
57
+ createLibrettoCloudProvider
58
+ };
File without changes
@@ -95,6 +95,10 @@ function listRunningSessions() {
95
95
  for (const name of sessions) {
96
96
  const state = readSessionState(name);
97
97
  if (!state) continue;
98
+ if (state.provider) {
99
+ running.push(state);
100
+ continue;
101
+ }
98
102
  if (state.pid == null || !isPidRunning(state.pid)) continue;
99
103
  running.push(state);
100
104
  }
@@ -207,6 +211,11 @@ function setSessionStatus(session, status, logger) {
207
211
  function assertSessionAvailableForStart(session, logger) {
208
212
  const existingState = readSessionState(session, logger);
209
213
  if (!existingState) return;
214
+ if (existingState.provider && existingState.cdpEndpoint) {
215
+ throw new Error(
216
+ `Session "${session}" is already open via ${existingState.provider.name} provider. Close it first with: libretto close --session ${session}`
217
+ );
218
+ }
210
219
  if (existingState.pid == null || !isPidRunning(existingState.pid)) {
211
220
  setSessionStatus(session, "exited", logger);
212
221
  return;
@@ -158,7 +158,9 @@ async function runIntegrationInternal(args, options) {
158
158
  headless: args.headless,
159
159
  storageStatePath,
160
160
  viewport: args.viewport,
161
- accessMode: args.accessMode
161
+ accessMode: args.accessMode,
162
+ cdpEndpoint: args.cdpEndpoint,
163
+ provider: args.provider
162
164
  });
163
165
  if (!args.headless && args.visualize !== false) {
164
166
  await installHeadedWorkflowVisualization({
@@ -8,7 +8,9 @@ const RunIntegrationWorkerRequestSchema = z.object({
8
8
  visualize: z.boolean().default(true),
9
9
  authProfileDomain: z.string().optional(),
10
10
  viewport: z.object({ width: z.number(), height: z.number() }).optional(),
11
- accessMode: SessionAccessModeSchema.default("write-access")
11
+ accessMode: SessionAccessModeSchema.default("write-access"),
12
+ cdpEndpoint: z.string().optional(),
13
+ provider: z.object({ name: z.string(), sessionId: z.string() }).optional()
12
14
  });
13
15
  export {
14
16
  RunIntegrationWorkerRequestSchema
@@ -11,6 +11,11 @@ type LaunchBrowserArgs = {
11
11
  };
12
12
  storageStatePath?: string;
13
13
  accessMode?: SessionAccessMode;
14
+ cdpEndpoint?: string;
15
+ provider?: {
16
+ name: string;
17
+ sessionId: string;
18
+ };
14
19
  };
15
20
  type BrowserSession = {
16
21
  browser: Browser;
@@ -20,6 +25,6 @@ type BrowserSession = {
20
25
  metadataPath: string;
21
26
  close: () => Promise<void>;
22
27
  };
23
- declare function launchBrowser({ sessionName, headless, viewport, storageStatePath, accessMode, }: LaunchBrowserArgs): Promise<BrowserSession>;
28
+ declare function launchBrowser({ sessionName, headless, viewport, storageStatePath, accessMode, cdpEndpoint, provider, }: LaunchBrowserArgs): Promise<BrowserSession>;
24
29
 
25
30
  export { type BrowserSession, type LaunchBrowserArgs, launchBrowser };
@@ -64,8 +64,46 @@ async function launchBrowser({
64
64
  headless = false,
65
65
  viewport = { width: 1366, height: 768 },
66
66
  storageStatePath,
67
- accessMode = "write-access"
67
+ accessMode = "write-access",
68
+ cdpEndpoint,
69
+ provider
68
70
  }) {
71
+ if (cdpEndpoint) {
72
+ const browser2 = await chromium.connectOverCDP(cdpEndpoint);
73
+ const context2 = browser2.contexts()[0] ?? await browser2.newContext({ viewport });
74
+ const page2 = context2.pages()[0] ?? await context2.newPage();
75
+ page2.setDefaultTimeout(3e4);
76
+ page2.setDefaultNavigationTimeout(45e3);
77
+ const metadataPath2 = ensureLibrettoSessionStatePath(sessionName);
78
+ writeFileSync(
79
+ metadataPath2,
80
+ JSON.stringify(
81
+ {
82
+ version: SESSION_STATE_VERSION,
83
+ session: sessionName,
84
+ port: 0,
85
+ cdpEndpoint,
86
+ pid: process.pid,
87
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
88
+ status: "active",
89
+ mode: accessMode,
90
+ ...provider ? { provider } : {}
91
+ },
92
+ null,
93
+ 2
94
+ )
95
+ );
96
+ return {
97
+ browser: browser2,
98
+ context: context2,
99
+ page: page2,
100
+ debugPort: 0,
101
+ metadataPath: metadataPath2,
102
+ close: async () => {
103
+ await browser2.close();
104
+ }
105
+ };
106
+ }
69
107
  const debugPort = await pickFreePort();
70
108
  const windowPosition = headless ? void 0 : resolveWindowPosition();
71
109
  const browser = await chromium.launch({
@@ -7,6 +7,7 @@ declare const SessionStatusSchema: z.ZodEnum<{
7
7
  completed: "completed";
8
8
  failed: "failed";
9
9
  exited: "exited";
10
+ "cleanup-failed": "cleanup-failed";
10
11
  }>;
11
12
  declare const SessionAccessModeSchema: z.ZodEnum<{
12
13
  "read-only": "read-only";
@@ -16,6 +17,10 @@ declare const SessionViewportSchema: z.ZodObject<{
16
17
  width: z.ZodNumber;
17
18
  height: z.ZodNumber;
18
19
  }, z.core.$strip>;
20
+ declare const ProviderStateSchema: z.ZodObject<{
21
+ name: z.ZodString;
22
+ sessionId: z.ZodString;
23
+ }, z.core.$strip>;
19
24
  declare const SessionStateFileSchema: z.ZodObject<{
20
25
  version: z.ZodLiteral<1>;
21
26
  port: z.ZodNumber;
@@ -29,6 +34,7 @@ declare const SessionStateFileSchema: z.ZodObject<{
29
34
  completed: "completed";
30
35
  failed: "failed";
31
36
  exited: "exited";
37
+ "cleanup-failed": "cleanup-failed";
32
38
  }>>;
33
39
  mode: z.ZodDefault<z.ZodEnum<{
34
40
  "read-only": "read-only";
@@ -38,6 +44,10 @@ declare const SessionStateFileSchema: z.ZodObject<{
38
44
  width: z.ZodNumber;
39
45
  height: z.ZodNumber;
40
46
  }, z.core.$strip>>;
47
+ provider: z.ZodOptional<z.ZodObject<{
48
+ name: z.ZodString;
49
+ sessionId: z.ZodString;
50
+ }, z.core.$strip>>;
41
51
  }, z.core.$strip>;
42
52
  type SessionStatus = z.infer<typeof SessionStatusSchema>;
43
53
  type SessionAccessMode = z.infer<typeof SessionAccessModeSchema>;
@@ -47,4 +57,4 @@ declare function parseSessionStateData(rawState: unknown, source: string): Sessi
47
57
  declare function parseSessionStateContent(content: string, source: string): SessionState;
48
58
  declare function serializeSessionState(state: SessionState): SessionStateFile;
49
59
 
50
- export { SESSION_STATE_VERSION, type SessionAccessMode, SessionAccessModeSchema, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
60
+ export { ProviderStateSchema, SESSION_STATE_VERSION, type SessionAccessMode, SessionAccessModeSchema, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
@@ -5,13 +5,18 @@ const SessionStatusSchema = z.enum([
5
5
  "paused",
6
6
  "completed",
7
7
  "failed",
8
- "exited"
8
+ "exited",
9
+ "cleanup-failed"
9
10
  ]);
10
11
  const SessionAccessModeSchema = z.enum(["read-only", "write-access"]);
11
12
  const SessionViewportSchema = z.object({
12
13
  width: z.number().int().min(1),
13
14
  height: z.number().int().min(1)
14
15
  });
16
+ const ProviderStateSchema = z.object({
17
+ name: z.string(),
18
+ sessionId: z.string()
19
+ });
15
20
  const SessionStateFileSchema = z.object({
16
21
  version: z.literal(SESSION_STATE_VERSION),
17
22
  port: z.number().int().min(0).max(65535),
@@ -21,7 +26,8 @@ const SessionStateFileSchema = z.object({
21
26
  startedAt: z.string().datetime({ offset: true }),
22
27
  status: SessionStatusSchema.optional(),
23
28
  mode: SessionAccessModeSchema.default("write-access"),
24
- viewport: SessionViewportSchema.optional()
29
+ viewport: SessionViewportSchema.optional(),
30
+ provider: ProviderStateSchema.optional()
25
31
  });
26
32
  function formatIssues(error) {
27
33
  return error.issues.map((issue) => {
@@ -57,6 +63,7 @@ function serializeSessionState(state) {
57
63
  });
58
64
  }
59
65
  export {
66
+ ProviderStateSchema,
60
67
  SESSION_STATE_VERSION,
61
68
  SessionAccessModeSchema,
62
69
  SessionStateFileSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.5.6",
3
+ "version": "0.6.1",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skills/AGENTS.md CHANGED
@@ -9,3 +9,4 @@
9
9
 
10
10
  - Run `pnpm sync:mirrors` after changing anything under `skills/`.
11
11
  - Run `pnpm check:mirrors` to verify that generated READMEs, skill mirrors, and skill version metadata are in sync.
12
+