@promptbook/wizard 0.112.0-12 → 0.112.0-13

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 (47) hide show
  1. package/esm/index.es.js +384 -2212
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/commitments/USE_BROWSER/resolveRunBrowserToolForNode.d.ts +1 -1
  4. package/esm/src/commitments/USE_TIMEOUT/TimeoutToolNames.d.ts +1 -0
  5. package/esm/src/commitments/USE_TIMEOUT/TimeoutToolRuntimeAdapter.d.ts +51 -2
  6. package/esm/src/commitments/USE_TIMEOUT/USE_TIMEOUT.d.ts +2 -2
  7. package/esm/src/commitments/USE_TIMEOUT/getTimeoutToolRuntimeAdapterOrDisabledResult.d.ts +2 -2
  8. package/esm/src/commitments/USE_TIMEOUT/parseTimeoutToolArgs.d.ts +14 -1
  9. package/esm/src/version.d.ts +1 -1
  10. package/package.json +2 -3
  11. package/umd/index.umd.js +387 -2212
  12. package/umd/index.umd.js.map +1 -1
  13. package/umd/src/commitments/USE_BROWSER/resolveRunBrowserToolForNode.d.ts +1 -1
  14. package/umd/src/commitments/USE_TIMEOUT/TimeoutToolNames.d.ts +1 -0
  15. package/umd/src/commitments/USE_TIMEOUT/TimeoutToolRuntimeAdapter.d.ts +51 -2
  16. package/umd/src/commitments/USE_TIMEOUT/USE_TIMEOUT.d.ts +2 -2
  17. package/umd/src/commitments/USE_TIMEOUT/getTimeoutToolRuntimeAdapterOrDisabledResult.d.ts +2 -2
  18. package/umd/src/commitments/USE_TIMEOUT/parseTimeoutToolArgs.d.ts +14 -1
  19. package/umd/src/version.d.ts +1 -1
  20. package/esm/apps/agents-server/config.d.ts +0 -86
  21. package/esm/apps/agents-server/src/tools/$provideBrowserForServer.d.ts +0 -28
  22. package/esm/apps/agents-server/src/tools/BrowserConnectionProvider.d.ts +0 -142
  23. package/esm/apps/agents-server/src/tools/runBrowserArtifacts.d.ts +0 -25
  24. package/esm/apps/agents-server/src/tools/runBrowserConstants.d.ts +0 -21
  25. package/esm/apps/agents-server/src/tools/runBrowserErrorHandling.d.ts +0 -60
  26. package/esm/apps/agents-server/src/tools/runBrowserErrors.d.ts +0 -73
  27. package/esm/apps/agents-server/src/tools/runBrowserObservability.d.ts +0 -36
  28. package/esm/apps/agents-server/src/tools/runBrowserResultFormatting.d.ts +0 -47
  29. package/esm/apps/agents-server/src/tools/runBrowserRuntime.d.ts +0 -24
  30. package/esm/apps/agents-server/src/tools/runBrowserWorkflow.d.ts +0 -36
  31. package/esm/apps/agents-server/src/tools/run_browser.d.ts +0 -11
  32. package/esm/apps/agents-server/src/utils/retryWithBackoff.d.ts +0 -95
  33. package/esm/apps/agents-server/src/utils/runBrowserArtifactStorage.d.ts +0 -26
  34. package/umd/apps/agents-server/config.d.ts +0 -86
  35. package/umd/apps/agents-server/src/tools/$provideBrowserForServer.d.ts +0 -28
  36. package/umd/apps/agents-server/src/tools/BrowserConnectionProvider.d.ts +0 -142
  37. package/umd/apps/agents-server/src/tools/runBrowserArtifacts.d.ts +0 -25
  38. package/umd/apps/agents-server/src/tools/runBrowserConstants.d.ts +0 -21
  39. package/umd/apps/agents-server/src/tools/runBrowserErrorHandling.d.ts +0 -60
  40. package/umd/apps/agents-server/src/tools/runBrowserErrors.d.ts +0 -73
  41. package/umd/apps/agents-server/src/tools/runBrowserObservability.d.ts +0 -36
  42. package/umd/apps/agents-server/src/tools/runBrowserResultFormatting.d.ts +0 -47
  43. package/umd/apps/agents-server/src/tools/runBrowserRuntime.d.ts +0 -24
  44. package/umd/apps/agents-server/src/tools/runBrowserWorkflow.d.ts +0 -36
  45. package/umd/apps/agents-server/src/tools/run_browser.d.ts +0 -11
  46. package/umd/apps/agents-server/src/utils/retryWithBackoff.d.ts +0 -95
  47. package/umd/apps/agents-server/src/utils/runBrowserArtifactStorage.d.ts +0 -26
package/esm/index.es.js CHANGED
@@ -13,10 +13,7 @@ import { basename, join, dirname, isAbsolute, relative } from 'path';
13
13
  import { Readability } from '@mozilla/readability';
14
14
  import { JSDOM } from 'jsdom';
15
15
  import { Converter } from 'showdown';
16
- import { randomBytes, randomUUID } from 'crypto';
17
- import { tmpdir } from 'os';
18
- import { ConfigChecker } from 'configchecker';
19
- import { chromium } from 'playwright';
16
+ import { randomBytes } from 'crypto';
20
17
  import * as dotenv from 'dotenv';
21
18
  import sha256 from 'crypto-js/sha256';
22
19
  import JSZip from 'jszip';
@@ -41,7 +38,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
41
38
  * @generated
42
39
  * @see https://github.com/webgptorg/promptbook
43
40
  */
44
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-12';
41
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-13';
45
42
  /**
46
43
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
47
44
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -5965,15 +5962,6 @@ function parseToolRuntimeContext(rawValue) {
5965
5962
  function readToolRuntimeContextFromToolArgs(args) {
5966
5963
  return parseToolRuntimeContext(args[TOOL_RUNTIME_CONTEXT_ARGUMENT]);
5967
5964
  }
5968
- /**
5969
- * Reads the hidden tool-progress token from tool arguments.
5970
- *
5971
- * @private internal runtime wiring for commitment tools
5972
- */
5973
- function readToolProgressTokenFromToolArgs(args) {
5974
- const token = args[TOOL_PROGRESS_TOKEN_ARGUMENT];
5975
- return typeof token === 'string' && token.trim().length > 0 ? token : null;
5976
- }
5977
5965
  /**
5978
5966
  * Serializes runtime context for prompt parameters.
5979
5967
  *
@@ -6007,26 +5995,6 @@ function registerToolCallProgressListener(listener) {
6007
5995
  function unregisterToolCallProgressListener(token) {
6008
5996
  toolCallProgressListeners.delete(token);
6009
5997
  }
6010
- /**
6011
- * Emits one tool progress update using a hidden token carried in tool arguments.
6012
- *
6013
- * @param args - Raw tool arguments including hidden runtime keys.
6014
- * @param update - Incremental progress update.
6015
- * @returns `true` when a listener was found and notified.
6016
- * @private internal runtime wiring for commitment tools
6017
- */
6018
- function emitToolCallProgressFromToolArgs(args, update) {
6019
- const token = readToolProgressTokenFromToolArgs(args);
6020
- if (!token) {
6021
- return false;
6022
- }
6023
- const listener = toolCallProgressListeners.get(token);
6024
- if (!listener) {
6025
- return false;
6026
- }
6027
- listener(update);
6028
- return true;
6029
- }
6030
5998
  /**
6031
5999
  * Note: [💞] Ignore a discrepancy between file name and entity name
6032
6000
  */
