autokap 1.0.7 → 1.0.10

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 (301) hide show
  1. package/assets/cursors/macos.svg +4 -0
  2. package/assets/cursors/windows.svg +15 -0
  3. package/assets/skill/OPCODE-REFERENCE.md +635 -0
  4. package/assets/skill/README.md +39 -0
  5. package/assets/skill/SKILL.md +454 -468
  6. package/assets/skill/STUDIO-SKILL.md +476 -0
  7. package/assets/skill/references/examples.md +104 -0
  8. package/assets/skill/references/interactive-demo.md +225 -0
  9. package/assets/skill/references/mock-data.md +178 -0
  10. package/dist/action-verifier.d.ts +29 -0
  11. package/dist/action-verifier.js +133 -0
  12. package/dist/agent-action-recovery.d.ts +45 -0
  13. package/dist/agent-action-recovery.js +370 -0
  14. package/dist/agent-message-utils.d.ts +21 -0
  15. package/dist/agent-message-utils.js +77 -0
  16. package/dist/agent-url-utils.d.ts +30 -0
  17. package/dist/agent-url-utils.js +138 -0
  18. package/dist/agent.d.ts +92 -8
  19. package/dist/agent.js +2936 -781
  20. package/dist/ak-tree.d.ts +39 -0
  21. package/dist/ak-tree.js +368 -0
  22. package/dist/alt-text.d.ts +26 -0
  23. package/dist/alt-text.js +55 -0
  24. package/dist/auth-capture.d.ts +17 -0
  25. package/dist/auth-capture.js +197 -0
  26. package/dist/benchmark.d.ts +59 -0
  27. package/dist/benchmark.js +135 -0
  28. package/dist/billing-operation-logging.d.ts +3 -1
  29. package/dist/billing-operation-logging.js +4 -0
  30. package/dist/browser-bar.d.ts +14 -6
  31. package/dist/browser-bar.js +145 -8
  32. package/dist/browser-pool.d.ts +7 -0
  33. package/dist/browser-pool.js +15 -5
  34. package/dist/browser-utils.d.ts +31 -0
  35. package/dist/browser-utils.js +97 -0
  36. package/dist/browser.d.ts +61 -11
  37. package/dist/browser.js +1513 -59
  38. package/dist/capture-alt-text.js +2 -1
  39. package/dist/capture-encryption.d.ts +3 -1
  40. package/dist/capture-encryption.js +21 -6
  41. package/dist/capture-language-preflight.js +14 -0
  42. package/dist/capture-llm-page-identity.js +22 -10
  43. package/dist/capture-page-identity.d.ts +5 -7
  44. package/dist/capture-page-identity.js +211 -78
  45. package/dist/capture-preset-credentials.d.ts +50 -0
  46. package/dist/capture-preset-credentials.js +127 -0
  47. package/dist/capture-request-plan.d.ts +2 -2
  48. package/dist/capture-request-plan.js +64 -16
  49. package/dist/capture-run-optimizer.js +48 -33
  50. package/dist/capture-selector-memory.d.ts +5 -0
  51. package/dist/capture-selector-memory.js +18 -0
  52. package/dist/capture-strategy.d.ts +36 -0
  53. package/dist/capture-strategy.js +96 -0
  54. package/dist/capture-studio-sync.d.ts +1 -0
  55. package/dist/capture-studio-sync.js +9 -3
  56. package/dist/capture-surface-contract.d.ts +36 -0
  57. package/dist/capture-surface-contract.js +299 -0
  58. package/dist/capture-transition-engine.d.ts +28 -0
  59. package/dist/capture-transition-engine.js +292 -0
  60. package/dist/capture-variant-state.d.ts +2 -0
  61. package/dist/capture-variant-state.js +26 -0
  62. package/dist/capture-verification.d.ts +35 -0
  63. package/dist/capture-verification.js +95 -0
  64. package/dist/capture-viewport-lock.d.ts +48 -0
  65. package/dist/capture-viewport-lock.js +74 -0
  66. package/dist/circuit-breaker.d.ts +42 -0
  67. package/dist/circuit-breaker.js +119 -0
  68. package/dist/cli-config.d.ts +9 -1
  69. package/dist/cli-config.js +112 -7
  70. package/dist/cli-contract.d.ts +19 -0
  71. package/dist/cli-contract.js +173 -0
  72. package/dist/cli-runner-local.d.ts +12 -0
  73. package/dist/cli-runner-local.js +115 -0
  74. package/dist/cli-runner.d.ts +34 -0
  75. package/dist/cli-runner.js +580 -0
  76. package/dist/cli-utils.d.ts +0 -1
  77. package/dist/cli-utils.js +2 -5
  78. package/dist/cli.js +1011 -267
  79. package/dist/clip-begin-frame-recorder.d.ts +44 -0
  80. package/dist/clip-begin-frame-recorder.js +250 -0
  81. package/dist/clip-capture-backend.d.ts +25 -0
  82. package/dist/clip-capture-backend.js +189 -0
  83. package/dist/clip-capture-loop.d.ts +61 -0
  84. package/dist/clip-capture-loop.js +111 -0
  85. package/dist/clip-frame-recorder.d.ts +63 -0
  86. package/dist/clip-frame-recorder.js +305 -0
  87. package/dist/clip-orchestrator.js +9 -2
  88. package/dist/clip-postprocess.d.ts +31 -2
  89. package/dist/clip-postprocess.js +194 -68
  90. package/dist/clip-runtime.d.ts +18 -0
  91. package/dist/clip-runtime.js +67 -0
  92. package/dist/clip-scale.d.ts +10 -0
  93. package/dist/clip-scale.js +21 -0
  94. package/dist/clip-screencast-recorder.d.ts +42 -0
  95. package/dist/clip-screencast-recorder.js +242 -0
  96. package/dist/clip-sidecar.d.ts +54 -0
  97. package/dist/clip-sidecar.js +208 -0
  98. package/dist/cookie-dismiss.d.ts +2 -0
  99. package/dist/cookie-dismiss.js +48 -13
  100. package/dist/cost-logging.d.ts +8 -0
  101. package/dist/cost-logging.js +160 -46
  102. package/dist/cost-resolution-monitor.d.ts +16 -0
  103. package/dist/cost-resolution-monitor.js +34 -0
  104. package/dist/credential-templates.js +2 -2
  105. package/dist/cursor-overlay-script.d.ts +6 -0
  106. package/dist/cursor-overlay-script.js +169 -0
  107. package/dist/dom-css-purger.d.ts +65 -0
  108. package/dist/dom-css-purger.js +333 -0
  109. package/dist/dom-font-inliner.d.ts +45 -0
  110. package/dist/dom-font-inliner.js +148 -0
  111. package/dist/dom-patch-resolver.d.ts +52 -0
  112. package/dist/dom-patch-resolver.js +242 -0
  113. package/dist/dom-serializer.d.ts +82 -0
  114. package/dist/dom-serializer.js +378 -0
  115. package/dist/element-capture.d.ts +1 -41
  116. package/dist/element-capture.js +202 -446
  117. package/dist/env-validation.d.ts +5 -0
  118. package/dist/env-validation.js +63 -0
  119. package/dist/execution-schema.d.ts +4753 -0
  120. package/dist/execution-schema.js +563 -0
  121. package/dist/execution-types.d.ts +936 -0
  122. package/dist/execution-types.js +66 -0
  123. package/dist/fonts-loader.d.ts +14 -0
  124. package/dist/fonts-loader.js +55 -0
  125. package/dist/hybrid-navigator.js +12 -12
  126. package/dist/index.d.ts +11 -6
  127. package/dist/index.js +11 -4
  128. package/dist/legacy/agent-action-recovery.d.ts +45 -0
  129. package/dist/legacy/agent-action-recovery.js +370 -0
  130. package/dist/legacy/agent-message-utils.d.ts +21 -0
  131. package/dist/legacy/agent-message-utils.js +77 -0
  132. package/dist/legacy/agent-url-utils.d.ts +30 -0
  133. package/dist/legacy/agent-url-utils.js +138 -0
  134. package/dist/legacy/agent.d.ts +226 -0
  135. package/dist/legacy/agent.js +6666 -0
  136. package/dist/legacy/clip-orchestrator.d.ts +148 -0
  137. package/dist/legacy/clip-orchestrator.js +957 -0
  138. package/dist/legacy/credential-templates.d.ts +5 -0
  139. package/dist/legacy/credential-templates.js +60 -0
  140. package/dist/legacy/hybrid-navigator.d.ts +138 -0
  141. package/dist/legacy/hybrid-navigator.js +468 -0
  142. package/dist/legacy/llm-usage.d.ts +17 -0
  143. package/dist/legacy/llm-usage.js +45 -0
  144. package/dist/legacy/prompt-cache.d.ts +10 -0
  145. package/dist/legacy/prompt-cache.js +24 -0
  146. package/dist/legacy/prompts.d.ts +175 -0
  147. package/dist/legacy/prompts.js +1038 -0
  148. package/dist/legacy/tools.d.ts +4 -0
  149. package/dist/legacy/tools.js +216 -0
  150. package/dist/legacy/video-agent.d.ts +143 -0
  151. package/dist/legacy/video-agent.js +4788 -0
  152. package/dist/legacy/video-observation.d.ts +36 -0
  153. package/dist/legacy/video-observation.js +192 -0
  154. package/dist/legacy/video-planner.d.ts +12 -0
  155. package/dist/legacy/video-planner.js +501 -0
  156. package/dist/legacy/video-prompts.d.ts +37 -0
  157. package/dist/legacy/video-prompts.js +569 -0
  158. package/dist/legacy/video-tools.d.ts +3 -0
  159. package/dist/legacy/video-tools.js +59 -0
  160. package/dist/legacy/video-variant-state.d.ts +29 -0
  161. package/dist/legacy/video-variant-state.js +80 -0
  162. package/dist/legacy/vision-model.d.ts +17 -0
  163. package/dist/legacy/vision-model.js +74 -0
  164. package/dist/llm-healer.d.ts +55 -0
  165. package/dist/llm-healer.js +213 -0
  166. package/dist/llm-provider.d.ts +29 -0
  167. package/dist/llm-provider.js +83 -0
  168. package/dist/logger.d.ts +6 -2
  169. package/dist/logger.js +15 -1
  170. package/dist/mockup-html.js +35 -25
  171. package/dist/mockup.d.ts +95 -2
  172. package/dist/mockup.js +427 -166
  173. package/dist/mouse-animation.d.ts +2 -2
  174. package/dist/mouse-animation.js +34 -20
  175. package/dist/opcode-actions.d.ts +42 -0
  176. package/dist/opcode-actions.js +524 -0
  177. package/dist/opcode-runner.d.ts +51 -0
  178. package/dist/opcode-runner.js +779 -0
  179. package/dist/openrouter-client.d.ts +40 -0
  180. package/dist/openrouter-client.js +16 -0
  181. package/dist/overlay-engine.d.ts +24 -0
  182. package/dist/overlay-engine.js +176 -0
  183. package/dist/postcondition.d.ts +16 -0
  184. package/dist/postcondition.js +269 -0
  185. package/dist/program-patcher.d.ts +25 -0
  186. package/dist/program-patcher.js +44 -0
  187. package/dist/program-signing.d.ts +1094 -0
  188. package/dist/program-signing.js +140 -0
  189. package/dist/prompts.d.ts +13 -5
  190. package/dist/prompts.js +224 -351
  191. package/dist/provider-config.d.ts +17 -0
  192. package/dist/provider-config.js +42 -0
  193. package/dist/recovery-chain.d.ts +37 -0
  194. package/dist/recovery-chain.js +374 -0
  195. package/dist/remote-browser.d.ts +28 -4
  196. package/dist/remote-browser.js +60 -5
  197. package/dist/safari-browser-bar.d.ts +15 -0
  198. package/dist/safari-browser-bar.js +95 -0
  199. package/dist/safari-toolbar-asset.d.ts +15 -0
  200. package/dist/safari-toolbar-asset.js +12 -0
  201. package/dist/security.d.ts +2 -1
  202. package/dist/security.js +49 -10
  203. package/dist/selector-resolver.d.ts +34 -0
  204. package/dist/selector-resolver.js +181 -0
  205. package/dist/semantic-resolver.d.ts +35 -0
  206. package/dist/semantic-resolver.js +161 -0
  207. package/dist/server-capture-runtime.d.ts +5 -3
  208. package/dist/server-capture-runtime.js +42 -95
  209. package/dist/server-credit-usage.d.ts +2 -2
  210. package/dist/server-project-webhooks.d.ts +15 -1
  211. package/dist/server-project-webhooks.js +34 -8
  212. package/dist/server-screenshot-watermark.js +27 -5
  213. package/dist/session-profile.js +164 -1
  214. package/dist/sf-pro-symbols.d.ts +1 -0
  215. package/dist/sf-pro-symbols.js +55 -0
  216. package/dist/skill-packaging.d.ts +28 -0
  217. package/dist/skill-packaging.js +169 -0
  218. package/dist/smart-wait.d.ts +27 -0
  219. package/dist/smart-wait.js +81 -0
  220. package/dist/status-bar-render.d.ts +20 -0
  221. package/dist/status-bar-render.js +410 -0
  222. package/dist/status-bar.d.ts +9 -0
  223. package/dist/status-bar.js +298 -14
  224. package/dist/svg-browser-bar.d.ts +33 -0
  225. package/dist/svg-browser-bar.js +206 -0
  226. package/dist/svg-status-bar.d.ts +36 -0
  227. package/dist/svg-status-bar.js +597 -0
  228. package/dist/svg-text.d.ts +61 -0
  229. package/dist/svg-text.js +118 -0
  230. package/dist/tools.js +89 -451
  231. package/dist/types.d.ts +248 -7
  232. package/dist/types.js +23 -1
  233. package/dist/v2/action-verifier.d.ts +29 -0
  234. package/dist/v2/action-verifier.js +133 -0
  235. package/dist/v2/alt-text.d.ts +26 -0
  236. package/dist/v2/alt-text.js +55 -0
  237. package/dist/v2/benchmark.d.ts +59 -0
  238. package/dist/v2/benchmark.js +135 -0
  239. package/dist/v2/capture-strategy.d.ts +30 -0
  240. package/dist/v2/capture-strategy.js +67 -0
  241. package/dist/v2/capture-verification.d.ts +35 -0
  242. package/dist/v2/capture-verification.js +95 -0
  243. package/dist/v2/circuit-breaker.d.ts +42 -0
  244. package/dist/v2/circuit-breaker.js +119 -0
  245. package/dist/v2/cli-runner-local.d.ts +11 -0
  246. package/dist/v2/cli-runner-local.js +91 -0
  247. package/dist/v2/cli-runner.d.ts +34 -0
  248. package/dist/v2/cli-runner.js +300 -0
  249. package/dist/v2/compiler-prompts.d.ts +27 -0
  250. package/dist/v2/compiler-prompts.js +123 -0
  251. package/dist/v2/compiler.d.ts +37 -0
  252. package/dist/v2/compiler.js +147 -0
  253. package/dist/v2/explorer.d.ts +41 -0
  254. package/dist/v2/explorer.js +56 -0
  255. package/dist/v2/index.d.ts +37 -0
  256. package/dist/v2/index.js +31 -0
  257. package/dist/v2/llm-healer.d.ts +62 -0
  258. package/dist/v2/llm-healer.js +166 -0
  259. package/dist/v2/llm-provider.d.ts +29 -0
  260. package/dist/v2/llm-provider.js +80 -0
  261. package/dist/v2/opcode-runner.d.ts +47 -0
  262. package/dist/v2/opcode-runner.js +634 -0
  263. package/dist/v2/overlay-engine.d.ts +24 -0
  264. package/dist/v2/overlay-engine.js +150 -0
  265. package/dist/v2/postcondition.d.ts +16 -0
  266. package/dist/v2/postcondition.js +249 -0
  267. package/dist/v2/program-patcher.d.ts +25 -0
  268. package/dist/v2/program-patcher.js +44 -0
  269. package/dist/v2/recovery-chain.d.ts +30 -0
  270. package/dist/v2/recovery-chain.js +368 -0
  271. package/dist/v2/schema.d.ts +2580 -0
  272. package/dist/v2/schema.js +295 -0
  273. package/dist/v2/selector-resolver.d.ts +34 -0
  274. package/dist/v2/selector-resolver.js +181 -0
  275. package/dist/v2/semantic-resolver.d.ts +35 -0
  276. package/dist/v2/semantic-resolver.js +161 -0
  277. package/dist/v2/smart-wait.d.ts +27 -0
  278. package/dist/v2/smart-wait.js +81 -0
  279. package/dist/v2/types.d.ts +444 -0
  280. package/dist/v2/types.js +19 -0
  281. package/dist/v2/web-playwright-local.d.ts +69 -0
  282. package/dist/v2/web-playwright-local.js +392 -0
  283. package/dist/version.d.ts +1 -0
  284. package/dist/version.js +5 -0
  285. package/dist/video-agent.js +18 -13
  286. package/dist/video-planner.js +2 -1
  287. package/dist/video-prompts.js +3 -3
  288. package/dist/web-playwright-local.d.ts +156 -0
  289. package/dist/web-playwright-local.js +989 -0
  290. package/dist/ws-auth.js +4 -1
  291. package/dist/ws-broadcast.d.ts +34 -0
  292. package/dist/ws-broadcast.js +85 -0
  293. package/dist/ws-connection-limits.d.ts +12 -0
  294. package/dist/ws-connection-limits.js +44 -0
  295. package/dist/ws-handler-utils.d.ts +32 -0
  296. package/dist/ws-handler-utils.js +139 -0
  297. package/dist/ws-handler.js +294 -164
  298. package/dist/ws-metrics-server.d.ts +9 -0
  299. package/dist/ws-metrics-server.js +31 -0
  300. package/dist/ws-server.js +41 -1
  301. package/package.json +62 -35