@@ -27486,9 +27454,9 @@ function createTimeoutSystemMessage(extraInstructions) {
27486
27454
  return spaceTrim$1((block) => `
27487
27455
  Timeout scheduling:
27488
27456
  - Use "set_timeout" to wake this same chat thread in the future.
27489
- - Timers are thread-scoped, not global for the whole agent.
27457
+ - Use "list_timeouts" to review timeouts across all chats for the same user+agent scope.
27458
+ - "cancel_timeout" accepts a timeout id from any chat in this same user+agent scope.
27490
27459
  - When one timeout elapses, you will receive a new user-like message that explicitly says it is a timeout wake-up and includes the \`timeoutId\`.
27491
- - Use "cancel_timeout" when a previously scheduled timeout is no longer relevant.
27492
27460
  - Do not claim a timer was set or cancelled unless the tool confirms it.
27493
27461
  ${block(extraInstructions)}
27494
27462
  `);
@@ -27500,13 +27468,6 @@ function createTimeoutSystemMessage(extraInstructions) {
27500
27468
  * @private internal utility of USE TIMEOUT
27501
27469
  */
27502
27470
  function createDisabledTimeoutResult(action, message) {
27503
- if (action === 'set') {
27504
- return {
27505
- action,
27506
- status: 'disabled',
27507
- message,
27508
- };
27509
- }
27510
27471
  return {
27511
27472
  action,
27512
27473
  status: 'disabled',
@@ -27533,6 +27494,18 @@ function getTimeoutToolRuntimeAdapterOrDisabledResult(action, runtimeContext) {
27533
27494
  }
27534
27495
  }
27535
27496
 
27497
+ /**
27498
+ * Default number of rows returned by `list_timeouts`.
27499
+ *
27500
+ * @private internal USE TIMEOUT constant
27501
+ */
27502
+ const DEFAULT_LIST_TIMEOUTS_LIMIT = 20;
27503
+ /**
27504
+ * Hard cap for `list_timeouts` page size.
27505
+ *
27506
+ * @private internal USE TIMEOUT constant
27507
+ */
27508
+ const MAX_LIST_TIMEOUTS_LIMIT = 100;
27536
27509
  /**
27537
27510
  * Parses and validates `USE TIMEOUT` tool arguments.
27538
27511
  *
@@ -27567,6 +27540,31 @@ const parseTimeoutToolArgs = {
27567
27540
  }
27568
27541
  return { timeoutId };
27569
27542
  },
27543
+ /**
27544
+ * Parses `list_timeouts` input.
27545
+ */
27546
+ list(args) {
27547
+ if (args.includeFinished !== undefined && typeof args.includeFinished !== 'boolean') {
27548
+ throw new PipelineExecutionError(spaceTrim$1(`
27549
+ Timeout \`includeFinished\` must be a boolean when provided.
27550
+ `));
27551
+ }
27552
+ const parsedLimit = args.limit === undefined ? DEFAULT_LIST_TIMEOUTS_LIMIT : Math.floor(Number(args.limit));
27553
+ if (!Number.isFinite(parsedLimit) || parsedLimit <= 0) {
27554
+ throw new PipelineExecutionError(spaceTrim$1(`
27555
+ Timeout \`limit\` must be a positive number.
27556
+ `));
27557
+ }
27558
+ if (parsedLimit > MAX_LIST_TIMEOUTS_LIMIT) {
27559
+ throw new PipelineExecutionError(spaceTrim$1(`
27560
+ Timeout \`limit\` must be at most \`${MAX_LIST_TIMEOUTS_LIMIT}\`.
27561
+ `));
27562
+ }
27563
+ return {
27564
+ includeFinished: args.includeFinished === true,
27565
+ limit: parsedLimit,
27566
+ };
27567
+ },
27570
27568
  };
27571
27569
 
27572
27570
  /**
@@ -27577,6 +27575,7 @@ const parseTimeoutToolArgs = {
27577
27575
  const TimeoutToolNames = {
27578
27576
  set: 'set_timeout',
27579
27577
  cancel: 'cancel_timeout',
27578
+ list: 'list_timeouts',
27580
27579
  };
27581
27580
 
27582
27581
  /**
@@ -27676,6 +27675,35 @@ function createTimeoutToolFunctions() {
27676
27675
  return JSON.stringify(result);
27677
27676
  }
27678
27677
  },
27678
+ async [TimeoutToolNames.list](args) {
27679
+ const runtimeContext = resolveTimeoutRuntimeContext(args);
27680
+ const { adapter, disabledResult } = getTimeoutToolRuntimeAdapterOrDisabledResult('list', runtimeContext);
27681
+ if (!adapter || disabledResult) {
27682
+ return JSON.stringify(disabledResult);
27683
+ }
27684
+ try {
27685
+ const parsedArgs = parseTimeoutToolArgs.list(args);
27686
+ const listedTimeouts = await adapter.listTimeouts(parsedArgs, runtimeContext);
27687
+ const result = {
27688
+ action: 'list',
27689
+ status: 'listed',
27690
+ items: listedTimeouts.items,
27691
+ total: listedTimeouts.total,
27692
+ };
27693
+ return createToolExecutionEnvelope({
27694
+ assistantMessage: listedTimeouts.total === 1 ? 'Found 1 timeout.' : `Found ${listedTimeouts.total} timeouts.`,
27695
+ toolResult: result,
27696
+ });
27697
+ }
27698
+ catch (error) {
27699
+ const result = {
27700
+ action: 'list',
27701
+ status: 'error',
27702
+ message: error instanceof Error ? error.message : String(error),
27703
+ };
27704
+ return JSON.stringify(result);
27705
+ }
27706
+ },
27679
27707
  };
27680
27708
  }
27681
27709
 
@@ -27709,26 +27737,45 @@ function createTimeoutTools(existingTools = []) {
27709
27737
  if (!tools.some((tool) => tool.name === TimeoutToolNames.cancel)) {
27710
27738
  tools.push({
27711
27739
  name: TimeoutToolNames.cancel,
27712
- description: 'Cancel one previously scheduled timeout in the current chat thread.',
27740
+ description: 'Cancel one previously scheduled timeout within the same user+agent scope, even if it was set in another chat.',
27713
27741
  parameters: {
27714
27742
  type: 'object',
27715
27743
  properties: {
27716
27744
  timeoutId: {
27717
27745
  type: 'string',
27718
- description: 'Identifier returned earlier by `set_timeout`.',
27746
+ description: 'Identifier returned earlier by `set_timeout` or `list_timeouts`.',
27719
27747
  },
27720
27748
  },
27721
27749
  required: ['timeoutId'],
27722
27750
  },
27723
27751
  });
27724
27752
  }
27753
+ if (!tools.some((tool) => tool.name === TimeoutToolNames.list)) {
27754
+ tools.push({
27755
+ name: TimeoutToolNames.list,
27756
+ description: 'List scheduled timeouts across all chats for this same user+agent scope so they can be reviewed or cancelled.',
27757
+ parameters: {
27758
+ type: 'object',
27759
+ properties: {
27760
+ includeFinished: {
27761
+ type: 'boolean',
27762
+ description: 'When true, include completed, failed, and cancelled rows in addition to active timeouts.',
27763
+ },
27764
+ limit: {
27765
+ type: 'number',
27766
+ description: 'Maximum number of rows to return (default 20, max 100).',
27767
+ },
27768
+ },
27769
+ },
27770
+ });
27771
+ }
27725
27772
  return tools;
27726
27773
  }
27727
27774
 
27728
27775
  /**
27729
27776
  * `USE TIMEOUT` commitment definition.
27730
27777
  *
27731
- * The `USE TIMEOUT` commitment enables thread-scoped timers that wake the same chat later.
27778
+ * The `USE TIMEOUT` commitment enables timeout wake-ups and scoped timeout management.
27732
27779
  *
27733
27780
  * @private [🪔] Maybe export the commitments through some package
27734
27781
  */
@@ -27743,7 +27790,7 @@ class UseTimeoutCommitmentDefinition extends BaseCommitmentDefinition {
27743
27790
  * Short one-line description of `USE TIMEOUT`.
27744
27791
  */
27745
27792
  get description() {
27746
- return 'Enable thread-scoped timers that can wake the same chat in the future.';
27793
+ return 'Enable timeout wake-ups plus scoped timeout listing/cancellation across chats.';
27747
27794
  }
27748
27795
  /**
27749
27796
  * Icon for this commitment.
@@ -27758,14 +27805,15 @@ class UseTimeoutCommitmentDefinition extends BaseCommitmentDefinition {
27758
27805
  return spaceTrim$1(`
27759
27806
  # USE TIMEOUT
27760
27807
 
27761
- Enables the agent to schedule thread-scoped timeout wake-ups.
27808
+ Enables timeout wake-ups and timeout management for the same user+agent scope.
27762
27809
 
27763
27810
  ## Key aspects
27764
27811
 
27765
27812
  - The agent uses \`set_timeout\` to schedule a future wake-up in the same chat thread.
27766
27813
  - The tool returns immediately while the timeout is stored and executed by the runtime later.
27767
27814
  - The wake-up arrives as a new user-like timeout message in the same conversation.
27768
- - The agent can cancel an existing timeout by \`timeoutId\` via \`cancel_timeout\`.
27815
+ - The agent can inspect known timeouts via \`list_timeouts\`.
27816
+ - The agent can cancel an existing timeout by \`timeoutId\` via \`cancel_timeout\`, including timeouts created in another chat.
27769
27817
  - Commitment content is treated as optional timeout policy instructions.
27770
27818
 
27771
27819
  ## Examples
@@ -27794,6 +27842,7 @@ class UseTimeoutCommitmentDefinition extends BaseCommitmentDefinition {
27794
27842
  return {
27795
27843
  [TimeoutToolNames.set]: 'Set timer',
27796
27844
  [TimeoutToolNames.cancel]: 'Cancel timer',
27845
+ [TimeoutToolNames.list]: 'List timers',
27797
27846
  };
27798
27847
  }
27799
27848
  /**
@@ -30176,2203 +30225,177 @@ async function fetchUrlContent(url) {
30176
30225
  */
30177
30226
 
30178
30227
  /**
30179
- * Logical public directory marker used in `run_browser` payload paths.
30228
+ * Cached implementation of `run_browser` when it can be resolved.
30180
30229
  *
30181
- * This value is kept stable for UI parsing and `/api/browser-artifacts/*` URL mapping.
30182
- */
30183
- const RUN_BROWSER_ARTIFACT_PUBLIC_DIRECTORY = '.playwright-cli';
30184
- /**
30185
- * Runtime environment variable that overrides local artifact storage directory.
30186
- */
30187
- const RUN_BROWSER_ARTIFACT_STORAGE_DIRECTORY_ENV = 'RUN_BROWSER_ARTIFACT_STORAGE_DIRECTORY';
30188
- /**
30189
- * Default writable directory for `run_browser` screenshot/video artifacts.
30230
+ * @private internal utility for USE BROWSER commitment
30190
30231
  */
30191
- const DEFAULT_RUN_BROWSER_ARTIFACT_STORAGE_DIRECTORY = join(tmpdir(), 'promptbook', 'run-browser-artifacts');
30232
+ let cachedRunBrowserTool = null;
30192
30233
  /**
30193
- * Converts Windows separators to POSIX separators for payload paths.
30234
+ * Cached loading error to avoid repeating expensive resolution attempts.
30235
+ *
30236
+ * @private internal utility for USE BROWSER commitment
30194
30237
  */
30195
- function toPosixPath(pathname) {
30196
- return pathname.split('\\').join('/');
30197
- }
30238
+ let cachedRunBrowserToolError = null;
30198
30239
  /**
30199
- * Resolves writable filesystem directory used for artifact persistence.
30240
+ * Attempts to load the server-side `run_browser` tool lazily.
30241
+ *
30242
+ * @returns Loaded `run_browser` implementation
30243
+ * @private internal utility for USE BROWSER commitment
30200
30244
  */
30201
- function resolveRunBrowserArtifactStorageDirectory() {
30202
- const configuredStorageDirectory = process.env[RUN_BROWSER_ARTIFACT_STORAGE_DIRECTORY_ENV];
30203
- if (configuredStorageDirectory && configuredStorageDirectory.trim()) {
30204
- return configuredStorageDirectory.trim();
30245
+ function loadRunBrowserToolForNode() {
30246
+ if (cachedRunBrowserTool !== null) {
30247
+ return cachedRunBrowserTool;
30205
30248
  }
30206
- return DEFAULT_RUN_BROWSER_ARTIFACT_STORAGE_DIRECTORY;
30207
- }
30208
- /**
30209
- * Resolves absolute filesystem path of one artifact filename.
30210
- */
30211
- function resolveRunBrowserArtifactFilesystemPath(artifactFilename) {
30212
- return join(resolveRunBrowserArtifactStorageDirectory(), artifactFilename);
30213
- }
30214
- /**
30215
- * Resolves payload path of one artifact filename used by replay renderers.
30216
- */
30217
- function resolveRunBrowserArtifactPublicPath(artifactFilename) {
30218
- return toPosixPath(`${RUN_BROWSER_ARTIFACT_PUBLIC_DIRECTORY}/${artifactFilename}`);
30219
- }
30220
-
30221
- /**
30222
- * Error code used for remote-browser infrastructure outages.
30223
- */
30224
- const REMOTE_BROWSER_UNAVAILABLE_ERROR_CODE = 'REMOTE_BROWSER_UNAVAILABLE';
30225
- /**
30226
- * Error thrown when a remote Playwright browser cannot be reached.
30227
- */
30228
- class RemoteBrowserUnavailableError extends KnowledgeScrapeError {
30229
- constructor(options) {
30230
- var _a;
30231
- super(options.message);
30232
- this.code = REMOTE_BROWSER_UNAVAILABLE_ERROR_CODE;
30233
- this.isRetryable = true;
30234
- this.debug = options.debug;
30235
- this.suggestedNextSteps =
30236
- (_a = options.suggestedNextSteps) !== null && _a !== void 0 ? _a : [
30237
- 'Verify remote browser infrastructure is running and reachable from Agents Server.',
30238
- 'Check firewall and DNS routing for the remote browser host and port.',
30239
- 'Retry later or continue with non-graphical fallback scraping.',
30240
- ];
30241
- this.cause = options.cause;
30242
- Object.setPrototypeOf(this, RemoteBrowserUnavailableError.prototype);
30249
+ if (cachedRunBrowserToolError !== null) {
30250
+ throw cachedRunBrowserToolError;
30251
+ }
30252
+ try {
30253
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
30254
+ const runBrowserModule = require('../../../apps/agents-server/src/tools/run_browser');
30255
+ if (typeof runBrowserModule.run_browser !== 'function') {
30256
+ throw new Error('run_browser value is not a function but ' + typeof runBrowserModule.run_browser);
30257
+ }
30258
+ cachedRunBrowserTool = runBrowserModule.run_browser;
30259
+ return cachedRunBrowserTool;
30260
+ }
30261
+ catch (error) {
30262
+ assertsError(error);
30263
+ cachedRunBrowserToolError = error;
30264
+ throw error;
30243
30265
  }
30244
30266
  }
30245
30267
  /**
30246
- * Returns true when an unknown value is one of the remote-browser outage errors.
30268
+ * Resolves the server-side implementation of the `run_browser` tool for Node.js environments.
30269
+ *
30270
+ * This uses fully lazy resolution to keep CLI startup independent from optional browser tooling.
30271
+ * When the server tool cannot be resolved, the fallback implementation throws a helpful error.
30272
+ *
30273
+ * @private internal utility for USE BROWSER commitment
30247
30274
  */
30248
- function isRemoteBrowserUnavailableError(error) {
30249
- return error instanceof RemoteBrowserUnavailableError;
30275
+ function resolveRunBrowserToolForNode() {
30276
+ return async (args) => {
30277
+ try {
30278
+ const runBrowserTool = loadRunBrowserToolForNode();
30279
+ return await runBrowserTool(args);
30280
+ }
30281
+ catch (error) {
30282
+ assertsError(error);
30283
+ throw new EnvironmentMismatchError(spaceTrim$1((block) => `
30284
+ \`run_browser\` tool is not available in this environment.
30285
+ This commitment requires the Agents Server browser runtime with Playwright CLI.
30286
+
30287
+ ${error.name}:
30288
+ ${block(error.message)}
30289
+ `));
30290
+ }
30291
+ };
30250
30292
  }
30293
+
30251
30294
  /**
30252
- * Sanitizes a remote websocket endpoint so debug payloads never expose path secrets.
30295
+ * Resolves the server-side implementation of the send_email tool for Node.js environments.
30296
+ *
30297
+ * This uses a lazy require so the core package can still load even if the Agents Server
30298
+ * module is unavailable. When the server tool cannot be resolved, a fallback implementation
30299
+ * throws a helpful error message.
30300
+ *
30301
+ * @private internal utility for USE EMAIL commitment
30253
30302
  */
30254
- function sanitizeRemoteBrowserEndpoint(wsEndpoint) {
30255
- var _a, _b;
30303
+ function resolveSendEmailToolForNode() {
30256
30304
  try {
30257
- const parsedEndpoint = new URL(wsEndpoint);
30258
- return {
30259
- protocol: parsedEndpoint.protocol || null,
30260
- host: parsedEndpoint.hostname || null,
30261
- port: parsedEndpoint.port ? Number.parseInt(parsedEndpoint.port, 10) : null,
30262
- };
30305
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
30306
+ const { send_email } = require('../../../apps/agents-server/src/tools/send_email');
30307
+ if (typeof send_email !== 'function') {
30308
+ throw new Error('send_email value is not a function but ' + typeof send_email);
30309
+ }
30310
+ return send_email;
30263
30311
  }
30264
- catch (_c) {
30265
- const hostPortMatch = wsEndpoint.trim().match(/^(?:wss?:\/\/)?(?<host>[^:/?#]+)(?::(?<port>\d{1,5}))?/i);
30266
- const host = ((_a = hostPortMatch === null || hostPortMatch === void 0 ? void 0 : hostPortMatch.groups) === null || _a === void 0 ? void 0 : _a.host) || null;
30267
- const parsedPort = (_b = hostPortMatch === null || hostPortMatch === void 0 ? void 0 : hostPortMatch.groups) === null || _b === void 0 ? void 0 : _b.port;
30268
- return {
30269
- protocol: wsEndpoint.startsWith('wss://') ? 'wss:' : wsEndpoint.startsWith('ws://') ? 'ws:' : null,
30270
- host,
30271
- port: parsedPort ? Number.parseInt(parsedPort, 10) : null,
30312
+ catch (error) {
30313
+ const normalizedError = error instanceof Error
30314
+ ? error
30315
+ : new Error(typeof error === 'string' ? error : JSON.stringify(error !== null && error !== void 0 ? error : 'Unknown error'));
30316
+ return async () => {
30317
+ throw new EnvironmentMismatchError(spaceTrim$1((block) => `
30318
+ \`send_email\` tool is not available in this environment.
30319
+ This commitment requires Agents Server runtime with wallet-backed SMTP sending.
30320
+
30321
+ ${normalizedError.name}:
30322
+ ${block(normalizedError.message)}
30323
+ `));
30272
30324
  };
30273
30325
  }
30274
30326
  }
30327
+
30275
30328
  /**
30276
- * Extracts network-like error code from unknown error payload.
30329
+ * Resolves the server-side `spawn_agent` tool for Node.js runtimes.
30330
+ *
30331
+ * Uses lazy require so core package can load outside Agents Server.
30332
+ *
30333
+ * @private internal utility for USE SPAWN commitment
30277
30334
  */
30278
- function extractNetworkErrorCode(error) {
30279
- var _a;
30280
- if (error && typeof error === 'object') {
30281
- const maybeCode = error.code;
30282
- if (typeof maybeCode === 'string' && maybeCode.trim()) {
30283
- return maybeCode.trim().toUpperCase();
30335
+ function resolveSpawnAgentToolForNode() {
30336
+ try {
30337
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
30338
+ const { spawn_agent } = require('../../../apps/agents-server/src/tools/spawn_agent');
30339
+ if (typeof spawn_agent !== 'function') {
30340
+ throw new Error('spawn_agent value is not a function but ' + typeof spawn_agent);
30284
30341
  }
30342
+ return spawn_agent;
30285
30343
  }
30286
- const message = getErrorMessage(error);
30287
- const match = message.match(/\b(ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|ECONNRESET|EHOSTUNREACH|ENETUNREACH)\b/i);
30288
- return ((_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || null;
30289
- }
30290
- /**
30291
- * Classifies whether an unknown error most likely represents remote browser infra outage.
30292
- */
30293
- function isRemoteBrowserInfrastructureError(error) {
30294
- const networkErrorCode = extractNetworkErrorCode(error);
30295
- if (networkErrorCode) {
30296
- return true;
30297
- }
30298
- const message = getErrorMessage(error).toLowerCase();
30299
- const isWebSocketFailure = message.includes('websocket') ||
30300
- message.includes('<ws error>') ||
30301
- message.includes('ws connect error') ||
30302
- message.includes('socket hang up');
30303
- const hasHandshakeFailure = message.includes('unexpected server response') ||
30304
- message.includes('handshake') ||
30305
- message.includes('code=1006') ||
30306
- message.includes('disconnected');
30307
- return isWebSocketFailure && hasHandshakeFailure;
30308
- }
30309
- /**
30310
- * Converts unknown thrown values into safe string messages.
30311
- */
30312
- function getErrorMessage(error) {
30313
- return error instanceof Error ? error.message : String(error);
30314
- }
30315
- /**
30316
- * Converts unknown errors into stack payloads that are safe to render in debug mode.
30317
- */
30318
- function getErrorStack(error) {
30319
- return error instanceof Error && error.stack ? error.stack : null;
30320
- }
30344
+ catch (error) {
30345
+ const normalizedError = error instanceof Error
30346
+ ? error
30347
+ : new Error(typeof error === 'string' ? error : JSON.stringify(error !== null && error !== void 0 ? error : 'Unknown error'));
30348
+ return async () => {
30349
+ throw new EnvironmentMismatchError(spaceTrim$1((block) => `
30350
+ \`spawn_agent\` tool is not available in this environment.
30351
+ This commitment requires Agents Server runtime with agent persistence enabled.
30321
30352
 
30322
- /**
30323
- * Matches unsupported characters in snapshot file suffixes.
30324
- */
30325
- const SNAPSHOT_FILE_SUFFIX_UNSAFE_CHARACTER_PATTERN = /[^a-z0-9-]/g;
30326
- /**
30327
- * Creates one filesystem-safe optional filename suffix for a snapshot.
30328
- */
30329
- function createSnapshotFileSuffix(rawSuffix) {
30330
- if (!rawSuffix) {
30331
- return '';
30353
+ ${normalizedError.name}:
30354
+ ${block(normalizedError.message)}
30355
+ `));
30356
+ };
30332
30357
  }
30333
- const normalized = rawSuffix
30334
- .trim()
30335
- .toLowerCase()
30336
- .replace(/\s+/g, '-')
30337
- .replace(SNAPSHOT_FILE_SUFFIX_UNSAFE_CHARACTER_PATTERN, '-')
30338
- .replace(/-+/g, '-')
30339
- .replace(/^-|-$/g, '');
30340
- return normalized;
30341
- }
30342
- /**
30343
- * Resolves snapshot filename for one session and optional stage suffix.
30344
- */
30345
- function resolveSnapshotFilename(sessionId, fileSuffix) {
30346
- const safeSuffix = createSnapshotFileSuffix(fileSuffix);
30347
- return safeSuffix ? `${sessionId}-${safeSuffix}.png` : `${sessionId}.png`;
30348
30358
  }
30359
+
30349
30360
  /**
30350
- * Creates one user-facing description for an executed browser action.
30361
+ * Collects tool functions from all commitment definitions.
30362
+ *
30363
+ * @returns Map of tool function implementations.
30364
+ * @private internal helper for commitment tool registry
30351
30365
  */
30352
- function formatActionSummary(action) {
30353
- switch (action.type) {
30354
- case 'navigate':
30355
- return `Navigate to ${action.url}`;
30356
- case 'click':
30357
- return `Click ${action.selector}`;
30358
- case 'type':
30359
- return `Type into ${action.selector}`;
30360
- case 'wait':
30361
- return `Wait ${action.milliseconds}ms`;
30362
- case 'scroll':
30363
- return action.selector ? `Scroll ${action.pixels}px in ${action.selector}` : `Scroll ${action.pixels}px on page`;
30366
+ function collectCommitmentToolFunctions() {
30367
+ const allToolFunctions = {};
30368
+ for (const commitmentDefinition of getAllCommitmentDefinitions()) {
30369
+ const toolFunctions = commitmentDefinition.getToolFunctions();
30370
+ for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
30371
+ if (allToolFunctions[funcName] !== undefined &&
30372
+ just(false) /* <- Note: [??] How to deal with commitment aliases */) {
30373
+ throw new UnexpectedError(`Duplicate tool function name detected: \`${funcName}\` provided by commitment \`${commitmentDefinition.type}\``);
30374
+ }
30375
+ allToolFunctions[funcName] = funcImpl;
30376
+ }
30364
30377
  }
30378
+ return allToolFunctions;
30365
30379
  }
30366
30380
  /**
30367
- * Screenshot/artifact and page-cleanup helpers for `run_browser`.
30381
+ * Creates a proxy that resolves tool functions on demand.
30368
30382
  *
30369
- * @private function of `run_browser`
30383
+ * @param getFunctions - Provider of current tool functions.
30384
+ * @returns Proxy exposing tool functions as properties.
30385
+ * @private internal helper for commitment tool registry
30370
30386
  */
30371
- const runBrowserArtifacts = {
30372
- /**
30373
- * Captures a screenshot artifact for the current page and returns relative path.
30374
- */
30375
- async captureSnapshot(page, sessionId, fileSuffix) {
30376
- const snapshotFilename = resolveSnapshotFilename(sessionId, fileSuffix);
30377
- const snapshotDirectoryPath = resolveRunBrowserArtifactStorageDirectory();
30378
- const snapshotPath = resolveRunBrowserArtifactFilesystemPath(snapshotFilename);
30379
- try {
30380
- await mkdir(snapshotDirectoryPath, { recursive: true });
30381
- try {
30382
- await page.screenshot({ path: snapshotPath, fullPage: true });
30387
+ function createToolFunctionsProxy(getFunctions) {
30388
+ const resolveFunctions = () => getFunctions();
30389
+ return new Proxy({}, {
30390
+ get(_target, prop) {
30391
+ if (typeof prop !== 'string') {
30392
+ return undefined;
30383
30393
  }
30384
- catch (error) {
30385
- console.warn('[run_browser] Full-page snapshot failed, retrying viewport-only screenshot', {
30386
- sessionId,
30387
- snapshotFilename,
30388
- error: getErrorMessage(error),
30389
- });
30390
- await page.screenshot({ path: snapshotPath, fullPage: false });
30391
- }
30392
- return resolveRunBrowserArtifactPublicPath(snapshotFilename);
30393
- }
30394
- catch (error) {
30395
- console.error('[run_browser] Failed to capture snapshot', {
30396
- sessionId,
30397
- snapshotFilename,
30398
- error: getErrorMessage(error),
30399
- });
30400
- return null;
30401
- }
30402
- },
30403
- /**
30404
- * Safely retrieves page title from current browser page.
30405
- */
30406
- async getPageTitle(page) {
30407
- try {
30408
- return await page.title();
30409
- }
30410
- catch (_a) {
30411
- return null;
30412
- }
30413
- },
30414
- /**
30415
- * Closes browser page and logs non-fatal cleanup errors.
30416
- */
30417
- async cleanupPage(page, sessionId) {
30418
- if (!page) {
30419
- return;
30420
- }
30421
- try {
30422
- await page.close();
30423
- }
30424
- catch (error) {
30425
- console.error('[run_browser] Failed to cleanup browser page', {
30426
- sessionId,
30427
- error: getErrorMessage(error),
30428
- });
30429
- }
30430
- },
30431
- /**
30432
- * Captures one screenshot artifact and enriches it with page metadata.
30433
- */
30434
- async captureSnapshotArtifact(options) {
30435
- const { page, sessionId, label, fileSuffix, actionIndex, action } = options;
30436
- const path = await this.captureSnapshot(page, sessionId, fileSuffix);
30437
- if (!path) {
30438
- return null;
30439
- }
30440
- const actionSummary = action ? formatActionSummary(action) : undefined;
30441
- return {
30442
- kind: 'screenshot',
30443
- label,
30444
- path,
30445
- capturedAt: new Date().toISOString(),
30446
- url: page.url(),
30447
- title: await this.getPageTitle(page),
30448
- actionIndex,
30449
- actionSummary,
30450
- };
30451
- },
30452
- };
30453
-
30454
- /**
30455
- * Shared constants used by the `run_browser` tool.
30456
- *
30457
- * @private internal constants of `run_browser`
30458
- */
30459
- const runBrowserConstants = {
30460
- sessionPrefix: 'agents-server-run-browser',
30461
- snapshotDirectory: '.playwright-cli',
30462
- resultSchema: 'promptbook/run-browser@1',
30463
- defaultWaitMs: 1000,
30464
- maxWaitMs: 60000,
30465
- defaultScrollPixels: 800,
30466
- defaultNavigationTimeoutMs: 20000,
30467
- defaultActionTimeoutMs: 15000,
30468
- fallbackDynamicContentWarning: 'Remote browser is unavailable. Fallback scraping was used and dynamic content may be missing.',
30469
- validationErrorCode: 'RUN_BROWSER_VALIDATION_ERROR',
30470
- navigationFailedErrorCode: 'RUN_BROWSER_NAVIGATION_FAILED',
30471
- actionFailedErrorCode: 'RUN_BROWSER_ACTION_FAILED',
30472
- cancelledErrorCode: 'RUN_BROWSER_CANCELLED',
30473
- unknownErrorCode: 'RUN_BROWSER_UNKNOWN_ERROR',
30474
- };
30475
-
30476
- const config = ConfigChecker.from({
30477
- ...process.env,
30478
- // Note: To expose env variables to the browser, using this seemingly strange syntax:
30479
- // @see https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#exposing-environment-variables-to-the-browser
30480
- NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
30481
- NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV,
30482
- NEXT_PUBLIC_VERCEL_TARGET_ENV: process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV,
30483
- NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
30484
- NEXT_PUBLIC_VERCEL_BRANCH_URL: process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL,
30485
- NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL: process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL,
30486
- NEXT_PUBLIC_VERCEL_GIT_PROVIDER: process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER,
30487
- NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER,
30488
- NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG,
30489
- NEXT_PUBLIC_VERCEL_GIT_REPO_ID: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_ID,
30490
- NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
30491
- NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE,
30492
- NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF,
30493
- NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME,
30494
- NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN,
30495
- NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA: process.env.NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA,
30496
- NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID: process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID,
30497
- });
30498
- /**
30499
- * Public URL of the deployment, e.g. "https://my-app.vercel.app"
30500
- *
30501
- * Note: When a request resolves through the global `_Server` registry,
30502
- * this URL will be overridden by the matched server domain.
30503
- */
30504
- config.get('NEXT_PUBLIC_SITE_URL').url().value;
30505
- /**
30506
- * [♐️] Vercel environment: "development" | "preview" | "production"
30507
- */
30508
- config.get('NEXT_PUBLIC_VERCEL_ENV').value;
30509
- /**
30510
- * [♐️] Target environment – can be system or custom
30511
- */
30512
- config.get('NEXT_PUBLIC_VERCEL_TARGET_ENV').value;
30513
- /**
30514
- * [♐️] Deployment URL (without https://), e.g. "my-app-abc123.vercel.app"
30515
- */
30516
- config.get('NEXT_PUBLIC_VERCEL_URL').value;
30517
- /**
30518
- * [♐️] Branch URL (without https://), only for branch deployments
30519
- */
30520
- config.get('NEXT_PUBLIC_VERCEL_BRANCH_URL').value;
30521
- /**
30522
- * [♐️] Production domain of the project
30523
- */
30524
- config.get('NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL').value;
30525
- /**
30526
- * [♐️] Git provider (github | gitlab | bitbucket)
30527
- */
30528
- config.get('NEXT_PUBLIC_VERCEL_GIT_PROVIDER').value;
30529
- /**
30530
- * [♐️] Repository owner (e.g. "hejny")
30531
- */
30532
- config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER').value;
30533
- /**
30534
- * [♐️] Repository slug (e.g. "my-project")
30535
- */
30536
- config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG').value;
30537
- /**
30538
- * [♐️] Repository internal ID
30539
- */
30540
- config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_ID').value;
30541
- /**
30542
- * [♐️] Git commit SHA (short or long)
30543
- */
30544
- config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA').value;
30545
- /**
30546
- * [♐️] Commit message used for this deployment
30547
- */
30548
- config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE').value;
30549
- /**
30550
- * [♐️] Branch name (ref), e.g. "main"
30551
- */
30552
- config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF').value;
30553
- /**
30554
- * Author name of the commit
30555
- */
30556
- config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME').value;
30557
- /**
30558
- * [♐️] Author login/username
30559
- */
30560
- config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN').value;
30561
- /**
30562
- * [♐️] Previous deployment commit SHA (if exists)
30563
- */
30564
- config.get('NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA').value;
30565
- /**
30566
- * [♐️] Pull Request ID for PR-based deployments
30567
- */
30568
- config.get('NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID').value;
30569
- /**
30570
- * Supabase table prefix
30571
- *
30572
- * This remains the fallback/default prefix used before `_Server` contains records
30573
- * or for local development requests.
30574
- */
30575
- config.get('SUPABASE_TABLE_PREFIX').value;
30576
- /**
30577
- * WebSocket endpoint URL for remote Playwright browser server (e.g., ws://browser-server:3000).
30578
- *
30579
- * When set, browser automation will connect to this remote server instead of launching a local browser.
30580
- * This is useful for environments like Vercel where running a full browser locally is not possible.
30581
- * Leave empty to use local browser mode.
30582
- */
30583
- const rawRemoteBrowserUrl = config.get('REMOTE_BROWSER_URL').value;
30584
- /**
30585
- * WebSocket endpoint URL for remote Playwright browser server (e.g., ws://browser-server:3000).
30586
- *
30587
- * When set, browser automation will connect to this remote server instead of launching a local browser.
30588
- * This is useful for environments like Vercel where running a full browser locally is not possible.
30589
- * Leave empty to use local browser mode.
30590
- */
30591
- const REMOTE_BROWSER_URL = typeof rawRemoteBrowserUrl === 'string' ? rawRemoteBrowserUrl : '';
30592
-
30593
- /**
30594
- * Reads a positive integer value from environment variables.
30595
- */
30596
- function resolvePositiveIntFromEnv$1(variableName, defaultValue) {
30597
- const rawValue = process.env[variableName];
30598
- if (!rawValue || !rawValue.trim()) {
30599
- return defaultValue;
30600
- }
30601
- const parsed = Number.parseInt(rawValue.trim(), 10);
30602
- if (!Number.isFinite(parsed) || parsed <= 0) {
30603
- return defaultValue;
30604
- }
30605
- return parsed;
30606
- }
30607
- /**
30608
- * Runtime helpers for mode/session/timeout handling in `run_browser`.
30609
- *
30610
- * @private function of `run_browser`
30611
- */
30612
- const runBrowserRuntime = {
30613
- /**
30614
- * Creates a dedicated session id for one tool invocation.
30615
- */
30616
- createRunBrowserSessionId() {
30617
- return `${runBrowserConstants.sessionPrefix}-${randomUUID()}`;
30618
- },
30619
- /**
30620
- * Determines whether the browser tool is running in local or remote mode.
30621
- */
30622
- resolveExecutionMode() {
30623
- return REMOTE_BROWSER_URL && REMOTE_BROWSER_URL.trim().length > 0 ? 'remote' : 'local';
30624
- },
30625
- /**
30626
- * Converts the execution mode into a human-readable label.
30627
- */
30628
- formatExecutionMode(mode) {
30629
- return mode === 'remote' ? 'remote-browser' : 'local-browser';
30630
- },
30631
- /**
30632
- * Resolves timeout configuration from env defaults and optional call overrides.
30633
- */
30634
- resolveTimeoutConfiguration(overrides) {
30635
- const envNavigationTimeoutMs = resolvePositiveIntFromEnv$1('RUN_BROWSER_NAVIGATION_TIMEOUT_MS', runBrowserConstants.defaultNavigationTimeoutMs);
30636
- const envActionTimeoutMs = resolvePositiveIntFromEnv$1('RUN_BROWSER_ACTION_TIMEOUT_MS', runBrowserConstants.defaultActionTimeoutMs);
30637
- const navigationTimeoutMs = (overrides === null || overrides === void 0 ? void 0 : overrides.navigationMs) && Number.isFinite(overrides.navigationMs) && overrides.navigationMs > 0
30638
- ? Math.floor(overrides.navigationMs)
30639
- : envNavigationTimeoutMs;
30640
- const actionTimeoutMs = (overrides === null || overrides === void 0 ? void 0 : overrides.actionMs) && Number.isFinite(overrides.actionMs) && overrides.actionMs > 0
30641
- ? Math.floor(overrides.actionMs)
30642
- : envActionTimeoutMs;
30643
- return {
30644
- navigationTimeoutMs,
30645
- actionTimeoutMs,
30646
- };
30647
- },
30648
- };
30649
-
30650
- /**
30651
- * Error classification and cancellation helpers used by `run_browser`.
30652
- *
30653
- * @private function of `run_browser`
30654
- */
30655
- const runBrowserErrorHandling = {
30656
- /**
30657
- * Creates one tagged ParseError used for deterministic input validation failures.
30658
- */
30659
- createRunBrowserValidationError(options) {
30660
- const error = new ParseError(options.message);
30661
- error.name = 'RunBrowserValidationError';
30662
- error.runBrowserCode = runBrowserConstants.validationErrorCode;
30663
- error.isRetryable = false;
30664
- error.suggestedNextSteps = [
30665
- 'Fix the action payload to match the run_browser schema.',
30666
- 'Check selectors and required action fields before retrying.',
30667
- ];
30668
- error.debug = options.debug;
30669
- return error;
30670
- },
30671
- /**
30672
- * Creates one tagged KnowledgeScrapeError used for navigation failures.
30673
- */
30674
- createRunBrowserNavigationError(options) {
30675
- const error = new KnowledgeScrapeError(options.message);
30676
- error.name = 'RunBrowserNavigationError';
30677
- error.runBrowserCode = runBrowserConstants.navigationFailedErrorCode;
30678
- error.isRetryable = false;
30679
- error.suggestedNextSteps = [
30680
- 'Verify the URL is reachable and not blocked.',
30681
- 'Retry with a simpler action sequence.',
30682
- ];
30683
- error.debug = options.debug;
30684
- error.cause = options.cause;
30685
- return error;
30686
- },
30687
- /**
30688
- * Creates one tagged KnowledgeScrapeError used for action failures.
30689
- */
30690
- createRunBrowserActionError(options) {
30691
- const error = new KnowledgeScrapeError(options.message);
30692
- error.name = 'RunBrowserActionError';
30693
- error.runBrowserCode = runBrowserConstants.actionFailedErrorCode;
30694
- error.isRetryable = false;
30695
- error.suggestedNextSteps = [
30696
- 'Verify selectors and action values.',
30697
- 'Reduce the action sequence to isolate the failing step.',
30698
- ];
30699
- error.debug = options.debug;
30700
- error.cause = options.cause;
30701
- return error;
30702
- },
30703
- /**
30704
- * Creates one tagged KnowledgeScrapeError used for cancellation.
30705
- */
30706
- createRunBrowserCancelledError(options) {
30707
- const error = new KnowledgeScrapeError(options.message);
30708
- error.name = 'RunBrowserCancelledError';
30709
- error.runBrowserCode = runBrowserConstants.cancelledErrorCode;
30710
- error.isRetryable = true;
30711
- error.suggestedNextSteps = [
30712
- 'Retry while request context is still active.',
30713
- 'Increase timeout if operation is expected to run longer.',
30714
- ];
30715
- error.debug = options.debug;
30716
- error.cause = options.cause;
30717
- return error;
30718
- },
30719
- /**
30720
- * Checks whether an unknown error carries run_browser classification tags.
30721
- */
30722
- isTaggedRunBrowserError(error) {
30723
- if (!error || typeof error !== 'object') {
30724
- return false;
30725
- }
30726
- const candidate = error;
30727
- return (typeof candidate.runBrowserCode === 'string' &&
30728
- typeof candidate.isRetryable === 'boolean' &&
30729
- Array.isArray(candidate.suggestedNextSteps) &&
30730
- typeof candidate.debug === 'object' &&
30731
- candidate.debug !== null);
30732
- },
30733
- /**
30734
- * Converts unknown errors into structured tool error payloads.
30735
- */
30736
- classifyRunBrowserToolError(options) {
30737
- if (isRemoteBrowserUnavailableError(options.error)) {
30738
- return {
30739
- code: options.error.code,
30740
- message: options.error.message,
30741
- isRetryable: options.error.isRetryable,
30742
- suggestedNextSteps: options.error.suggestedNextSteps,
30743
- debug: {
30744
- ...options.error.debug,
30745
- sessionId: options.sessionId,
30746
- mode: runBrowserRuntime.formatExecutionMode(options.mode),
30747
- },
30748
- };
30749
- }
30750
- if (this.isTaggedRunBrowserError(options.error)) {
30751
- return {
30752
- code: options.error.runBrowserCode,
30753
- message: options.error.message,
30754
- isRetryable: options.error.isRetryable,
30755
- suggestedNextSteps: options.error.suggestedNextSteps,
30756
- debug: {
30757
- ...options.error.debug,
30758
- sessionId: options.sessionId,
30759
- mode: runBrowserRuntime.formatExecutionMode(options.mode),
30760
- },
30761
- };
30762
- }
30763
- const remoteBrowserEndpoint = REMOTE_BROWSER_URL && REMOTE_BROWSER_URL.trim().length > 0
30764
- ? sanitizeRemoteBrowserEndpoint(REMOTE_BROWSER_URL.trim())
30765
- : null;
30766
- const message = getErrorMessage(options.error);
30767
- return {
30768
- code: runBrowserConstants.unknownErrorCode,
30769
- message,
30770
- isRetryable: false,
30771
- suggestedNextSteps: ['Inspect debug details to identify the failing phase.', 'Retry with fewer actions.'],
30772
- debug: {
30773
- sessionId: options.sessionId,
30774
- mode: runBrowserRuntime.formatExecutionMode(options.mode),
30775
- remoteBrowserEndpoint,
30776
- message,
30777
- stack: getErrorStack(options.error),
30778
- },
30779
- };
30780
- },
30781
- /**
30782
- * Asserts that the run was not aborted.
30783
- */
30784
- assertNotAborted(signal, sessionId) {
30785
- if (!(signal === null || signal === void 0 ? void 0 : signal.aborted)) {
30786
- return;
30787
- }
30788
- throw this.createRunBrowserCancelledError({
30789
- message: 'run_browser execution was cancelled.',
30790
- debug: { sessionId },
30791
- });
30792
- },
30793
- /**
30794
- * Returns true when the tool error represents remote browser unavailability.
30795
- */
30796
- isRemoteBrowserUnavailableCode(code) {
30797
- return code === REMOTE_BROWSER_UNAVAILABLE_ERROR_CODE;
30798
- },
30799
- };
30800
-
30801
- /**
30802
- * In-memory observability counters for browser tool execution.
30803
- */
30804
- const RUN_BROWSER_OBSERVABILITY = {
30805
- totalRuns: 0,
30806
- fallbackRuns: 0,
30807
- errorCodeCounts: {},
30808
- };
30809
- /**
30810
- * Observability counters and metric logging for `run_browser`.
30811
- *
30812
- * @private function of `run_browser`
30813
- */
30814
- const runBrowserObservability = {
30815
- /**
30816
- * Increments total-run counter and returns the updated value.
30817
- */
30818
- incrementTotalRuns() {
30819
- RUN_BROWSER_OBSERVABILITY.totalRuns++;
30820
- return RUN_BROWSER_OBSERVABILITY.totalRuns;
30821
- },
30822
- /**
30823
- * Returns current total run count.
30824
- */
30825
- getTotalRuns() {
30826
- return RUN_BROWSER_OBSERVABILITY.totalRuns;
30827
- },
30828
- /**
30829
- * Increments fallback counter and returns updated metrics.
30830
- */
30831
- incrementFallbackRunsAndGetMetrics() {
30832
- RUN_BROWSER_OBSERVABILITY.fallbackRuns++;
30833
- return {
30834
- fallbackRuns: RUN_BROWSER_OBSERVABILITY.fallbackRuns,
30835
- fallbackRate: RUN_BROWSER_OBSERVABILITY.totalRuns === 0
30836
- ? 0
30837
- : RUN_BROWSER_OBSERVABILITY.fallbackRuns / RUN_BROWSER_OBSERVABILITY.totalRuns,
30838
- };
30839
- },
30840
- /**
30841
- * Increments one error-code counter and returns the updated value.
30842
- */
30843
- incrementRunBrowserErrorCodeCounter(code) {
30844
- const currentValue = RUN_BROWSER_OBSERVABILITY.errorCodeCounts[code] || 0;
30845
- const nextValue = currentValue + 1;
30846
- RUN_BROWSER_OBSERVABILITY.errorCodeCounts[code] = nextValue;
30847
- return nextValue;
30848
- },
30849
- /**
30850
- * Writes one structured metric line for browser-tool observability.
30851
- */
30852
- logRunBrowserMetric(options) {
30853
- console.info('[run_browser][metric]', {
30854
- tool: 'run_browser',
30855
- mode: options.mode,
30856
- sessionId: options.sessionId,
30857
- event: options.event,
30858
- ...(options.payload || {}),
30859
- });
30860
- },
30861
- };
30862
-
30863
- /**
30864
- * Computes one compact preview of a fallback scrape payload.
30865
- */
30866
- function createContentPreview(content) {
30867
- const normalized = content.replace(/\s+/g, ' ').trim();
30868
- if (normalized.length <= 280) {
30869
- return normalized;
30870
- }
30871
- return `${normalized.slice(0, 277)}...`;
30872
- }
30873
- /**
30874
- * Payload and markdown formatters for `run_browser` outcomes.
30875
- *
30876
- * @private function of `run_browser`
30877
- */
30878
- const runBrowserResultFormatting = {
30879
- /**
30880
- * Produces one structured payload consumed by chat UI browser replay renderers.
30881
- */
30882
- createResultPayload(options) {
30883
- return {
30884
- schema: runBrowserConstants.resultSchema,
30885
- sessionId: options.sessionId,
30886
- mode: options.mode,
30887
- modeUsed: options.modeUsed,
30888
- initialUrl: options.initialUrl,
30889
- finalUrl: options.finalUrl,
30890
- finalTitle: options.finalTitle,
30891
- executedActions: options.executedActions,
30892
- artifacts: options.artifacts,
30893
- warning: options.warning,
30894
- error: options.error,
30895
- fallback: options.modeUsed === 'fallback' && options.fallbackContent !== null
30896
- ? {
30897
- scraper: 'fetch_url_content',
30898
- contentPreview: createContentPreview(options.fallbackContent),
30899
- }
30900
- : null,
30901
- timing: options.timing,
30902
- };
30903
- },
30904
- /**
30905
- * Produces a model-friendly markdown summary from browser execution artifacts.
30906
- */
30907
- formatSuccessResult(options) {
30908
- const { payload, snapshotPath } = options;
30909
- return spaceTrim$1((block) => {
30910
- var _a, _b, _c;
30911
- return `
30912
- # Browser run completed
30913
-
30914
- **Session:** ${payload.sessionId}
30915
- **Mode requested:** ${runBrowserRuntime.formatExecutionMode(payload.mode)}
30916
- **Mode used:** ${payload.modeUsed}
30917
- **Initial URL:** ${payload.initialUrl}
30918
- **Executed actions:** ${payload.executedActions.length}
30919
-
30920
- ## Final page
30921
-
30922
- - URL: ${payload.finalUrl || 'Unknown'}
30923
- - Title: ${payload.finalTitle || 'Unknown'}
30924
-
30925
- ## Timings
30926
-
30927
- - Connect: ${(_a = payload.timing.connectDurationMs) !== null && _a !== void 0 ? _a : 'Unknown'} ms
30928
- - Initial navigation: ${(_b = payload.timing.initialNavigationDurationMs) !== null && _b !== void 0 ? _b : 'Unknown'} ms
30929
- - Time to first byte: ${(_c = payload.timing.timeToFirstByteMs) !== null && _c !== void 0 ? _c : 'Unknown'} ms
30930
- - Total: ${payload.timing.totalDurationMs} ms
30931
-
30932
- ${payload.artifacts.length === 0
30933
- ? ''
30934
- : `
30935
- ## Visual replay
30936
-
30937
- ${payload.artifacts
30938
- .map((artifact, index) => {
30939
- const actionPart = artifact.actionSummary ? ` (${artifact.actionSummary})` : '';
30940
- return `- ${index + 1}. ${artifact.label}${actionPart}: ${artifact.path}`;
30941
- })
30942
- .join('\n')}
30943
- `}
30944
-
30945
- ${!snapshotPath
30946
- ? ''
30947
- : `
30948
- ## Final snapshot
30949
-
30950
- ${snapshotPath}
30951
- `}
30952
-
30953
- ## Playback payload
30954
-
30955
- \`\`\`json
30956
- ${JSON.stringify(payload, null, 2)}
30957
- \`\`\`
30958
-
30959
- ${block(payload.executedActions.length === 0
30960
- ? ''
30961
- : `
30962
- ## Action log
30963
-
30964
- ${payload.executedActions
30965
- .map((action, index) => `- ${index + 1}. ${JSON.stringify(action)}`)
30966
- .join('\n')}
30967
- `)}
30968
-
30969
- Note: Browser page has been automatically closed to free up resources.
30970
- `;
30971
- });
30972
- },
30973
- /**
30974
- * Produces a model-friendly markdown payload when fallback scraping is used.
30975
- */
30976
- formatFallbackResult(options) {
30977
- const { payload, fallbackContent, requestedActions } = options;
30978
- return spaceTrim$1(`
30979
- # Browser run completed with fallback
30980
-
30981
- **Session:** ${payload.sessionId}
30982
- **Mode requested:** ${runBrowserRuntime.formatExecutionMode(payload.mode)}
30983
- **Mode used:** ${payload.modeUsed}
30984
- **Initial URL:** ${payload.initialUrl}
30985
- **Requested actions:** ${requestedActions}
30986
- **Executed actions:** ${payload.executedActions.length}
30987
- **Warning:** ${payload.warning || runBrowserConstants.fallbackDynamicContentWarning}
30988
-
30989
- ## Extracted content
30990
-
30991
- ${fallbackContent}
30992
-
30993
- ## Playback payload
30994
-
30995
- \`\`\`json
30996
- ${JSON.stringify(payload, null, 2)}
30997
- \`\`\`
30998
- `);
30999
- },
31000
- /**
31001
- * Produces a model-friendly markdown error payload from browser execution failures.
31002
- */
31003
- formatErrorResult(options) {
31004
- const { payload } = options;
31005
- const toolError = payload.error;
31006
- const suggestedNextSteps = (toolError === null || toolError === void 0 ? void 0 : toolError.suggestedNextSteps) || [];
31007
- return spaceTrim$1(`
31008
- # Browser run failed
31009
-
31010
- **Session:** ${payload.sessionId}
31011
- **Mode requested:** ${runBrowserRuntime.formatExecutionMode(payload.mode)}
31012
- **Mode used:** ${payload.modeUsed}
31013
- **Initial URL:** ${payload.initialUrl}
31014
- **Error code:** ${(toolError === null || toolError === void 0 ? void 0 : toolError.code) || runBrowserConstants.unknownErrorCode}
31015
- **Error:** ${(toolError === null || toolError === void 0 ? void 0 : toolError.message) || 'Unknown browser tool error'}
31016
-
31017
- ${suggestedNextSteps.length === 0
31018
- ? ''
31019
- : `
31020
- ## Suggested next steps
31021
-
31022
- ${suggestedNextSteps.map((step) => `- ${step}`).join('\n')}
31023
- `}
31024
-
31025
- ## Playback payload
31026
-
31027
- \`\`\`json
31028
- ${JSON.stringify(payload, null, 2)}
31029
- \`\`\`
31030
-
31031
- The browser tool could not complete the requested actions.
31032
- `);
31033
- },
31034
- };
31035
-
31036
- /**
31037
- * Attempts to locate the specified application on a Linux system using the 'which' command.
31038
- * Returns the path to the executable if found, or null otherwise.
31039
- *
31040
- * @private within the repository
31041
- */
31042
- async function locateAppOnLinux({ linuxWhich, }) {
31043
- try {
31044
- const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
31045
- return result.trim();
31046
- }
31047
- catch (error) {
31048
- assertsError(error);
31049
- return null;
31050
- }
31051
- }
31052
- /**
31053
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
31054
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
31055
- */
31056
-
31057
- /**
31058
- * Checks if the file is executable
31059
- *
31060
- * @private within the repository
31061
- */
31062
- async function isExecutable(path, fs) {
31063
- try {
31064
- await fs.access(path, fs.constants.X_OK);
31065
- return true;
31066
- }
31067
- catch (error) {
31068
- return false;
31069
- }
31070
- }
31071
- /**
31072
- * Note: Not [~🟢~] because it is not directly dependent on `fs
31073
- * TODO: [🖇] What about symlinks?
31074
- */
31075
-
31076
- // Note: Module `userhome` has no types available, so it is imported using `require`
31077
- // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
31078
- // eslint-disable-next-line @typescript-eslint/no-var-requires
31079
- const userhome = require('userhome');
31080
- /**
31081
- * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
31082
- * Returns the path to the executable if found, or null otherwise.
31083
- *
31084
- * @private within the repository
31085
- */
31086
- async function locateAppOnMacOs({ macOsName, }) {
31087
- try {
31088
- const toExec = `/Contents/MacOS/${macOsName}`;
31089
- const regPath = `/Applications/${macOsName}.app` + toExec;
31090
- const altPath = userhome(regPath.slice(1));
31091
- if (await isExecutable(regPath, $provideFilesystemForNode())) {
31092
- return regPath;
31093
- }
31094
- else if (await isExecutable(altPath, $provideFilesystemForNode())) {
31095
- return altPath;
31096
- }
31097
- const result = await $execCommand({
31098
- crashOnError: true,
31099
- command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
31100
- });
31101
- return result.trim() + toExec;
31102
- }
31103
- catch (error) {
31104
- assertsError(error);
31105
- return null;
31106
- }
31107
- }
31108
- /**
31109
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
31110
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
31111
- */
31112
-
31113
- /**
31114
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
31115
- * Returns the path to the executable if found, or null otherwise.
31116
- *
31117
- * @private within the repository
31118
- */
31119
- async function locateAppOnWindows({ appName, windowsSuffix, }) {
31120
- try {
31121
- const prefixes = [
31122
- process.env.LOCALAPPDATA,
31123
- join(process.env.LOCALAPPDATA || '', 'Programs'),
31124
- process.env.PROGRAMFILES,
31125
- process.env['PROGRAMFILES(X86)'],
31126
- ];
31127
- for (const prefix of prefixes) {
31128
- const path = prefix + windowsSuffix;
31129
- if (await isExecutable(path, $provideFilesystemForNode())) {
31130
- return path;
31131
- }
31132
- }
31133
- throw new Error(`Can not locate app ${appName} on Windows.`);
31134
- }
31135
- catch (error) {
31136
- assertsError(error);
31137
- return null;
31138
- }
31139
- }
31140
- /**
31141
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
31142
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
31143
- */
31144
-
31145
- /**
31146
- * Locates an application on the system
31147
- *
31148
- * @private within the repository
31149
- */
31150
- function locateApp(options) {
31151
- if (!$isRunningInNode()) {
31152
- throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
31153
- }
31154
- const { appName, linuxWhich, windowsSuffix, macOsName } = options;
31155
- if (process.platform === 'win32') {
31156
- if (windowsSuffix) {
31157
- return locateAppOnWindows({ appName, windowsSuffix });
31158
- }
31159
- else {
31160
- throw new Error(`${appName} is not available on Windows.`);
31161
- }
31162
- }
31163
- else if (process.platform === 'darwin') {
31164
- if (macOsName) {
31165
- return locateAppOnMacOs({ macOsName });
31166
- }
31167
- else {
31168
- throw new Error(`${appName} is not available on macOS.`);
31169
- }
31170
- }
31171
- else {
31172
- if (linuxWhich) {
31173
- return locateAppOnLinux({ linuxWhich });
31174
- }
31175
- else {
31176
- throw new Error(`${appName} is not available on Linux.`);
31177
- }
31178
- }
31179
- }
31180
- /**
31181
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
31182
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
31183
- */
31184
-
31185
- /**
31186
- * @@@
31187
- *
31188
- * @private within the repository
31189
- */
31190
- function locateChrome() {
31191
- return locateApp({
31192
- appName: 'Chrome',
31193
- linuxWhich: 'google-chrome',
31194
- windowsSuffix: '\\Google\\Chrome\\Application\\chrome.exe',
31195
- macOsName: 'Google Chrome',
31196
- });
31197
- }
31198
-
31199
- /**
31200
- * Creates one standard abort error for cancelled retry loops.
31201
- *
31202
- * @private utility for Agents Server runtime retries
31203
- */
31204
- function createAbortError$1() {
31205
- const error = new Error('Operation was aborted.');
31206
- error.name = 'AbortError';
31207
- return error;
31208
- }
31209
- /**
31210
- * Throws when the supplied signal is already aborted.
31211
- *
31212
- * @private utility for Agents Server runtime retries
31213
- */
31214
- function assertNotAborted(signal) {
31215
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
31216
- throw createAbortError$1();
31217
- }
31218
- }
31219
- /**
31220
- * Waits for a duration while respecting cancellation.
31221
- *
31222
- * @private utility for Agents Server runtime retries
31223
- */
31224
- async function sleepWithAbort(delayMs, signal) {
31225
- if (delayMs <= 0) {
31226
- assertNotAborted(signal);
31227
- return;
31228
- }
31229
- await new Promise((resolve, reject) => {
31230
- const timeout = setTimeout(() => {
31231
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
31232
- resolve();
31233
- }, delayMs);
31234
- const onAbort = () => {
31235
- clearTimeout(timeout);
31236
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
31237
- reject(createAbortError$1());
31238
- };
31239
- signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
31240
- });
31241
- }
31242
- /**
31243
- * Resolves the retry wait duration for one failed attempt.
31244
- *
31245
- * @private utility for Agents Server runtime retries
31246
- */
31247
- function resolveBackoffDelayMs(options) {
31248
- const exponentialDelay = options.initialDelayMs * Math.pow(options.backoffFactor, Math.max(0, options.attempt - 1));
31249
- const boundedDelay = Math.min(exponentialDelay, options.maxDelayMs);
31250
- const jitterDelay = boundedDelay * options.jitterRatio * Math.max(0, options.random());
31251
- return Math.max(0, Math.round(boundedDelay + jitterDelay));
31252
- }
31253
- /**
31254
- * Retries one async operation with exponential backoff and jitter.
31255
- *
31256
- * @private utility for Agents Server runtime retries
31257
- */
31258
- async function retryWithBackoff(operation, options) {
31259
- var _a, _b, _c;
31260
- const startedAt = Date.now();
31261
- const totalAttempts = Math.max(1, options.retries + 1);
31262
- const random = (_a = options.random) !== null && _a !== void 0 ? _a : Math.random;
31263
- const sleep = (_b = options.sleep) !== null && _b !== void 0 ? _b : sleepWithAbort;
31264
- for (let attempt = 1; attempt <= totalAttempts; attempt++) {
31265
- assertNotAborted(options.signal);
31266
- try {
31267
- const value = await operation(attempt);
31268
- return {
31269
- value,
31270
- attempts: attempt,
31271
- durationMs: Date.now() - startedAt,
31272
- };
31273
- }
31274
- catch (error) {
31275
- const isLastAttempt = attempt >= totalAttempts;
31276
- const isRetryable = options.shouldRetry ? options.shouldRetry(error, attempt) : true;
31277
- if (isLastAttempt || !isRetryable) {
31278
- throw error;
31279
- }
31280
- const delayMs = resolveBackoffDelayMs({
31281
- attempt,
31282
- initialDelayMs: options.initialDelayMs,
31283
- maxDelayMs: options.maxDelayMs,
31284
- backoffFactor: options.backoffFactor,
31285
- jitterRatio: options.jitterRatio,
31286
- random,
31287
- });
31288
- (_c = options.onRetry) === null || _c === void 0 ? void 0 : _c.call(options, {
31289
- attempt,
31290
- retries: options.retries,
31291
- delayMs,
31292
- error,
31293
- });
31294
- await sleep(delayMs, options.signal);
31295
- }
31296
- }
31297
- throw new Error('Retry loop exited unexpectedly.');
31298
- }
31299
-
31300
- const DEFAULT_BROWSER_USER_DATA_DIR = join(tmpdir(), 'promptbook', 'browser', 'user-data');
31301
- /**
31302
- * Default remote browser connect timeout in milliseconds.
31303
- */
31304
- const DEFAULT_REMOTE_CONNECT_TIMEOUT_MS = 10000;
31305
- /**
31306
- * Default retry count for remote browser connection establishment.
31307
- */
31308
- const DEFAULT_REMOTE_CONNECT_RETRIES = 2;
31309
- /**
31310
- * Default initial retry delay for remote browser connection.
31311
- */
31312
- const DEFAULT_REMOTE_CONNECT_BACKOFF_INITIAL_MS = 250;
31313
- /**
31314
- * Default maximum retry delay for remote browser connection.
31315
- */
31316
- const DEFAULT_REMOTE_CONNECT_BACKOFF_MAX_MS = 1000;
31317
- /**
31318
- * Default exponential multiplier for remote browser retry delay.
31319
- */
31320
- const DEFAULT_REMOTE_CONNECT_BACKOFF_FACTOR = 4;
31321
- /**
31322
- * Default retry jitter ratio for remote browser connection.
31323
- */
31324
- const DEFAULT_REMOTE_CONNECT_JITTER_RATIO = 0.2;
31325
- /**
31326
- * In-memory metrics counters for remote browser connect attempts.
31327
- */
31328
- const REMOTE_BROWSER_CONNECT_METRICS = {
31329
- success: 0,
31330
- failure: 0,
31331
- };
31332
- /**
31333
- * Reads a positive integer from environment variables with a fallback default.
31334
- */
31335
- function resolvePositiveIntFromEnv(variableName, defaultValue) {
31336
- const rawValue = process.env[variableName];
31337
- if (!rawValue || !rawValue.trim()) {
31338
- return defaultValue;
31339
- }
31340
- const parsed = Number.parseInt(rawValue.trim(), 10);
31341
- if (!Number.isFinite(parsed) || parsed <= 0) {
31342
- return defaultValue;
31343
- }
31344
- return parsed;
31345
- }
31346
- /**
31347
- * Reads a positive number from environment variables with a fallback default.
31348
- */
31349
- function resolvePositiveNumberFromEnv(variableName, defaultValue) {
31350
- const rawValue = process.env[variableName];
31351
- if (!rawValue || !rawValue.trim()) {
31352
- return defaultValue;
31353
- }
31354
- const parsed = Number.parseFloat(rawValue.trim());
31355
- if (!Number.isFinite(parsed) || parsed <= 0) {
31356
- return defaultValue;
31357
- }
31358
- return parsed;
31359
- }
31360
- /**
31361
- * Reads a non-negative integer from environment variables with a fallback default.
31362
- */
31363
- function resolveNonNegativeIntFromEnv(variableName, defaultValue) {
31364
- const rawValue = process.env[variableName];
31365
- if (!rawValue || !rawValue.trim()) {
31366
- return defaultValue;
31367
- }
31368
- const parsed = Number.parseInt(rawValue.trim(), 10);
31369
- if (!Number.isFinite(parsed) || parsed < 0) {
31370
- return defaultValue;
31371
- }
31372
- return parsed;
31373
- }
31374
- /**
31375
- * Reads a non-negative number from environment variables with a fallback default.
31376
- */
31377
- function resolveNonNegativeNumberFromEnv(variableName, defaultValue) {
31378
- const rawValue = process.env[variableName];
31379
- if (!rawValue || !rawValue.trim()) {
31380
- return defaultValue;
31381
- }
31382
- const parsed = Number.parseFloat(rawValue.trim());
31383
- if (!Number.isFinite(parsed) || parsed < 0) {
31384
- return defaultValue;
31385
- }
31386
- return parsed;
31387
- }
31388
- /**
31389
- * Creates one standard abort error.
31390
- */
31391
- function createAbortError() {
31392
- const error = new Error('Browser connection request was aborted.');
31393
- error.name = 'AbortError';
31394
- return error;
31395
- }
31396
- /**
31397
- * Provides browser context instances with support for both local and remote browser connections.
31398
- *
31399
- * This provider manages browser lifecycle and supports:
31400
- * - Local mode: Launches a persistent Chromium context on the same machine
31401
- * - Remote mode: Connects to a remote Playwright browser via WebSocket
31402
- *
31403
- * The remote mode is useful for environments like Vercel where running a full browser
31404
- * is not possible due to resource constraints.
31405
- *
31406
- * @private internal utility for Agents Server browser tools
31407
- */
31408
- class BrowserConnectionProvider {
31409
- /**
31410
- * Creates a new BrowserConnectionProvider.
31411
- *
31412
- * @param options - Provider options
31413
- * @param options.isVerbose - Enable verbose logging
31414
- */
31415
- constructor(options = {}) {
31416
- var _a, _b, _c, _d, _e, _f, _g, _h;
31417
- this.browserContext = null;
31418
- this.connectionMode = null;
31419
- this.isVerbose = (_a = options.isVerbose) !== null && _a !== void 0 ? _a : false;
31420
- this.remoteConnectTimeoutMs =
31421
- (_b = options.remoteConnectTimeoutMs) !== null && _b !== void 0 ? _b : resolvePositiveIntFromEnv('RUN_BROWSER_CONNECT_TIMEOUT_MS', DEFAULT_REMOTE_CONNECT_TIMEOUT_MS);
31422
- this.remoteConnectRetries =
31423
- (_c = options.remoteConnectRetries) !== null && _c !== void 0 ? _c : resolveNonNegativeIntFromEnv('RUN_BROWSER_CONNECT_RETRIES', DEFAULT_REMOTE_CONNECT_RETRIES);
31424
- this.remoteConnectBackoffInitialMs =
31425
- (_d = options.remoteConnectBackoffInitialMs) !== null && _d !== void 0 ? _d : resolvePositiveIntFromEnv('RUN_BROWSER_CONNECT_BACKOFF_INITIAL_MS', DEFAULT_REMOTE_CONNECT_BACKOFF_INITIAL_MS);
31426
- this.remoteConnectBackoffMaxMs =
31427
- (_e = options.remoteConnectBackoffMaxMs) !== null && _e !== void 0 ? _e : resolvePositiveIntFromEnv('RUN_BROWSER_CONNECT_BACKOFF_MAX_MS', DEFAULT_REMOTE_CONNECT_BACKOFF_MAX_MS);
31428
- this.remoteConnectBackoffFactor =
31429
- (_f = options.remoteConnectBackoffFactor) !== null && _f !== void 0 ? _f : resolvePositiveNumberFromEnv('RUN_BROWSER_CONNECT_BACKOFF_FACTOR', DEFAULT_REMOTE_CONNECT_BACKOFF_FACTOR);
31430
- this.remoteConnectJitterRatio =
31431
- (_g = options.remoteConnectJitterRatio) !== null && _g !== void 0 ? _g : resolveNonNegativeNumberFromEnv('RUN_BROWSER_CONNECT_JITTER_RATIO', DEFAULT_REMOTE_CONNECT_JITTER_RATIO);
31432
- this.random = (_h = options.random) !== null && _h !== void 0 ? _h : Math.random;
31433
- this.sleep = options.sleep;
31434
- }
31435
- /**
31436
- * Gets a browser context, creating a new one if needed.
31437
- *
31438
- * This method automatically determines whether to use local or remote browser
31439
- * based on the REMOTE_BROWSER_URL environment variable.
31440
- *
31441
- * @returns Browser context instance
31442
- */
31443
- async getBrowserContext(options = {}) {
31444
- var _a;
31445
- if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
31446
- throw createAbortError();
31447
- }
31448
- // Check if we have a cached connection that's still valid
31449
- if (this.browserContext !== null && this.isBrowserContextAlive(this.browserContext)) {
31450
- return this.browserContext;
31451
- }
31452
- // Determine connection mode from configuration
31453
- const mode = this.resolveConnectionMode();
31454
- this.connectionMode = mode;
31455
- if (this.isVerbose) {
31456
- console.info('[BrowserConnectionProvider] Creating new browser context', {
31457
- mode: mode.type,
31458
- wsEndpoint: mode.type === 'remote' ? mode.wsEndpoint : undefined,
31459
- });
31460
- }
31461
- // Create new browser context based on mode
31462
- if (mode.type === 'local') {
31463
- this.browserContext = await this.createLocalBrowserContext();
31464
- }
31465
- else {
31466
- this.browserContext = await this.createRemoteBrowserContext(mode.wsEndpoint, options);
31467
- }
31468
- return this.browserContext;
31469
- }
31470
- /**
31471
- * Closes all pages in the current browser context.
31472
- *
31473
- * This method is useful for cleanup between agent tasks without closing
31474
- * the entire browser instance.
31475
- */
31476
- async closeAllPages() {
31477
- if (!this.browserContext) {
31478
- return;
31479
- }
31480
- try {
31481
- const pages = this.browserContext.pages();
31482
- if (this.isVerbose) {
31483
- console.info('[BrowserConnectionProvider] Closing all pages', {
31484
- pageCount: pages.length,
31485
- });
31486
- }
31487
- await Promise.all(pages.map((page) => page.close().catch((error) => {
31488
- console.error('[BrowserConnectionProvider] Failed to close page', { error });
31489
- })));
31490
- }
31491
- catch (error) {
31492
- console.error('[BrowserConnectionProvider] Error closing pages', { error });
31493
- }
31494
- }
31495
- /**
31496
- * Closes the browser context and disconnects from the browser.
31497
- *
31498
- * This should be called when the browser is no longer needed to free up resources.
31499
- * For local mode, this closes the browser process. For remote mode, it disconnects
31500
- * from the remote browser but doesn't shut down the remote server.
31501
- */
31502
- async close() {
31503
- var _a;
31504
- if (!this.browserContext) {
31505
- return;
31506
- }
31507
- try {
31508
- if (this.isVerbose) {
31509
- console.info('[BrowserConnectionProvider] Closing browser context', {
31510
- mode: (_a = this.connectionMode) === null || _a === void 0 ? void 0 : _a.type,
31511
- });
31512
- }
31513
- await this.browserContext.close();
31514
- this.browserContext = null;
31515
- this.connectionMode = null;
31516
- }
31517
- catch (error) {
31518
- console.error('[BrowserConnectionProvider] Error closing browser context', { error });
31519
- // Reset state even if close fails
31520
- this.browserContext = null;
31521
- this.connectionMode = null;
31522
- }
31523
- }
31524
- /**
31525
- * Checks if a browser context is still alive and connected.
31526
- *
31527
- * @param context - Browser context to check
31528
- * @returns True if the context is connected and usable
31529
- */
31530
- isBrowserContextAlive(context) {
31531
- try {
31532
- const browser = context.browser();
31533
- return browser !== null && browser.isConnected();
31534
- }
31535
- catch (_a) {
31536
- return false;
31537
- }
31538
- }
31539
- /**
31540
- * Determines whether to use local or remote browser based on configuration.
31541
- *
31542
- * @returns Connection mode configuration
31543
- */
31544
- resolveConnectionMode() {
31545
- const remoteBrowserUrl = REMOTE_BROWSER_URL;
31546
- if (remoteBrowserUrl && remoteBrowserUrl.trim().length > 0) {
31547
- return {
31548
- type: 'remote',
31549
- wsEndpoint: remoteBrowserUrl.trim(),
31550
- };
31551
- }
31552
- return { type: 'local' };
31553
- }
31554
- /**
31555
- * Creates a local browser context using persistent Chromium.
31556
- *
31557
- * @returns Local browser context
31558
- */
31559
- async createLocalBrowserContext() {
31560
- if (this.isVerbose) {
31561
- console.info('[BrowserConnectionProvider] Launching local browser context');
31562
- }
31563
- const userDataDir = join(DEFAULT_BROWSER_USER_DATA_DIR, 'run-browser');
31564
- await mkdir(userDataDir, { recursive: true });
31565
- const launchOptions = {
31566
- headless: false,
31567
- args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
31568
- };
31569
- try {
31570
- const chromePath = await locateChrome();
31571
- launchOptions.executablePath = chromePath;
31572
- }
31573
- catch (error) {
31574
- if (this.isVerbose) {
31575
- console.warn('[BrowserConnectionProvider] Could not locate system Chrome; using Playwright bundled Chromium', {
31576
- error: error instanceof Error ? error.message : String(error),
31577
- });
31578
- }
31579
- }
31580
- return await chromium.launchPersistentContext(userDataDir, launchOptions);
31581
- }
31582
- /**
31583
- * Creates a remote browser context by connecting to a Playwright server.
31584
- *
31585
- * @param wsEndpoint - WebSocket endpoint of the remote Playwright server
31586
- * @returns Remote browser context
31587
- */
31588
- async createRemoteBrowserContext(wsEndpoint, options) {
31589
- const endpointDebug = sanitizeRemoteBrowserEndpoint(wsEndpoint);
31590
- const startedAt = Date.now();
31591
- if (this.isVerbose) {
31592
- console.info('[BrowserConnectionProvider] Connecting to remote browser', {
31593
- endpoint: endpointDebug,
31594
- connectTimeoutMs: this.remoteConnectTimeoutMs,
31595
- retries: this.remoteConnectRetries,
31596
- });
31597
- }
31598
- let attempts = 0;
31599
- try {
31600
- const connectResult = await retryWithBackoff(async (attempt) => {
31601
- attempts = attempt;
31602
- return await chromium.connect(wsEndpoint, {
31603
- timeout: this.remoteConnectTimeoutMs,
31604
- });
31605
- }, {
31606
- retries: this.remoteConnectRetries,
31607
- initialDelayMs: this.remoteConnectBackoffInitialMs,
31608
- maxDelayMs: this.remoteConnectBackoffMaxMs,
31609
- backoffFactor: this.remoteConnectBackoffFactor,
31610
- jitterRatio: this.remoteConnectJitterRatio,
31611
- signal: options.signal,
31612
- shouldRetry: (error) => isRemoteBrowserInfrastructureError(error),
31613
- onRetry: ({ attempt, delayMs, error }) => {
31614
- console.warn('[run_browser][retry]', {
31615
- tool: 'run_browser',
31616
- mode: 'remote-browser',
31617
- sessionId: options.sessionId || null,
31618
- event: 'remote_browser_connect_retry',
31619
- attempt,
31620
- delayMs,
31621
- endpoint: endpointDebug,
31622
- errorCode: extractNetworkErrorCode(error),
31623
- error: getErrorMessage(error),
31624
- });
31625
- },
31626
- random: this.random,
31627
- sleep: this.sleep,
31628
- });
31629
- const browser = connectResult.value;
31630
- // For remote connections, we need to create a new context
31631
- // Note: Remote browsers don't support persistent contexts
31632
- const context = await browser.newContext();
31633
- REMOTE_BROWSER_CONNECT_METRICS.success++;
31634
- console.info('[run_browser][metric]', {
31635
- tool: 'run_browser',
31636
- mode: 'remote-browser',
31637
- sessionId: options.sessionId || null,
31638
- event: 'remote_browser_connect_success',
31639
- attempts: connectResult.attempts,
31640
- connectDurationMs: connectResult.durationMs,
31641
- endpoint: endpointDebug,
31642
- counter: REMOTE_BROWSER_CONNECT_METRICS.success,
31643
- });
31644
- if (this.isVerbose) {
31645
- console.info('[BrowserConnectionProvider] Successfully connected to remote browser');
31646
- }
31647
- return context;
31648
- }
31649
- catch (error) {
31650
- REMOTE_BROWSER_CONNECT_METRICS.failure++;
31651
- const durationMs = Date.now() - startedAt;
31652
- const remoteInfraUnavailable = isRemoteBrowserInfrastructureError(error);
31653
- if (remoteInfraUnavailable) {
31654
- const remoteBrowserUnavailableError = new RemoteBrowserUnavailableError({
31655
- message: `Remote browser is unavailable. Could not establish a websocket connection.`,
31656
- debug: {
31657
- endpoint: endpointDebug,
31658
- attempts: Math.max(1, attempts),
31659
- connectTimeoutMs: this.remoteConnectTimeoutMs,
31660
- durationMs,
31661
- networkErrorCode: extractNetworkErrorCode(error),
31662
- originalMessage: getErrorMessage(error),
31663
- },
31664
- cause: error,
31665
- });
31666
- console.warn('[run_browser][metric]', {
31667
- tool: 'run_browser',
31668
- mode: 'remote-browser',
31669
- sessionId: options.sessionId || null,
31670
- event: 'remote_browser_connect_failure',
31671
- errorCode: remoteBrowserUnavailableError.code,
31672
- attempts: Math.max(1, attempts),
31673
- connectDurationMs: durationMs,
31674
- endpoint: endpointDebug,
31675
- counter: REMOTE_BROWSER_CONNECT_METRICS.failure,
31676
- });
31677
- throw remoteBrowserUnavailableError;
31678
- }
31679
- console.error('[run_browser][metric]', {
31680
- tool: 'run_browser',
31681
- mode: 'remote-browser',
31682
- sessionId: options.sessionId || null,
31683
- event: 'remote_browser_connect_failure',
31684
- errorCode: 'REMOTE_BROWSER_CONNECT_ERROR',
31685
- attempts: Math.max(1, attempts),
31686
- connectDurationMs: durationMs,
31687
- endpoint: endpointDebug,
31688
- error: getErrorMessage(error),
31689
- counter: REMOTE_BROWSER_CONNECT_METRICS.failure,
31690
- });
31691
- throw error;
31692
- }
31693
- }
31694
- }
31695
-
31696
- /**
31697
- * Singleton instance of the browser connection provider.
31698
- *
31699
- * @private internal cache for `$provideBrowserForServer`
31700
- */
31701
- let browserProvider = null;
31702
- /**
31703
- * Provides a browser context for server-side operations, with caching to reuse instances.
31704
- *
31705
- * This function supports both local and remote browser connections based on environment configuration.
31706
- * Use REMOTE_BROWSER_URL environment variable to configure a remote Playwright server.
31707
- *
31708
- * @param options - Optional runtime request options used for cancellation and logging context.
31709
- * @returns Browser context instance
31710
- */
31711
- async function $provideBrowserForServer(options = {}) {
31712
- if (browserProvider === null) {
31713
- browserProvider = new BrowserConnectionProvider({ isVerbose: false });
31714
- }
31715
- return await browserProvider.getBrowserContext(options);
31716
- }
31717
- /**
31718
- * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
31719
- */
31720
-
31721
- /**
31722
- * Attempts to compute time-to-first-byte from Playwright response timing.
31723
- */
31724
- function resolveTimeToFirstByteMs(response) {
31725
- if (!response) {
31726
- return null;
31727
- }
31728
- try {
31729
- const timing = response.request().timing();
31730
- if (typeof (timing === null || timing === void 0 ? void 0 : timing.responseStart) === 'number' &&
31731
- typeof (timing === null || timing === void 0 ? void 0 : timing.startTime) === 'number' &&
31732
- timing.responseStart >= timing.startTime) {
31733
- return Math.round(timing.responseStart - timing.startTime);
31734
- }
31735
- }
31736
- catch (_a) {
31737
- return null;
31738
- }
31739
- return null;
31740
- }
31741
- /**
31742
- * Page open, action normalization and action execution helpers for `run_browser`.
31743
- *
31744
- * @private function of `run_browser`
31745
- */
31746
- const runBrowserWorkflow = {
31747
- /**
31748
- * Opens a new browser page and navigates to the requested URL.
31749
- */
31750
- async openPageWithUrl(options) {
31751
- runBrowserErrorHandling.assertNotAborted(options.signal, options.sessionId);
31752
- const connectStartedAt = Date.now();
31753
- const browserContext = await $provideBrowserForServer({
31754
- signal: options.signal,
31755
- sessionId: options.sessionId,
31756
- });
31757
- const connectDurationMs = Date.now() - connectStartedAt;
31758
- const page = await browserContext.newPage();
31759
- page.setDefaultNavigationTimeout(options.timeouts.navigationTimeoutMs);
31760
- page.setDefaultTimeout(options.timeouts.actionTimeoutMs);
31761
- const navigationStartedAt = Date.now();
31762
- try {
31763
- const navigationResponse = await page.goto(options.url, {
31764
- waitUntil: 'domcontentloaded',
31765
- timeout: options.timeouts.navigationTimeoutMs,
31766
- });
31767
- return {
31768
- page,
31769
- connectDurationMs,
31770
- initialNavigationDurationMs: Date.now() - navigationStartedAt,
31771
- timeToFirstByteMs: resolveTimeToFirstByteMs(navigationResponse),
31772
- };
31773
- }
31774
- catch (error) {
31775
- throw runBrowserErrorHandling.createRunBrowserNavigationError({
31776
- message: `Failed to navigate to \`${options.url}\`.`,
31777
- debug: {
31778
- phase: 'initial-navigation',
31779
- url: options.url,
31780
- navigationTimeoutMs: options.timeouts.navigationTimeoutMs,
31781
- },
31782
- cause: error,
31783
- });
31784
- }
31785
- },
31786
- /**
31787
- * Validates and normalizes browser actions received from the model.
31788
- */
31789
- normalizeActions(actions) {
31790
- if (!actions || actions.length === 0) {
31791
- return [];
31792
- }
31793
- return actions.map((action, index) => this.normalizeAction(action, index));
31794
- },
31795
- /**
31796
- * Validates and normalizes a single action.
31797
- */
31798
- normalizeAction(action, index) {
31799
- var _a, _b, _c;
31800
- switch (action.type) {
31801
- case 'navigate': {
31802
- const url = String(action.value || '').trim();
31803
- if (!url) {
31804
- throw runBrowserErrorHandling.createRunBrowserValidationError({
31805
- message: spaceTrim$1(`Action ${index + 1}: \`navigate\` requires non-empty \`value\` URL.`),
31806
- debug: {
31807
- actionIndex: index + 1,
31808
- actionType: action.type,
31809
- },
31810
- });
31811
- }
31812
- return { type: 'navigate', url };
31813
- }
31814
- case 'click': {
31815
- const selector = String(action.selector || '').trim();
31816
- if (!selector) {
31817
- throw runBrowserErrorHandling.createRunBrowserValidationError({
31818
- message: spaceTrim$1(`Action ${index + 1}: \`click\` requires non-empty \`selector\`.`),
31819
- debug: {
31820
- actionIndex: index + 1,
31821
- actionType: action.type,
31822
- },
31823
- });
31824
- }
31825
- return { type: 'click', selector };
31826
- }
31827
- case 'type': {
31828
- const selector = String(action.selector || '').trim();
31829
- if (!selector) {
31830
- throw runBrowserErrorHandling.createRunBrowserValidationError({
31831
- message: spaceTrim$1(`Action ${index + 1}: \`type\` requires non-empty \`selector\`.`),
31832
- debug: {
31833
- actionIndex: index + 1,
31834
- actionType: action.type,
31835
- },
31836
- });
31837
- }
31838
- const text = String((_a = action.value) !== null && _a !== void 0 ? _a : '');
31839
- return { type: 'type', selector, text };
31840
- }
31841
- case 'wait': {
31842
- const requestedValue = Number.parseInt(String((_b = action.value) !== null && _b !== void 0 ? _b : runBrowserConstants.defaultWaitMs), 10);
31843
- const milliseconds = Number.isFinite(requestedValue)
31844
- ? Math.min(Math.max(requestedValue, 1), runBrowserConstants.maxWaitMs)
31845
- : runBrowserConstants.defaultWaitMs;
31846
- return { type: 'wait', milliseconds };
31847
- }
31848
- case 'scroll': {
31849
- const requestedValue = Number.parseInt(String((_c = action.value) !== null && _c !== void 0 ? _c : runBrowserConstants.defaultScrollPixels), 10);
31850
- const pixels = Number.isFinite(requestedValue) ? requestedValue : runBrowserConstants.defaultScrollPixels;
31851
- const rawSelector = String(action.selector || '').trim();
31852
- return { type: 'scroll', selector: rawSelector || null, pixels };
31853
- }
31854
- }
31855
- },
31856
- /**
31857
- * Executes one normalized browser action on a Playwright page.
31858
- */
31859
- async executeAction(options) {
31860
- const { page, action, actionIndex, timeouts, signal } = options;
31861
- runBrowserErrorHandling.assertNotAborted(signal, `action-${actionIndex}`);
31862
- try {
31863
- switch (action.type) {
31864
- case 'navigate':
31865
- await page.goto(action.url, {
31866
- waitUntil: 'domcontentloaded',
31867
- timeout: timeouts.navigationTimeoutMs,
31868
- });
31869
- return;
31870
- case 'click':
31871
- await page.locator(action.selector).first().click({ timeout: timeouts.actionTimeoutMs });
31872
- return;
31873
- case 'type':
31874
- await page.locator(action.selector).first().fill(action.text, { timeout: timeouts.actionTimeoutMs });
31875
- return;
31876
- case 'wait':
31877
- if (action.milliseconds > timeouts.actionTimeoutMs) {
31878
- throw runBrowserErrorHandling.createRunBrowserActionError({
31879
- message: `Action ${actionIndex}: \`wait\` exceeds action timeout (${timeouts.actionTimeoutMs}ms).`,
31880
- debug: {
31881
- actionIndex,
31882
- action,
31883
- actionTimeoutMs: timeouts.actionTimeoutMs,
31884
- },
31885
- });
31886
- }
31887
- await page.waitForTimeout(action.milliseconds);
31888
- return;
31889
- case 'scroll':
31890
- if (action.selector) {
31891
- await page
31892
- .locator(action.selector)
31893
- .first()
31894
- .scrollIntoViewIfNeeded({ timeout: timeouts.actionTimeoutMs });
31895
- }
31896
- await page.mouse.wheel(0, action.pixels);
31897
- return;
31898
- }
31899
- }
31900
- catch (error) {
31901
- if (runBrowserErrorHandling.isTaggedRunBrowserError(error)) {
31902
- throw error;
31903
- }
31904
- if (action.type === 'navigate') {
31905
- throw runBrowserErrorHandling.createRunBrowserNavigationError({
31906
- message: `Action ${actionIndex}: failed to navigate to \`${action.url}\`.`,
31907
- debug: {
31908
- actionIndex,
31909
- action,
31910
- navigationTimeoutMs: timeouts.navigationTimeoutMs,
31911
- },
31912
- cause: error,
31913
- });
31914
- }
31915
- throw runBrowserErrorHandling.createRunBrowserActionError({
31916
- message: `Action ${actionIndex}: failed to execute \`${action.type}\`.`,
31917
- debug: {
31918
- actionIndex,
31919
- action,
31920
- actionTimeoutMs: timeouts.actionTimeoutMs,
31921
- },
31922
- cause: error,
31923
- });
31924
- }
31925
- },
31926
- };
31927
-
31928
- /**
31929
- * Summarizes one normalized browser action in user-facing language.
31930
- */
31931
- function formatRunBrowserActionSummary(action) {
31932
- switch (action.type) {
31933
- case 'navigate':
31934
- return `Navigate to ${action.url}`;
31935
- case 'click':
31936
- return `Click ${action.selector}`;
31937
- case 'type':
31938
- return `Type into ${action.selector}`;
31939
- case 'wait':
31940
- return `Wait ${action.milliseconds}ms`;
31941
- case 'scroll':
31942
- return action.selector ? `Scroll ${action.pixels}px in ${action.selector}` : `Scroll ${action.pixels}px on page`;
31943
- }
31944
- }
31945
- /**
31946
- * Emits one incremental browser-tool update when a hidden chat-progress listener is attached.
31947
- */
31948
- function emitRunBrowserProgress(args, update) {
31949
- emitToolCallProgressFromToolArgs(args, update);
31950
- }
31951
- /**
31952
- * Returns the current timestamp in the branded ISO-8601 format used by tool-call logs.
31953
- */
31954
- function createRunBrowserLogTimestamp() {
31955
- return new Date().toISOString();
31956
- }
31957
- /**
31958
- * Executes non-graphical fallback scraping.
31959
- */
31960
- async function runFallbackScrape(url) {
31961
- return await fetchUrlContent(url);
31962
- }
31963
- /**
31964
- * Runs interactive browser automation through Playwright.
31965
- *
31966
- * @param args Tool arguments provided by the model.
31967
- * @param internalOptions Optional runtime options for cancellation.
31968
- * @returns Markdown summary with structured playback payload.
31969
- */
31970
- async function run_browser(args, internalOptions = {}) {
31971
- runBrowserObservability.incrementTotalRuns();
31972
- const startedAt = Date.now();
31973
- const sessionId = runBrowserRuntime.createRunBrowserSessionId();
31974
- const initialUrl = String(args.url || '').trim();
31975
- const mode = runBrowserRuntime.resolveExecutionMode();
31976
- const timeoutConfiguration = runBrowserRuntime.resolveTimeoutConfiguration(args.timeouts);
31977
- let page = null;
31978
- let connectDurationMs = null;
31979
- let initialNavigationDurationMs = null;
31980
- let timeToFirstByteMs = null;
31981
- try {
31982
- if (!initialUrl) {
31983
- throw runBrowserErrorHandling.createRunBrowserValidationError({
31984
- message: 'Missing required `url` argument.',
31985
- debug: {
31986
- field: 'url',
31987
- },
31988
- });
31989
- }
31990
- const normalizedActions = runBrowserWorkflow.normalizeActions(args.actions);
31991
- runBrowserErrorHandling.assertNotAborted(internalOptions.signal, sessionId);
31992
- const openedPage = await runBrowserWorkflow.openPageWithUrl({
31993
- url: initialUrl,
31994
- sessionId,
31995
- timeouts: timeoutConfiguration,
31996
- signal: internalOptions.signal,
31997
- });
31998
- page = openedPage.page;
31999
- connectDurationMs = openedPage.connectDurationMs;
32000
- initialNavigationDurationMs = openedPage.initialNavigationDurationMs;
32001
- timeToFirstByteMs = openedPage.timeToFirstByteMs;
32002
- emitRunBrowserProgress(args, {
32003
- state: 'PARTIAL',
32004
- log: {
32005
- createdAt: createRunBrowserLogTimestamp(),
32006
- kind: 'browser-session',
32007
- title: 'Browser ready',
32008
- message: 'Opened the initial page and started the browser session.',
32009
- payload: {
32010
- sessionId,
32011
- initialUrl,
32012
- connectDurationMs,
32013
- initialNavigationDurationMs,
32014
- timeToFirstByteMs,
32015
- },
32016
- },
32017
- });
32018
- const artifacts = [];
32019
- const initialArtifact = await runBrowserArtifacts.captureSnapshotArtifact({
32020
- page,
32021
- sessionId,
32022
- label: 'Initial page',
32023
- fileSuffix: 'initial',
32024
- });
32025
- if (initialArtifact) {
32026
- artifacts.push(initialArtifact);
32027
- }
32028
- for (const [index, action] of normalizedActions.entries()) {
32029
- runBrowserErrorHandling.assertNotAborted(internalOptions.signal, sessionId);
32030
- emitRunBrowserProgress(args, {
32031
- state: 'PARTIAL',
32032
- log: {
32033
- createdAt: createRunBrowserLogTimestamp(),
32034
- kind: 'browser-action',
32035
- title: `Action ${index + 1} running`,
32036
- message: formatRunBrowserActionSummary(action),
32037
- payload: {
32038
- actionIndex: index + 1,
32039
- action,
32040
- phase: 'running',
32041
- },
32042
- },
32043
- });
32044
- await runBrowserWorkflow.executeAction({
32045
- page,
32046
- action,
32047
- actionIndex: index + 1,
32048
- timeouts: timeoutConfiguration,
32049
- signal: internalOptions.signal,
32050
- });
32051
- emitRunBrowserProgress(args, {
32052
- state: 'PARTIAL',
32053
- log: {
32054
- createdAt: createRunBrowserLogTimestamp(),
32055
- kind: 'browser-action',
32056
- title: `Action ${index + 1} finished`,
32057
- message: formatRunBrowserActionSummary(action),
32058
- payload: {
32059
- actionIndex: index + 1,
32060
- action,
32061
- phase: 'complete',
32062
- },
32063
- },
32064
- });
32065
- const actionArtifact = await runBrowserArtifacts.captureSnapshotArtifact({
32066
- page,
32067
- sessionId,
32068
- label: `After action ${index + 1}`,
32069
- fileSuffix: `action-${String(index + 1).padStart(3, '0')}-${action.type}`,
32070
- actionIndex: index + 1,
32071
- action,
32072
- });
32073
- if (actionArtifact) {
32074
- artifacts.push(actionArtifact);
32075
- }
32076
- }
32077
- const snapshotPath = await runBrowserArtifacts.captureSnapshot(page, sessionId);
32078
- const finalUrl = page.url();
32079
- const finalTitle = await runBrowserArtifacts.getPageTitle(page);
32080
- if (snapshotPath) {
32081
- artifacts.push({
32082
- kind: 'screenshot',
32083
- label: 'Final page',
32084
- path: snapshotPath,
32085
- capturedAt: new Date().toISOString(),
32086
- url: finalUrl,
32087
- title: finalTitle,
32088
- });
32089
- }
32090
- const payload = runBrowserResultFormatting.createResultPayload({
32091
- sessionId,
32092
- mode,
32093
- modeUsed: 'remote-browser',
32094
- initialUrl,
32095
- finalUrl,
32096
- finalTitle,
32097
- executedActions: normalizedActions,
32098
- artifacts,
32099
- warning: null,
32100
- error: null,
32101
- fallbackContent: null,
32102
- timing: {
32103
- connectDurationMs,
32104
- initialNavigationDurationMs,
32105
- timeToFirstByteMs,
32106
- totalDurationMs: Date.now() - startedAt,
32107
- },
32108
- });
32109
- runBrowserObservability.logRunBrowserMetric({
32110
- event: 'run_browser_success',
32111
- sessionId,
32112
- mode: 'remote-browser',
32113
- payload: {
32114
- actions: normalizedActions.length,
32115
- connectDurationMs,
32116
- initialNavigationDurationMs,
32117
- timeToFirstByteMs,
32118
- },
32119
- });
32120
- return runBrowserResultFormatting.formatSuccessResult({
32121
- payload,
32122
- snapshotPath,
32123
- });
32124
- }
32125
- catch (error) {
32126
- const toolError = runBrowserErrorHandling.classifyRunBrowserToolError({
32127
- error,
32128
- sessionId,
32129
- mode,
32130
- });
32131
- const errorCodeCount = runBrowserObservability.incrementRunBrowserErrorCodeCounter(toolError.code);
32132
- if (runBrowserErrorHandling.isRemoteBrowserUnavailableCode(toolError.code) && initialUrl) {
32133
- const fallbackContent = await runFallbackScrape(initialUrl);
32134
- const { fallbackRuns, fallbackRate } = runBrowserObservability.incrementFallbackRunsAndGetMetrics();
32135
- emitRunBrowserProgress(args, {
32136
- state: 'PARTIAL',
32137
- log: {
32138
- createdAt: createRunBrowserLogTimestamp(),
32139
- kind: 'warning',
32140
- level: 'warning',
32141
- title: 'Fallback enabled',
32142
- message: 'Remote browser was unavailable, so fallback scraping was used instead.',
32143
- payload: {
32144
- errorCode: toolError.code,
32145
- initialUrl,
32146
- },
32147
- },
32148
- });
32149
- const payload = runBrowserResultFormatting.createResultPayload({
32150
- sessionId,
32151
- mode,
32152
- modeUsed: 'fallback',
32153
- initialUrl,
32154
- finalUrl: null,
32155
- finalTitle: null,
32156
- executedActions: [],
32157
- artifacts: [],
32158
- warning: runBrowserConstants.fallbackDynamicContentWarning,
32159
- error: toolError,
32160
- fallbackContent,
32161
- timing: {
32162
- connectDurationMs,
32163
- initialNavigationDurationMs,
32164
- timeToFirstByteMs,
32165
- totalDurationMs: Date.now() - startedAt,
32166
- },
32167
- });
32168
- runBrowserObservability.logRunBrowserMetric({
32169
- event: 'run_browser_fallback_used',
32170
- sessionId,
32171
- mode: 'fallback',
32172
- payload: {
32173
- errorCode: toolError.code,
32174
- errorCodeCount,
32175
- fallbackRuns,
32176
- totalRuns: runBrowserObservability.getTotalRuns(),
32177
- fallbackRate,
32178
- },
32179
- });
32180
- return runBrowserResultFormatting.formatFallbackResult({
32181
- payload,
32182
- fallbackContent,
32183
- requestedActions: Array.isArray(args.actions) ? args.actions.length : 0,
32184
- });
32185
- }
32186
- emitRunBrowserProgress(args, {
32187
- state: 'ERROR',
32188
- log: {
32189
- createdAt: createRunBrowserLogTimestamp(),
32190
- kind: 'error',
32191
- level: 'error',
32192
- title: 'Browser run failed',
32193
- message: toolError.message,
32194
- payload: {
32195
- code: toolError.code,
32196
- debug: toolError.debug,
32197
- },
32198
- },
32199
- });
32200
- const payload = runBrowserResultFormatting.createResultPayload({
32201
- sessionId,
32202
- mode,
32203
- modeUsed: 'remote-browser',
32204
- initialUrl,
32205
- finalUrl: page ? page.url() : null,
32206
- finalTitle: page ? await runBrowserArtifacts.getPageTitle(page) : null,
32207
- executedActions: [],
32208
- artifacts: [],
32209
- warning: null,
32210
- error: toolError,
32211
- fallbackContent: null,
32212
- timing: {
32213
- connectDurationMs,
32214
- initialNavigationDurationMs,
32215
- timeToFirstByteMs,
32216
- totalDurationMs: Date.now() - startedAt,
32217
- },
32218
- });
32219
- runBrowserObservability.logRunBrowserMetric({
32220
- event: 'run_browser_failed',
32221
- sessionId,
32222
- mode: 'remote-browser',
32223
- payload: {
32224
- errorCode: toolError.code,
32225
- errorCodeCount,
32226
- connectDurationMs,
32227
- initialNavigationDurationMs,
32228
- timeToFirstByteMs,
32229
- },
32230
- });
32231
- return runBrowserResultFormatting.formatErrorResult({
32232
- payload,
32233
- });
32234
- }
32235
- finally {
32236
- await runBrowserArtifacts.cleanupPage(page, sessionId);
32237
- }
32238
- }
32239
-
32240
- /**
32241
- * Resolves the server-side implementation of the `run_browser` tool for Node.js environments.
32242
- *
32243
- * This uses lazy `require` to keep the core package decoupled from Agents Server internals.
32244
- * When the server tool cannot be resolved, the fallback implementation throws a helpful error.
32245
- *
32246
- * @private internal utility for USE BROWSER commitment
32247
- */
32248
- function resolveRunBrowserToolForNode() {
32249
- try {
32250
- // eslint-disable-next-line @typescript-eslint/no-var-requires
32251
- // const { run_browser } = require('../../../apps/agents-server/src/tools/run_browser');
32252
- if (typeof run_browser !== 'function') {
32253
- throw new Error('run_browser value is not a function but ' + typeof run_browser);
32254
- }
32255
- return run_browser;
32256
- }
32257
- catch (error) {
32258
- assertsError(error);
32259
- return async () => {
32260
- throw new EnvironmentMismatchError(spaceTrim$1((block) => `
32261
- \`run_browser\` tool is not available in this environment.
32262
- This commitment requires the Agents Server browser runtime with Playwright CLI.
32263
-
32264
- ${error.name}:
32265
- ${block(error.message)}
32266
- `));
32267
- };
32268
- }
32269
- }
32270
-
32271
- /**
32272
- * Resolves the server-side implementation of the send_email tool for Node.js environments.
32273
- *
32274
- * This uses a lazy require so the core package can still load even if the Agents Server
32275
- * module is unavailable. When the server tool cannot be resolved, a fallback implementation
32276
- * throws a helpful error message.
32277
- *
32278
- * @private internal utility for USE EMAIL commitment
32279
- */
32280
- function resolveSendEmailToolForNode() {
32281
- try {
32282
- // eslint-disable-next-line @typescript-eslint/no-var-requires
32283
- const { send_email } = require('../../../apps/agents-server/src/tools/send_email');
32284
- if (typeof send_email !== 'function') {
32285
- throw new Error('send_email value is not a function but ' + typeof send_email);
32286
- }
32287
- return send_email;
32288
- }
32289
- catch (error) {
32290
- const normalizedError = error instanceof Error
32291
- ? error
32292
- : new Error(typeof error === 'string' ? error : JSON.stringify(error !== null && error !== void 0 ? error : 'Unknown error'));
32293
- return async () => {
32294
- throw new EnvironmentMismatchError(spaceTrim$1((block) => `
32295
- \`send_email\` tool is not available in this environment.
32296
- This commitment requires Agents Server runtime with wallet-backed SMTP sending.
32297
-
32298
- ${normalizedError.name}:
32299
- ${block(normalizedError.message)}
32300
- `));
32301
- };
32302
- }
32303
- }
32304
-
32305
- /**
32306
- * Resolves the server-side `spawn_agent` tool for Node.js runtimes.
32307
- *
32308
- * Uses lazy require so core package can load outside Agents Server.
32309
- *
32310
- * @private internal utility for USE SPAWN commitment
32311
- */
32312
- function resolveSpawnAgentToolForNode() {
32313
- try {
32314
- // eslint-disable-next-line @typescript-eslint/no-var-requires
32315
- const { spawn_agent } = require('../../../apps/agents-server/src/tools/spawn_agent');
32316
- if (typeof spawn_agent !== 'function') {
32317
- throw new Error('spawn_agent value is not a function but ' + typeof spawn_agent);
32318
- }
32319
- return spawn_agent;
32320
- }
32321
- catch (error) {
32322
- const normalizedError = error instanceof Error
32323
- ? error
32324
- : new Error(typeof error === 'string' ? error : JSON.stringify(error !== null && error !== void 0 ? error : 'Unknown error'));
32325
- return async () => {
32326
- throw new EnvironmentMismatchError(spaceTrim$1((block) => `
32327
- \`spawn_agent\` tool is not available in this environment.
32328
- This commitment requires Agents Server runtime with agent persistence enabled.
32329
-
32330
- ${normalizedError.name}:
32331
- ${block(normalizedError.message)}
32332
- `));
32333
- };
32334
- }
32335
- }
32336
-
32337
- /**
32338
- * Collects tool functions from all commitment definitions.
32339
- *
32340
- * @returns Map of tool function implementations.
32341
- * @private internal helper for commitment tool registry
32342
- */
32343
- function collectCommitmentToolFunctions() {
32344
- const allToolFunctions = {};
32345
- for (const commitmentDefinition of getAllCommitmentDefinitions()) {
32346
- const toolFunctions = commitmentDefinition.getToolFunctions();
32347
- for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
32348
- if (allToolFunctions[funcName] !== undefined &&
32349
- just(false) /* <- Note: [??] How to deal with commitment aliases */) {
32350
- throw new UnexpectedError(`Duplicate tool function name detected: \`${funcName}\` provided by commitment \`${commitmentDefinition.type}\``);
32351
- }
32352
- allToolFunctions[funcName] = funcImpl;
32353
- }
32354
- }
32355
- return allToolFunctions;
32356
- }
32357
- /**
32358
- * Creates a proxy that resolves tool functions on demand.
32359
- *
32360
- * @param getFunctions - Provider of current tool functions.
32361
- * @returns Proxy exposing tool functions as properties.
32362
- * @private internal helper for commitment tool registry
32363
- */
32364
- function createToolFunctionsProxy(getFunctions) {
32365
- const resolveFunctions = () => getFunctions();
32366
- return new Proxy({}, {
32367
- get(_target, prop) {
32368
- if (typeof prop !== 'string') {
32369
- return undefined;
32370
- }
32371
- return resolveFunctions()[prop];
32372
- },
32373
- has(_target, prop) {
32374
- if (typeof prop !== 'string') {
32375
- return false;
30394
+ return resolveFunctions()[prop];
30395
+ },
30396
+ has(_target, prop) {
30397
+ if (typeof prop !== 'string') {
30398
+ return false;
32376
30399
  }
32377
30400
  return prop in resolveFunctions();
32378
30401
  },
@@ -32459,6 +30482,155 @@ function getAllCommitmentsToolFunctionsForNode() {
32459
30482
  * TODO: [??] Unite `xxxForServer` and `xxxForNode` naming
32460
30483
  */
32461
30484
 
30485
+ /**
30486
+ * Attempts to locate the specified application on a Linux system using the 'which' command.
30487
+ * Returns the path to the executable if found, or null otherwise.
30488
+ *
30489
+ * @private within the repository
30490
+ */
30491
+ async function locateAppOnLinux({ linuxWhich, }) {
30492
+ try {
30493
+ const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
30494
+ return result.trim();
30495
+ }
30496
+ catch (error) {
30497
+ assertsError(error);
30498
+ return null;
30499
+ }
30500
+ }
30501
+ /**
30502
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
30503
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
30504
+ */
30505
+
30506
+ /**
30507
+ * Checks if the file is executable
30508
+ *
30509
+ * @private within the repository
30510
+ */
30511
+ async function isExecutable(path, fs) {
30512
+ try {
30513
+ await fs.access(path, fs.constants.X_OK);
30514
+ return true;
30515
+ }
30516
+ catch (error) {
30517
+ return false;
30518
+ }
30519
+ }
30520
+ /**
30521
+ * Note: Not [~🟢~] because it is not directly dependent on `fs
30522
+ * TODO: [🖇] What about symlinks?
30523
+ */
30524
+
30525
+ // Note: Module `userhome` has no types available, so it is imported using `require`
30526
+ // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
30527
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
30528
+ const userhome = require('userhome');
30529
+ /**
30530
+ * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
30531
+ * Returns the path to the executable if found, or null otherwise.
30532
+ *
30533
+ * @private within the repository
30534
+ */
30535
+ async function locateAppOnMacOs({ macOsName, }) {
30536
+ try {
30537
+ const toExec = `/Contents/MacOS/${macOsName}`;
30538
+ const regPath = `/Applications/${macOsName}.app` + toExec;
30539
+ const altPath = userhome(regPath.slice(1));
30540
+ if (await isExecutable(regPath, $provideFilesystemForNode())) {
30541
+ return regPath;
30542
+ }
30543
+ else if (await isExecutable(altPath, $provideFilesystemForNode())) {
30544
+ return altPath;
30545
+ }
30546
+ const result = await $execCommand({
30547
+ crashOnError: true,
30548
+ command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
30549
+ });
30550
+ return result.trim() + toExec;
30551
+ }
30552
+ catch (error) {
30553
+ assertsError(error);
30554
+ return null;
30555
+ }
30556
+ }
30557
+ /**
30558
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
30559
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
30560
+ */
30561
+
30562
+ /**
30563
+ * Attempts to locate the specified application on a Windows system by searching common installation directories.
30564
+ * Returns the path to the executable if found, or null otherwise.
30565
+ *
30566
+ * @private within the repository
30567
+ */
30568
+ async function locateAppOnWindows({ appName, windowsSuffix, }) {
30569
+ try {
30570
+ const prefixes = [
30571
+ process.env.LOCALAPPDATA,
30572
+ join(process.env.LOCALAPPDATA || '', 'Programs'),
30573
+ process.env.PROGRAMFILES,
30574
+ process.env['PROGRAMFILES(X86)'],
30575
+ ];
30576
+ for (const prefix of prefixes) {
30577
+ const path = prefix + windowsSuffix;
30578
+ if (await isExecutable(path, $provideFilesystemForNode())) {
30579
+ return path;
30580
+ }
30581
+ }
30582
+ throw new Error(`Can not locate app ${appName} on Windows.`);
30583
+ }
30584
+ catch (error) {
30585
+ assertsError(error);
30586
+ return null;
30587
+ }
30588
+ }
30589
+ /**
30590
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
30591
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
30592
+ */
30593
+
30594
+ /**
30595
+ * Locates an application on the system
30596
+ *
30597
+ * @private within the repository
30598
+ */
30599
+ function locateApp(options) {
30600
+ if (!$isRunningInNode()) {
30601
+ throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
30602
+ }
30603
+ const { appName, linuxWhich, windowsSuffix, macOsName } = options;
30604
+ if (process.platform === 'win32') {
30605
+ if (windowsSuffix) {
30606
+ return locateAppOnWindows({ appName, windowsSuffix });
30607
+ }
30608
+ else {
30609
+ throw new Error(`${appName} is not available on Windows.`);
30610
+ }
30611
+ }
30612
+ else if (process.platform === 'darwin') {
30613
+ if (macOsName) {
30614
+ return locateAppOnMacOs({ macOsName });
30615
+ }
30616
+ else {
30617
+ throw new Error(`${appName} is not available on macOS.`);
30618
+ }
30619
+ }
30620
+ else {
30621
+ if (linuxWhich) {
30622
+ return locateAppOnLinux({ linuxWhich });
30623
+ }
30624
+ else {
30625
+ throw new Error(`${appName} is not available on Linux.`);
30626
+ }
30627
+ }
30628
+ }
30629
+ /**
30630
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
30631
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
30632
+ */
30633
+
32462
30634
  /**
32463
30635
  * Locates the LibreOffice executable on the current system by searching platform-specific paths.
32464
30636
  * Returns the path to the executable if found, or null otherwise.