@@ -0,0 +1,5 @@
1
+ import type { LoginCredentials } from "../types.js";
2
+ export declare function resolveCredentialTemplates(value: string | undefined, credentials?: LoginCredentials): string | undefined;
3
+ export declare function ensureNoCredentialTemplate(field: string, value: string | undefined): void;
4
+ export declare function sanitizeCredentialParams(params: Record<string, unknown>, credentials?: LoginCredentials): Record<string, unknown>;
5
+ export declare function resolveActionCredentialArgs(action: string, args: Record<string, unknown>, credentials?: LoginCredentials): Record<string, unknown>;
@@ -0,0 +1,60 @@
1
+ const TEMPLATE_RE = /\{\{credential\.(loginUrl|email|password)\}\}/g;
2
+ export function resolveCredentialTemplates(value, credentials) {
3
+ if (!value)
4
+ return value;
5
+ const replacements = {
6
+ loginUrl: credentials?.loginUrl,
7
+ email: credentials?.email,
8
+ password: credentials?.password,
9
+ };
10
+ return value.replace(TEMPLATE_RE, (match, key) => {
11
+ const replacement = replacements[key];
12
+ return typeof replacement === "string" ? replacement : match;
13
+ });
14
+ }
15
+ export function ensureNoCredentialTemplate(field, value) {
16
+ if (value && TEMPLATE_RE.test(value)) {
17
+ TEMPLATE_RE.lastIndex = 0;
18
+ throw new Error(`Missing credential value for ${field}`);
19
+ }
20
+ TEMPLATE_RE.lastIndex = 0;
21
+ }
22
+ function sanitizeCredentialString(value, credentials) {
23
+ if (!credentials)
24
+ return value;
25
+ if (credentials.password && value === credentials.password) {
26
+ return "{{credential.password}}";
27
+ }
28
+ if (credentials.email && value === credentials.email) {
29
+ return "{{credential.email}}";
30
+ }
31
+ if (credentials.loginUrl && value === credentials.loginUrl) {
32
+ return "{{credential.loginUrl}}";
33
+ }
34
+ return value;
35
+ }
36
+ export function sanitizeCredentialParams(params, credentials) {
37
+ const sanitized = {};
38
+ for (const [key, value] of Object.entries(params)) {
39
+ sanitized[key] =
40
+ typeof value === "string"
41
+ ? sanitizeCredentialString(value, credentials)
42
+ : value;
43
+ }
44
+ return sanitized;
45
+ }
46
+ export function resolveActionCredentialArgs(action, args, credentials) {
47
+ if (!credentials)
48
+ return args;
49
+ const resolved = { ...args };
50
+ if ((action === "type_text" || action === "type") && typeof resolved.text === "string") {
51
+ resolved.text = resolveCredentialTemplates(resolved.text, credentials);
52
+ ensureNoCredentialTemplate(`${action}.text`, resolved.text);
53
+ }
54
+ if (action === "navigate_to" && typeof resolved.url === "string") {
55
+ resolved.url = resolveCredentialTemplates(resolved.url, credentials);
56
+ ensureNoCredentialTemplate("navigate_to.url", resolved.url);
57
+ }
58
+ return resolved;
59
+ }
60
+ //# sourceMappingURL=credential-templates.js.map
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Hybrid Navigator — Uses the screenshot agent for navigation (Phase 1),
3
+ * then exports the session for video recording (Phase 2).
4
+ *
5
+ * This wrapper around `runAgent()` provides:
6
+ * - Agent-driven navigation with full self-healing
7
+ * - Session export (storageState + sessionStorage + scroll position)
8
+ * - Post-transfer auth detection (silent re-auth wait)
9
+ * - Lightweight selector pre-validation for clip plans
10
+ * - Fresh observation capture on recording browser
11
+ */
12
+ import { Browser } from '../browser.js';
13
+ import { type VariantStateDetection } from './video-variant-state.js';
14
+ import type { BrowserStorageState, BrowserSessionStorageState, CapturePageIdentity, ExecutedAction, InteractiveElement, LoginCredentials, StepUsage, VerificationResult, VideoPlan, VideoObservationSnapshot, VideoPageSignals, VideoStep } from '../types.js';
15
+ export interface NavigationResult {
16
+ success: boolean;
17
+ finalUrl: string;
18
+ scrollPosition: {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ storageState?: BrowserStorageState;
23
+ sessionStorage?: BrowserSessionStorageState;
24
+ observationSummary?: string;
25
+ observationSnapshot?: VideoObservationSnapshot;
26
+ coherenceKey?: string;
27
+ interactiveElements?: InteractiveElement[];
28
+ pageIdentity?: CapturePageIdentity | null;
29
+ actions?: ExecutedAction[];
30
+ replayableActions?: ExecutedAction[];
31
+ verification?: VerificationResult | null;
32
+ verificationMode?: VerificationResult['mode'];
33
+ exactStartStateVerified: boolean;
34
+ detectedLang?: string | null;
35
+ detectedTheme?: 'light' | 'dark' | null;
36
+ pageSignals?: VideoPageSignals;
37
+ variantState?: VariantStateDetection;
38
+ error?: string;
39
+ usage: StepUsage[];
40
+ }
41
+ export interface NavigateWithAgentConfig {
42
+ /** Target URL — can be relative (e.g. "/docs") if baseUrl is provided. */
43
+ url: string;
44
+ /** Base URL of the project (e.g. "https://example.com"). Used to resolve relative urls. */
45
+ baseUrl?: string;
46
+ navigationPrompt: string;
47
+ /**
48
+ * When provided (two-part mode), this describes the RECORDED interaction separately
49
+ * from the navigation. The navigation agent only needs to know what will be recorded
50
+ * to stop at the right pre-recording state — it must NOT perform the recorded actions.
51
+ */
52
+ recordingScript?: string;
53
+ viewport: {
54
+ width: number;
55
+ height: number;
56
+ };
57
+ outputScale?: number;
58
+ lang?: string;
59
+ theme?: 'light' | 'dark';
60
+ credentials?: LoginCredentials;
61
+ langInstructions?: string;
62
+ themeInstructions?: string;
63
+ navigationInstructions?: string;
64
+ model: string;
65
+ apiKey: string;
66
+ maxIterations?: number;
67
+ abortSignal?: AbortSignal;
68
+ onLog?: (entry: {
69
+ level: 'info' | 'action' | 'success' | 'error' | 'ai';
70
+ message: string;
71
+ timestamp: number;
72
+ }) => void;
73
+ onScreenshot?: (base64: string) => void;
74
+ }
75
+ export interface SelectorValidationResult {
76
+ valid: boolean;
77
+ missingSelectors: Array<{
78
+ stepIndex: number;
79
+ step: VideoStep;
80
+ selector: string;
81
+ }>;
82
+ }
83
+ export interface FreshObservationConfig {
84
+ url: string;
85
+ viewport: {
86
+ width: number;
87
+ height: number;
88
+ };
89
+ outputScale?: number;
90
+ lang?: string;
91
+ theme?: 'light' | 'dark';
92
+ storageState?: BrowserStorageState;
93
+ sessionStorage?: BrowserSessionStorageState;
94
+ scrollPosition?: {
95
+ x: number;
96
+ y: number;
97
+ };
98
+ abortSignal?: AbortSignal;
99
+ }
100
+ /**
101
+ * Phase 1: Use the screenshot agent to navigate to a target state.
102
+ * Exports the full session (cookies, localStorage, sessionStorage, scroll)
103
+ * for reuse in the video recording phase.
104
+ */
105
+ export declare function navigateWithAgent(config: NavigateWithAgentConfig): Promise<NavigationResult>;
106
+ /**
107
+ * Capture a fresh observation from a browser that already has the session loaded.
108
+ * Use this on Browser B (recording browser) AFTER navigation, not the stale
109
+ * observation from Browser A (agent browser).
110
+ */
111
+ export declare function captureFreshObservation(browser: Browser): Promise<string>;
112
+ /**
113
+ * Capture a fresh observation by opening a temporary pool browser with the
114
+ * exported session. Closes the browser automatically.
115
+ */
116
+ export declare function captureFreshObservationFromSession(config: FreshObservationConfig): Promise<string>;
117
+ /**
118
+ * After transferring session to a new browser context, detect if the page
119
+ * ended up in a logged-out state (e.g., due to IndexedDB/in-memory JWT loss).
120
+ * If detected, waits for silent re-auth to complete.
121
+ *
122
+ * Returns true if auth is confirmed or no auth issues detected.
123
+ */
124
+ export declare function waitForAuthStabilization(browser: Browser, options?: {
125
+ timeoutMs?: number;
126
+ }): Promise<{
127
+ ok: boolean;
128
+ reason?: string;
129
+ }>;
130
+ /**
131
+ * Validate selectors in a plan that can be checked on the current page.
132
+ *
133
+ * Only validates selectors up to the first page-mutating step (click, hover,
134
+ * type, select_option, key). After such a step the DOM changes and later
135
+ * selectors cannot be verified without actually executing the plan.
136
+ */
137
+ export declare function validatePlanSelectors(browser: Browser, plan: VideoPlan): Promise<SelectorValidationResult>;
138
+ export declare function buildNavigationPrompt(resolvedUrl: string, config: NavigateWithAgentConfig): string;
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Hybrid Navigator — Uses the screenshot agent for navigation (Phase 1),
3
+ * then exports the session for video recording (Phase 2).
4
+ *
5
+ * This wrapper around `runAgent()` provides:
6
+ * - Agent-driven navigation with full self-healing
7
+ * - Session export (storageState + sessionStorage + scroll position)
8
+ * - Post-transfer auth detection (silent re-auth wait)
9
+ * - Lightweight selector pre-validation for clip plans
10
+ * - Fresh observation capture on recording browser
11
+ */
12
+ import { Browser } from '../browser.js';
13
+ import { analyzeReplayCandidate, runAgent } from './agent.js';
14
+ import { buildVideoObservationSnapshot, buildVideoObservationSummary, captureVideoObservationSummary, } from './video-observation.js';
15
+ import { detectVariantStateDeterministic } from './video-variant-state.js';
16
+ import { logger, runWithLoggerCallbacks } from '../logger.js';
17
+ import { throwIfAborted } from '../abort.js';
18
+ import { dismissOverlaysWithLogging } from '../overlay-utils.js';
19
+ // ── Navigation ───────────────────────────────────────────────────────
20
+ /**
21
+ * Phase 1: Use the screenshot agent to navigate to a target state.
22
+ * Exports the full session (cookies, localStorage, sessionStorage, scroll)
23
+ * for reuse in the video recording phase.
24
+ */
25
+ export async function navigateWithAgent(config) {
26
+ throwIfAborted(config.abortSignal, 'Hybrid navigation cancelled.');
27
+ // Resolve relative URLs (e.g. "/docs") against the project base URL.
28
+ const resolvedUrl = resolveUrl(config.url, config.baseUrl);
29
+ const log = (msg, level = 'info') => {
30
+ if (level === 'error')
31
+ logger.error(msg);
32
+ else
33
+ logger.info(msg);
34
+ config.onLog?.({ level, message: msg, timestamp: Date.now() });
35
+ };
36
+ log(`[hybrid-nav] Starting agent navigation to: ${resolvedUrl}`);
37
+ const browser = await Browser.fromPool({
38
+ headed: false,
39
+ viewport: config.viewport,
40
+ deviceScaleFactor: config.outputScale,
41
+ lang: config.lang,
42
+ colorScheme: config.theme,
43
+ });
44
+ try {
45
+ // Navigate to the target URL before starting the agent.
46
+ // The agent expects the browser to already be on the page (same as capture pipeline).
47
+ // Without this, the browser starts on about:blank and relative URLs fail.
48
+ await browser.navigateTo(resolvedUrl);
49
+ await dismissOverlaysWithLogging(browser, {
50
+ context: 'hybrid navigation bootstrap',
51
+ onLog: config.onLog,
52
+ });
53
+ // Wrap runAgent with logger callbacks so that agent logs (reasoning, actions,
54
+ // verification) and live screenshots are forwarded to the SSE stream.
55
+ const agentResult = await runWithLoggerCallbacks({
56
+ onLog: config.onLog ? (entry) => config.onLog(entry) : undefined,
57
+ onScreenshot: config.onScreenshot ?? undefined,
58
+ }, () => runAgent(browser, {
59
+ url: resolvedUrl,
60
+ prompt: buildNavigationPrompt(resolvedUrl, config),
61
+ dark: config.theme === 'dark',
62
+ langs: config.lang ? [config.lang] : ['en'],
63
+ outputDir: '',
64
+ headed: false,
65
+ viewport: config.viewport,
66
+ maxIterations: config.maxIterations ?? 30,
67
+ model: config.model,
68
+ credentials: config.credentials,
69
+ langInstructions: config.langInstructions,
70
+ themeInstructions: config.themeInstructions,
71
+ currentLang: config.lang,
72
+ currentTheme: config.theme,
73
+ runMode: 'video_navigation_preflight',
74
+ abortSignal: config.abortSignal,
75
+ }, config.apiKey));
76
+ if (!agentResult.success) {
77
+ log(`[hybrid-nav] Agent navigation failed: ${agentResult.assessment}`, 'error');
78
+ return {
79
+ success: false,
80
+ finalUrl: browser.currentPage.url(),
81
+ scrollPosition: { x: 0, y: 0 },
82
+ exactStartStateVerified: false,
83
+ error: agentResult.assessment,
84
+ usage: agentResult.usage,
85
+ };
86
+ }
87
+ const verification = agentResult.verification ?? null;
88
+ const verificationMode = verification?.mode;
89
+ const exactStartStateVerified = verification?.verified === true
90
+ && (verificationMode === 'vision' || verificationMode === 'text_fallback');
91
+ const captureVerificationBundle = typeof browser.captureVideoVerificationBundle === 'function'
92
+ ? browser.captureVideoVerificationBundle({ maxAttempts: 1 }).catch(() => null)
93
+ : Promise.resolve(null);
94
+ const [storageState, sessionStorage, scrollPosition, variantState, pageState, pageTitle, verificationBundle] = await Promise.all([
95
+ browser.exportStorageState().catch(() => undefined),
96
+ browser.exportSessionStorage().catch(() => undefined),
97
+ browser.currentPage.evaluate(() => ({
98
+ x: Math.round(window.scrollX),
99
+ y: Math.round(window.scrollY),
100
+ })).catch(() => ({ x: 0, y: 0 })),
101
+ detectVariantStateDeterministic(browser, config.lang, config.theme).catch(() => undefined),
102
+ browser.getPageState().catch(() => undefined),
103
+ browser.currentPage.title().catch(() => ''),
104
+ captureVerificationBundle,
105
+ ]);
106
+ const finalUrl = browser.currentPage.url();
107
+ const observationSummary = verificationBundle
108
+ ? buildVideoObservationSummary({
109
+ url: verificationBundle.url,
110
+ title: verificationBundle.title,
111
+ accessibilityTree: verificationBundle.accessibilityTree,
112
+ interactiveElements: verificationBundle.interactiveElements,
113
+ pageSignals: verificationBundle.pageSignals,
114
+ maxAccessibilityChars: 3500,
115
+ maxElements: 18,
116
+ maxVisibleTextChars: 500,
117
+ })
118
+ : pageState && variantState
119
+ ? buildVideoObservationSummary({
120
+ url: finalUrl,
121
+ title: pageTitle,
122
+ accessibilityTree: pageState.accessibilityTree,
123
+ interactiveElements: pageState.interactiveElements,
124
+ pageSignals: variantState.pageSignals,
125
+ maxAccessibilityChars: 3500,
126
+ maxElements: 18,
127
+ maxVisibleTextChars: 500,
128
+ })
129
+ : await captureVideoObservationSummary(browser).catch(() => undefined);
130
+ const observationSnapshot = verificationBundle
131
+ ? buildVideoObservationSnapshot({
132
+ coherenceKey: verificationBundle.coherenceKey,
133
+ interactiveElements: verificationBundle.interactiveElements,
134
+ pageSignals: verificationBundle.pageSignals,
135
+ pageIdentity: null,
136
+ })
137
+ : pageState && variantState
138
+ ? buildVideoObservationSnapshot({
139
+ interactiveElements: pageState.interactiveElements,
140
+ pageSignals: variantState.pageSignals,
141
+ pageIdentity: null,
142
+ })
143
+ : undefined;
144
+ const replayAnalysis = analyzeReplayCandidate(agentResult.actions, {
145
+ currentUrl: finalUrl,
146
+ targetUrl: finalUrl,
147
+ currentViewport: browser.currentPage.viewportSize(),
148
+ isAuthenticated: !!config.credentials,
149
+ currentDialogCount: verificationBundle?.observation.dialogCount ?? null,
150
+ pageIdentity: null,
151
+ });
152
+ log(`[hybrid-nav] Navigation complete. Final URL: ${finalUrl} (runMode=video_navigation_preflight, verificationMode=${verificationMode ?? 'unknown'})`);
153
+ return {
154
+ success: true,
155
+ finalUrl,
156
+ scrollPosition,
157
+ storageState,
158
+ sessionStorage,
159
+ observationSummary,
160
+ observationSnapshot,
161
+ coherenceKey: verificationBundle?.coherenceKey,
162
+ interactiveElements: verificationBundle?.interactiveElements ?? pageState?.interactiveElements,
163
+ pageIdentity: null,
164
+ actions: agentResult.actions,
165
+ replayableActions: replayAnalysis.skipReason ? [] : replayAnalysis.replayableActions,
166
+ verification,
167
+ verificationMode,
168
+ exactStartStateVerified,
169
+ detectedLang: variantState?.lang.detected ?? null,
170
+ detectedTheme: variantState?.theme.detected ?? null,
171
+ pageSignals: variantState?.pageSignals,
172
+ variantState,
173
+ usage: agentResult.usage,
174
+ };
175
+ }
176
+ finally {
177
+ await browser.close().catch(() => { });
178
+ }
179
+ }
180
+ // ── Fresh Observation on Recording Browser ───────────────────────────
181
+ /**
182
+ * Capture a fresh observation from a browser that already has the session loaded.
183
+ * Use this on Browser B (recording browser) AFTER navigation, not the stale
184
+ * observation from Browser A (agent browser).
185
+ */
186
+ export async function captureFreshObservation(browser) {
187
+ return captureVideoObservationSummary(browser, {
188
+ maxAccessibilityChars: 3500,
189
+ maxElements: 18,
190
+ maxVisibleTextChars: 500,
191
+ });
192
+ }
193
+ /**
194
+ * Capture a fresh observation by opening a temporary pool browser with the
195
+ * exported session. Closes the browser automatically.
196
+ */
197
+ export async function captureFreshObservationFromSession(config) {
198
+ throwIfAborted(config.abortSignal, 'Observation capture cancelled.');
199
+ const browser = await Browser.fromPool({
200
+ headed: false,
201
+ viewport: config.viewport,
202
+ deviceScaleFactor: config.outputScale,
203
+ lang: config.lang,
204
+ colorScheme: config.theme,
205
+ storageState: config.storageState,
206
+ });
207
+ try {
208
+ if (config.sessionStorage && Object.keys(config.sessionStorage).length > 0) {
209
+ await browser.prepareSessionStorage(config.sessionStorage, { replace: true });
210
+ }
211
+ await browser.navigateTo(config.url);
212
+ await dismissOverlaysWithLogging(browser, { context: 'fresh observation bootstrap' });
213
+ // Restore scroll position
214
+ if (config.scrollPosition && (config.scrollPosition.x !== 0 || config.scrollPosition.y !== 0)) {
215
+ await browser.currentPage.evaluate(({ x, y }) => window.scrollTo({ left: x, top: y, behavior: 'instant' }), config.scrollPosition).catch(() => { });
216
+ await browser.currentPage.waitForTimeout(250);
217
+ }
218
+ return await captureVideoObservationSummary(browser, {
219
+ maxAccessibilityChars: 3500,
220
+ maxElements: 18,
221
+ maxVisibleTextChars: 500,
222
+ });
223
+ }
224
+ finally {
225
+ await browser.close().catch(() => { });
226
+ }
227
+ }
228
+ // ── Post-Transfer Auth Detection ─────────────────────────────────────
229
+ /**
230
+ * After transferring session to a new browser context, detect if the page
231
+ * ended up in a logged-out state (e.g., due to IndexedDB/in-memory JWT loss).
232
+ * If detected, waits for silent re-auth to complete.
233
+ *
234
+ * Returns true if auth is confirmed or no auth issues detected.
235
+ */
236
+ export async function waitForAuthStabilization(browser, options = {}) {
237
+ const timeout = options.timeoutMs ?? 5000;
238
+ const page = browser.currentPage;
239
+ // Check 1: Did the page redirect to a login URL?
240
+ const url = page.url().toLowerCase();
241
+ const loginPatterns = [
242
+ '/login',
243
+ '/signin',
244
+ '/sign-in',
245
+ '/auth',
246
+ '/sso',
247
+ '/oauth',
248
+ '/accounts/login',
249
+ '/connexion',
250
+ '/anmeldung',
251
+ '/acceso',
252
+ '/accedi',
253
+ ];
254
+ const isOnLoginPage = loginPatterns.some(p => url.includes(p));
255
+ if (isOnLoginPage) {
256
+ logger.info('[hybrid-nav] Detected redirect to login page after session transfer. Waiting for silent re-auth...');
257
+ // Wait for navigation away from login page (silent OAuth/OIDC redirect)
258
+ try {
259
+ await page.waitForURL((u) => !loginPatterns.some(p => u.toString().toLowerCase().includes(p)), { timeout });
260
+ logger.info('[hybrid-nav] Silent re-auth completed successfully.');
261
+ return { ok: true };
262
+ }
263
+ catch {
264
+ return { ok: false, reason: 'Session transfer failed: stuck on login page after re-auth timeout' };
265
+ }
266
+ }
267
+ // Check 2: Does the page contain visible login/sign-in buttons suggesting logged-out state?
268
+ const hasLoginPrompt = await page.evaluate(() => {
269
+ const buttons = Array.from(document.querySelectorAll('button, a, [role="button"]'));
270
+ const loginLabels = [
271
+ 'log in',
272
+ 'login',
273
+ 'sign in',
274
+ 'signin',
275
+ 'sign up',
276
+ 'get started',
277
+ 'se connecter',
278
+ 'connexion',
279
+ 'anmelden',
280
+ 'iniciar sesion',
281
+ 'accedi',
282
+ ];
283
+ return buttons.some(el => {
284
+ const text = (el.textContent || '').trim().toLowerCase();
285
+ return loginLabels.some(label => text === label) && el.checkVisibility?.();
286
+ });
287
+ }).catch(() => false);
288
+ if (hasLoginPrompt) {
289
+ logger.info('[hybrid-nav] Detected login prompt on page. Waiting briefly for potential silent re-auth...');
290
+ // Brief wait — some SPAs show login state briefly before silent auth completes
291
+ await page.waitForTimeout(2000);
292
+ // Re-check
293
+ const stillHasLogin = await page.evaluate(() => {
294
+ const buttons = Array.from(document.querySelectorAll('button, a, [role="button"]'));
295
+ const loginLabels = ['log in', 'login', 'sign in', 'signin', 'se connecter', 'connexion', 'anmelden', 'iniciar sesion', 'accedi'];
296
+ return buttons.some(el => {
297
+ const text = (el.textContent || '').trim().toLowerCase();
298
+ return loginLabels.some(label => text === label) && el.checkVisibility?.();
299
+ });
300
+ }).catch(() => false);
301
+ if (stillHasLogin) {
302
+ return { ok: false, reason: 'Session transfer may have failed: login prompt still visible' };
303
+ }
304
+ }
305
+ return { ok: true };
306
+ }
307
+ // ── Lightweight Selector Pre-Validation ──────────────────────────────
308
+ /**
309
+ * Validate selectors in a plan that can be checked on the current page.
310
+ *
311
+ * Only validates selectors up to the first page-mutating step (click, hover,
312
+ * type, select_option, key). After such a step the DOM changes and later
313
+ * selectors cannot be verified without actually executing the plan.
314
+ */
315
+ export async function validatePlanSelectors(browser, plan) {
316
+ const page = browser.currentPage;
317
+ const missing = [];
318
+ // Types that mutate the page state — hover is intentionally excluded so
319
+ // we can keep validating selectors that come after a non-committal reveal.
320
+ const mutatingTypes = new Set(['click', 'type', 'select_option', 'key', 'drag']);
321
+ const skipTypes = new Set(['navigate', 'wait', 'dismiss_overlays', 'assert_url', 'assert_text', 'assert_element', 'assert_page']);
322
+ for (let i = 0; i < plan.steps.length; i++) {
323
+ const step = plan.steps[i];
324
+ if (skipTypes.has(step.type))
325
+ continue;
326
+ if (!step.selector) {
327
+ // First mutating step without a selector — stop here
328
+ if (mutatingTypes.has(step.type))
329
+ break;
330
+ continue;
331
+ }
332
+ // Validate this selector
333
+ try {
334
+ const selectorParts = step.selector.split(',').map(s => s.trim()).filter(Boolean);
335
+ let found = false;
336
+ for (const part of selectorParts) {
337
+ const count = await page.locator(part).count().catch(() => 0);
338
+ if (count > 0) {
339
+ found = true;
340
+ break;
341
+ }
342
+ }
343
+ if (!found) {
344
+ missing.push({ stepIndex: i, step, selector: step.selector });
345
+ }
346
+ }
347
+ catch {
348
+ missing.push({ stepIndex: i, step, selector: step.selector });
349
+ }
350
+ // After validating this step, if it mutates the page, stop
351
+ if (mutatingTypes.has(step.type))
352
+ break;
353
+ }
354
+ return {
355
+ valid: missing.length === 0,
356
+ missingSelectors: missing,
357
+ };
358
+ }
359
+ // ── URL Resolution ───────────────────────────────────────────────────
360
+ /**
361
+ * Resolve a potentially relative URL against a base URL.
362
+ * - "/docs" + "https://example.com" → "https://example.com/docs"
363
+ * - "https://other.com/page" + "https://example.com" → "https://other.com/page" (absolute, untouched)
364
+ * - "/docs" + undefined → "/docs" (no base, pass through as-is)
365
+ */
366
+ function resolveUrl(url, baseUrl) {
367
+ // Already absolute — nothing to resolve
368
+ if (/^https?:\/\//i.test(url))
369
+ return url;
370
+ if (!baseUrl)
371
+ return url;
372
+ try {
373
+ return new URL(url, baseUrl).href;
374
+ }
375
+ catch {
376
+ // Fallback: simple concatenation if URL constructor fails
377
+ const base = baseUrl.replace(/\/+$/, '');
378
+ const path = url.startsWith('/') ? url : `/${url}`;
379
+ return `${base}${path}`;
380
+ }
381
+ }
382
+ // ── Prompt Builder ───────────────────────────────────────────────────
383
+ export function buildNavigationPrompt(resolvedUrl, config) {
384
+ // Two-part mode: navigationPrompt = WHERE to navigate, recordingScript = WHAT will be recorded.
385
+ // Legacy mode: navigationPrompt = full clip script used for both.
386
+ const hasTwoPartPrompt = !!config.recordingScript;
387
+ const parts = [
388
+ `Navigate to ${resolvedUrl} and prepare the exact pre-recording start state for a clip recording.`,
389
+ '',
390
+ 'Your tasks:',
391
+ '1. Load the page at the URL above',
392
+ '2. Handle login if credentials are provided',
393
+ '3. Dismiss ALL cookie banners, consent walls, analytics prompts, overlays, and popups — do this BEFORE calling capture',
394
+ '4. Navigate to the specific page/state described below',
395
+ '5. Once the page is clean (no overlays, no spinners, correct page), call capture',
396
+ ];
397
+ if (hasTwoPartPrompt) {
398
+ // Two-part mode: clear separation between navigation goal and what will be recorded.
399
+ parts.push('');
400
+ parts.push('<navigation_goal>');
401
+ parts.push(config.navigationPrompt);
402
+ parts.push('</navigation_goal>');
403
+ parts.push('');
404
+ parts.push('<recording_preview>');
405
+ parts.push(`After you call capture, the recording system will perform: ${config.recordingScript}`);
406
+ parts.push('You must NOT perform these recording actions yourself. Your job is ONLY to reach the state where the recording can begin.');
407
+ parts.push('</recording_preview>');
408
+ parts.push('');
409
+ parts.push('Follow the <navigation_goal> instructions exactly. Navigate to the described page/state. ' +
410
+ 'Dismiss all overlays and popups along the way. ' +
411
+ 'Once you are on the correct page with no obstructions, call capture.');
412
+ }
413
+ else if (config.navigationPrompt) {
414
+ // Legacy mode: single script for both navigation and recording.
415
+ parts.push('');
416
+ parts.push(`Clip description: "${config.navigationPrompt}"`);
417
+ parts.push('');
418
+ parts.push('Read the clip description above carefully. If it mentions navigation steps to perform BEFORE the main action ' +
419
+ '(e.g. "go to Settings", "open the API tab", "navigate to the pricing page"), you MUST perform those navigation steps now. ' +
420
+ 'Opening the correct project, workspace, section, tab, or source dialog counts as preparation and MUST be done now when requested. ' +
421
+ 'However, do NOT perform the final interaction that should be RECORDED (e.g. "click Regenerate", "click New", "click New preset", "scroll down", "toggle dark mode"). ' +
422
+ 'The recording system will handle the recorded interaction. Your job is to stop on the exact pre-recording start state for that interaction, not on a generic dashboard that only roughly matches the site.');
423
+ }
424
+ else {
425
+ parts.push('');
426
+ parts.push('No specific navigation required. Just load the page, dismiss overlays, and call capture once the exact pre-recording start state is visible.');
427
+ }
428
+ parts.push('');
429
+ parts.push('IMPORTANT — Before calling capture:\n' +
430
+ '- Dismiss ALL overlays: cookie banners, analytics prompts, consent walls, feedback widgets, onboarding tooltips. Tap dismiss/close buttons or press Escape.\n' +
431
+ '- If the URL is /home, /dashboard, /app, or / you are on a GENERIC dashboard, NOT inside a specific project/workspace. ' +
432
+ 'Do NOT call capture from a generic dashboard unless explicitly targeting the homepage.\n' +
433
+ '- Check the URL path: it must match the specific entity/page required.\n' +
434
+ '- If you changed language or theme via settings, navigate BACK to the target page before calling capture.');
435
+ if (config.credentials) {
436
+ parts.push('');
437
+ parts.push('If the page requires authentication, use the provided credentials to log in.');
438
+ }
439
+ if (config.lang) {
440
+ parts.push('');
441
+ parts.push(`<variant_requirement type="language" value="${config.lang}">\n` +
442
+ `The app\'s fixed UI chrome (menus, buttons, labels, navigation) MUST be in "${config.lang}" when you call capture.\n` +
443
+ `If the fixed chrome is in a different language, you MUST switch it before calling capture.\n` +
444
+ `Ignore user-generated content (project names, preset names, imported data) that may remain in another language.\n` +
445
+ `</variant_requirement>`);
446
+ }
447
+ if (config.theme) {
448
+ parts.push('');
449
+ parts.push(`<variant_requirement type="theme" value="${config.theme}">\n` +
450
+ `The app MUST be in "${config.theme}" theme when you call capture.\n` +
451
+ `If the app has an in-app theme toggle/selector, use it to switch to "${config.theme}" mode.\n` +
452
+ `</variant_requirement>`);
453
+ }
454
+ if (config.navigationInstructions) {
455
+ parts.push('');
456
+ parts.push(`Additional navigation instructions: ${config.navigationInstructions}`);
457
+ }
458
+ if (config.langInstructions) {
459
+ parts.push('');
460
+ parts.push(`Language instructions: ${config.langInstructions}`);
461
+ }
462
+ if (config.themeInstructions) {
463
+ parts.push('');
464
+ parts.push(`Theme instructions: ${config.themeInstructions}`);
465
+ }
466
+ return parts.join('\n');
467
+ }
468
+ //# sourceMappingURL=hybrid-navigator.js.map