@mercuryo-ai/agentbrowse 0.2.60 → 0.2.63

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 (105) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +132 -14
  3. package/dist/browser-session-state.d.ts +40 -10
  4. package/dist/browser-session-state.d.ts.map +1 -1
  5. package/dist/browser-session-state.js +63 -5
  6. package/dist/commands/act.d.ts.map +1 -1
  7. package/dist/commands/act.js +548 -535
  8. package/dist/commands/attach.d.ts +1 -3
  9. package/dist/commands/attach.d.ts.map +1 -1
  10. package/dist/commands/attach.js +5 -12
  11. package/dist/commands/browser-connection-failure.d.ts +9 -0
  12. package/dist/commands/browser-connection-failure.d.ts.map +1 -0
  13. package/dist/commands/browser-connection-failure.js +15 -0
  14. package/dist/commands/browser-status.d.ts +0 -2
  15. package/dist/commands/browser-status.d.ts.map +1 -1
  16. package/dist/commands/browser-status.js +27 -37
  17. package/dist/commands/close.d.ts.map +1 -1
  18. package/dist/commands/close.js +5 -0
  19. package/dist/commands/extract.d.ts.map +1 -1
  20. package/dist/commands/extract.js +147 -144
  21. package/dist/commands/interaction-kernel.d.ts +1 -1
  22. package/dist/commands/interaction-kernel.d.ts.map +1 -1
  23. package/dist/commands/interaction-kernel.js +1 -1
  24. package/dist/commands/launch.d.ts +0 -1
  25. package/dist/commands/launch.d.ts.map +1 -1
  26. package/dist/commands/launch.js +11 -12
  27. package/dist/commands/navigate.d.ts.map +1 -1
  28. package/dist/commands/navigate.js +79 -73
  29. package/dist/commands/observe-accessibility.d.ts.map +1 -1
  30. package/dist/commands/observe-accessibility.js +36 -2
  31. package/dist/commands/observe-inventory.d.ts +50 -7
  32. package/dist/commands/observe-inventory.d.ts.map +1 -1
  33. package/dist/commands/observe-inventory.js +822 -99
  34. package/dist/commands/observe-persistence.d.ts.map +1 -1
  35. package/dist/commands/observe-persistence.js +49 -6
  36. package/dist/commands/observe-projection.d.ts +6 -2
  37. package/dist/commands/observe-projection.d.ts.map +1 -1
  38. package/dist/commands/observe-projection.js +251 -27
  39. package/dist/commands/observe-semantics.d.ts +1 -0
  40. package/dist/commands/observe-semantics.d.ts.map +1 -1
  41. package/dist/commands/observe-semantics.js +541 -135
  42. package/dist/commands/observe-signals.d.ts +4 -4
  43. package/dist/commands/observe-signals.d.ts.map +1 -1
  44. package/dist/commands/observe-signals.js +2 -2
  45. package/dist/commands/observe-surfaces.d.ts +2 -1
  46. package/dist/commands/observe-surfaces.d.ts.map +1 -1
  47. package/dist/commands/observe-surfaces.js +143 -45
  48. package/dist/commands/observe.d.ts +5 -1
  49. package/dist/commands/observe.d.ts.map +1 -1
  50. package/dist/commands/observe.js +266 -274
  51. package/dist/commands/screenshot.d.ts.map +1 -1
  52. package/dist/commands/screenshot.js +50 -64
  53. package/dist/commands/semantic-observe.d.ts.map +1 -1
  54. package/dist/commands/semantic-observe.js +43 -0
  55. package/dist/library.d.ts +3 -1
  56. package/dist/library.d.ts.map +1 -1
  57. package/dist/library.js +3 -1
  58. package/dist/match-resolve-fill.d.ts +196 -0
  59. package/dist/match-resolve-fill.d.ts.map +1 -0
  60. package/dist/match-resolve-fill.js +700 -0
  61. package/dist/match-resolve-fill.test-support.d.ts +34 -0
  62. package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
  63. package/dist/match-resolve-fill.test-support.js +81 -0
  64. package/dist/protected-fill.d.ts.map +1 -1
  65. package/dist/protected-fill.js +46 -7
  66. package/dist/runtime-protected-state.d.ts.map +1 -1
  67. package/dist/runtime-protected-state.js +12 -0
  68. package/dist/runtime-state.d.ts +6 -0
  69. package/dist/runtime-state.d.ts.map +1 -1
  70. package/dist/runtime-state.js +6 -0
  71. package/dist/secrets/form-matcher.d.ts.map +1 -1
  72. package/dist/secrets/form-matcher.js +76 -27
  73. package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
  74. package/dist/secrets/protected-exact-value-redaction.js +6 -0
  75. package/dist/secrets/protected-fill.js +3 -3
  76. package/dist/session.d.ts +3 -3
  77. package/dist/session.d.ts.map +1 -1
  78. package/dist/session.js +2 -2
  79. package/dist/solver/browser-launcher.d.ts.map +1 -1
  80. package/dist/solver/browser-launcher.js +2 -1
  81. package/dist/sticky-owner-host-entry.d.ts +2 -0
  82. package/dist/sticky-owner-host-entry.d.ts.map +1 -0
  83. package/dist/sticky-owner-host-entry.js +97 -0
  84. package/dist/sticky-owner.d.ts +15 -0
  85. package/dist/sticky-owner.d.ts.map +1 -0
  86. package/dist/sticky-owner.js +431 -0
  87. package/dist/testing.d.ts +1 -0
  88. package/dist/testing.d.ts.map +1 -1
  89. package/dist/testing.js +1 -0
  90. package/docs/README.md +28 -11
  91. package/docs/api-reference.md +311 -19
  92. package/docs/assistive-runtime.md +41 -16
  93. package/docs/configuration.md +36 -4
  94. package/docs/getting-started.md +73 -5
  95. package/docs/integration-checklist.md +32 -3
  96. package/docs/match-resolve-fill.md +699 -0
  97. package/docs/protected-fill.md +373 -91
  98. package/docs/testing.md +147 -15
  99. package/docs/troubleshooting.md +47 -6
  100. package/examples/README.md +7 -0
  101. package/examples/match-resolve-fill.ts +107 -0
  102. package/package.json +4 -2
  103. package/dist/protected-fill-browser.d.ts +0 -22
  104. package/dist/protected-fill-browser.d.ts.map +0 -1
  105. package/dist/protected-fill-browser.js +0 -52
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * attach <cdp-url> — Attach AgentBrowse to an existing browser session.
3
3
  */
4
- import { type BrowserSessionState, type BrowserSessionCapabilities } from '../browser-session-state.js';
4
+ import { type BrowserSessionState } from '../browser-session-state.js';
5
5
  import type { BrowseSessionTransport } from '../browser-session-state.js';
6
6
  /** Stable top-level error codes returned by `attach(...)`. */
7
7
  export declare const ATTACH_ERROR_CODES: readonly ["browser_attach_failed"];
@@ -13,7 +13,6 @@ export type AttachOutcomeType = (typeof ATTACH_OUTCOME_TYPES)[number];
13
13
  export type AttachOptions = {
14
14
  launchedAt?: string;
15
15
  provider?: string;
16
- capabilities?: BrowserSessionCapabilities;
17
16
  transport?: BrowseSessionTransport;
18
17
  };
19
18
  /** Successful attach result for an existing CDP browser session. */
@@ -25,7 +24,6 @@ export type AttachSuccessResult = {
25
24
  url: string;
26
25
  title: string;
27
26
  provider?: string;
28
- captchaSolveCapable: boolean;
29
27
  };
30
28
  /** Failed attach result with a stable top-level error code. */
31
29
  export type AttachFailureResult = {
@@ -1 +1 @@
1
- {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../src/commands/attach.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAChC,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AAErE,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,sBAAuB,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,0BAA0B,CAAC;IAC1C,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,CAAC;AAEF,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAErE,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA2C/F"}
1
+ {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../src/commands/attach.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AAErE,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,sBAAuB,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,CAAC;AAEF,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAErE,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoC/F"}
@@ -2,28 +2,27 @@
2
2
  * attach <cdp-url> — Attach AgentBrowse to an existing browser session.
3
3
  */
4
4
  import { buildAttachedSession, buildCdpHttpEndpointUrl, } from '../browser-session-state.js';
5
- import { connectPlaywright, disconnectPlaywright, syncLaunchPage } from '../playwright-runtime.js';
5
+ import { syncLaunchPage } from '../playwright-runtime.js';
6
+ import { initializeBrowserSessionOwner, withStickyOwnerBrowser } from '../sticky-owner.js';
6
7
  /** Stable top-level error codes returned by `attach(...)`. */
7
8
  export const ATTACH_ERROR_CODES = ['browser_attach_failed'];
8
9
  /** Stable outcome categories emitted by `attach(...)`. */
9
10
  export const ATTACH_OUTCOME_TYPES = ['blocked'];
10
11
  /** Attaches AgentBrowse to an existing browser via a CDP websocket or HTTP endpoint. */
11
12
  export async function attach(cdpUrl, options = {}) {
12
- let browser = null;
13
13
  try {
14
14
  const normalizedCdpUrl = await resolveAttachEndpoint(cdpUrl);
15
15
  const session = buildAttachedSession({
16
16
  cdpUrl: normalizedCdpUrl,
17
17
  launchedAt: options.launchedAt ?? new Date().toISOString(),
18
18
  ...(options.provider ? { provider: options.provider } : {}),
19
- ...(options.capabilities ? { capabilities: options.capabilities } : {}),
20
19
  ...(options.transport ? { transport: options.transport } : {}),
21
20
  });
22
- browser = await connectPlaywright(normalizedCdpUrl);
23
- const syncedPage = await syncLaunchPage(session, browser, {
21
+ await initializeBrowserSessionOwner(session);
22
+ const syncedPage = await withStickyOwnerBrowser(session, (browser) => syncLaunchPage(session, browser, {
24
23
  fallbackUrl: '',
25
24
  fallbackTitle: '',
26
- });
25
+ }));
27
26
  return {
28
27
  success: true,
29
28
  runtime: 'attached',
@@ -32,7 +31,6 @@ export async function attach(cdpUrl, options = {}) {
32
31
  url: syncedPage.url,
33
32
  title: syncedPage.title,
34
33
  ...(options.provider ? { provider: options.provider } : {}),
35
- captchaSolveCapable: options.capabilities?.captchaSolve === true,
36
34
  };
37
35
  }
38
36
  catch (error) {
@@ -44,11 +42,6 @@ export async function attach(cdpUrl, options = {}) {
44
42
  reason: formatUnknownError(error),
45
43
  };
46
44
  }
47
- finally {
48
- if (browser) {
49
- await disconnectPlaywright(browser);
50
- }
51
- }
52
45
  }
53
46
  async function resolveAttachEndpoint(input) {
54
47
  const normalized = input.trim();
@@ -0,0 +1,9 @@
1
+ export declare const STICKY_OWNER_UNRECOVERABLE_REASON = "sticky_owner_unrecoverable";
2
+ export declare function describeBrowserConnectionFailure(error: unknown, options: {
3
+ defaultMessage: string;
4
+ unrecoverableSessionMessage: string;
5
+ }): {
6
+ message: string;
7
+ reason: string;
8
+ };
9
+ //# sourceMappingURL=browser-connection-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-connection-failure.d.ts","sourceRoot":"","sources":["../../src/commands/browser-connection-failure.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,+BAA+B,CAAC;AAK9E,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;IACP,cAAc,EAAE,MAAM,CAAC;IACvB,2BAA2B,EAAE,MAAM,CAAC;CACrC,GACA;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAcrC"}
@@ -0,0 +1,15 @@
1
+ export const STICKY_OWNER_UNRECOVERABLE_REASON = 'sticky_owner_unrecoverable';
2
+ const STICKY_OWNER_UNRECOVERABLE_PUBLIC_REASON = 'The previous browser session is no longer reachable, so AgentBrowse could not restore control of the browser. Launch or attach a new browser session and try again.';
3
+ export function describeBrowserConnectionFailure(error, options) {
4
+ const reason = error instanceof Error ? error.message : String(error);
5
+ if (reason === STICKY_OWNER_UNRECOVERABLE_REASON) {
6
+ return {
7
+ message: options.unrecoverableSessionMessage,
8
+ reason: STICKY_OWNER_UNRECOVERABLE_PUBLIC_REASON,
9
+ };
10
+ }
11
+ return {
12
+ message: options.defaultMessage,
13
+ reason,
14
+ };
15
+ }
@@ -29,13 +29,11 @@ export type BrowserStatusProtectedExposureResult = {
29
29
  exposureReason: string;
30
30
  message: string;
31
31
  reason: string;
32
- captchaSolveCapable?: boolean;
33
32
  };
34
33
  /** Success result when the browser is alive and a current page could be resolved or approximated. */
35
34
  export type BrowserStatusAliveResult = {
36
35
  success: true;
37
36
  alive: true;
38
- captchaSolveCapable?: boolean;
39
37
  pageRef?: string;
40
38
  url?: string;
41
39
  title?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"browser-status.d.ts","sourceRoot":"","sources":["../../src/commands/browser-status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAoBrC,KAAK,mBAAmB,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,QAAQ,GAAG,gBAAgB,CAAC;CAC5C,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,4BAA4B,gFAI/B,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AACrF,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClE,MAAM,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAEnE,uGAAuG;AACvG,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,CAAC,EAAE,gCAAgC,CAAC;IACvD,uBAAuB,EAAE,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,qGAAqG;AACrG,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,CAAC,EAAE,gCAAgC,CAAC;IACvD,qBAAqB,CAAC,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEF,kEAAkE;AAClE,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,qBAAqB,CAAC,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,oCAAoC,GACpC,wBAAwB,GACxB,6BAA6B,CAAC;AAuLlC,qFAAqF;AACrF,wBAAsB,aAAa,CACjC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAClC,OAAO,CAAC,mBAAmB,CAAC,CAgG9B"}
1
+ {"version":3,"file":"browser-status.d.ts","sourceRoot":"","sources":["../../src/commands/browser-status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAiBrC,KAAK,mBAAmB,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,QAAQ,GAAG,gBAAgB,CAAC;CAC5C,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,4BAA4B,gFAI/B,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AACrF,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClE,MAAM,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAEnE,uGAAuG;AACvG,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,CAAC,EAAE,gCAAgC,CAAC;IACvD,uBAAuB,EAAE,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qGAAqG;AACrG,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,CAAC,EAAE,gCAAgC,CAAC;IACvD,qBAAqB,CAAC,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEF,kEAAkE;AAClE,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,qBAAqB,CAAC,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,oCAAoC,GACpC,wBAAwB,GACxB,6BAA6B,CAAC;AA+KlC,qFAAqF;AACrF,wBAAsB,aAAa,CACjC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAClC,OAAO,CAAC,mBAAmB,CAAC,CA6F9B"}
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * browse browser-status — Check live browser/page/runtime state.
3
3
  */
4
- import { buildCdpHttpEndpointUrl, getSessionPort, supportsCaptchaSolve, } from '../browser-session-state.js';
5
- import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, } from '../playwright-runtime.js';
4
+ import { buildCdpHttpEndpointUrl, getSessionPort, } from '../browser-session-state.js';
5
+ import { resolveCurrentPageContext } from '../playwright-runtime.js';
6
6
  import { captureDiagnosticSnapshotBestEffort, finishDiagnosticStepBestEffort, recordCommandLifecycleEventBestEffort, startDiagnosticStep, } from '../diagnostics.js';
7
7
  import { scrubProtectedExactValues } from '../secrets/protected-exact-value-redaction.js';
8
+ import { withStickyOwnerBrowser } from '../sticky-owner.js';
8
9
  /** Stable outcome categories exposed by `status(...)`. */
9
10
  export const BROWSER_STATUS_OUTCOME_TYPES = [
10
11
  'browser_alive',
@@ -38,9 +39,6 @@ function buildProtectedStatusPayload(params) {
38
39
  success: true,
39
40
  alive: true,
40
41
  outcomeType: 'protected_exposure_active',
41
- ...(params.captchaSolveCapable !== undefined
42
- ? { captchaSolveCapable: params.captchaSolveCapable }
43
- : {}),
44
42
  runtime: params.runtimeSummary,
45
43
  ...(params.currentPageMismatch ? { currentPageMismatch: params.currentPageMismatch } : {}),
46
44
  protectedExposureActive: true,
@@ -56,40 +54,35 @@ function buildProtectedStatusPayload(params) {
56
54
  };
57
55
  }
58
56
  async function readCanonicalStatus(session) {
59
- let browser = null;
60
57
  try {
61
- browser = await connectPlaywright(session.cdpUrl);
62
- const resolved = await resolveCurrentPageContext(browser, session);
63
- const persisted = session.runtime?.currentPageRef;
64
- const title = (await resolved.page
65
- .title()
66
- .catch(() => session.runtime?.pages[resolved.pageRef]?.title ?? '')) ||
67
- session.runtime?.pages[resolved.pageRef]?.title ||
68
- 'unknown';
69
- const url = resolved.page.url() || session.runtime?.pages[resolved.pageRef]?.url || 'unknown';
70
- return {
71
- pageRef: resolved.pageRef,
72
- url,
73
- title,
74
- ...(persisted && (persisted !== resolved.pageRef || resolved.recoveredVia)
75
- ? {
76
- currentPageMismatch: {
77
- persistedPageRef: persisted,
78
- ...(persisted !== resolved.pageRef ? { livePageRef: resolved.pageRef } : {}),
79
- ...(resolved.recoveredVia ? { recoveredVia: resolved.recoveredVia } : {}),
80
- },
81
- }
82
- : {}),
83
- };
58
+ return await withStickyOwnerBrowser(session, async (browser) => {
59
+ const resolved = await resolveCurrentPageContext(browser, session);
60
+ const persisted = session.runtime?.currentPageRef;
61
+ const title = (await resolved.page
62
+ .title()
63
+ .catch(() => session.runtime?.pages[resolved.pageRef]?.title ?? '')) ||
64
+ session.runtime?.pages[resolved.pageRef]?.title ||
65
+ 'unknown';
66
+ const url = resolved.page.url() || session.runtime?.pages[resolved.pageRef]?.url || 'unknown';
67
+ return {
68
+ pageRef: resolved.pageRef,
69
+ url,
70
+ title,
71
+ ...(persisted && (persisted !== resolved.pageRef || resolved.recoveredVia)
72
+ ? {
73
+ currentPageMismatch: {
74
+ persistedPageRef: persisted,
75
+ ...(persisted !== resolved.pageRef ? { livePageRef: resolved.pageRef } : {}),
76
+ ...(resolved.recoveredVia ? { recoveredVia: resolved.recoveredVia } : {}),
77
+ },
78
+ }
79
+ : {}),
80
+ };
81
+ });
84
82
  }
85
83
  catch {
86
84
  return null;
87
85
  }
88
- finally {
89
- if (browser) {
90
- await disconnectPlaywright(browser);
91
- }
92
- }
93
86
  }
94
87
  function buildStatusEndpointUrl(session, port, resourcePath) {
95
88
  if (session) {
@@ -179,7 +172,6 @@ export async function browserStatus(session) {
179
172
  pageRef: canonical.pageRef,
180
173
  pageUrl: canonical.url,
181
174
  pageTitle: canonical.title,
182
- captchaSolveCapable: supportsCaptchaSolve(session),
183
175
  currentPageMismatch: canonical.currentPageMismatch,
184
176
  protectedExposure,
185
177
  });
@@ -190,7 +182,6 @@ export async function browserStatus(session) {
190
182
  const result = {
191
183
  success: true,
192
184
  alive: true,
193
- captchaSolveCapable: supportsCaptchaSolve(session),
194
185
  pageRef: canonical.pageRef,
195
186
  url: canonical.url,
196
187
  title: canonical.title,
@@ -219,7 +210,6 @@ export async function browserStatus(session) {
219
210
  const result = {
220
211
  success: true,
221
212
  alive: true,
222
- ...(session ? { captchaSolveCapable: supportsCaptchaSolve(session) } : {}),
223
213
  url: page.url ?? 'unknown',
224
214
  title: page.title ?? 'unknown',
225
215
  ...(session?.runtime ? { currentPageUnresolved: true } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAWvF,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,mCAAoC,CAAC;AAEnE,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,gEAAiE,CAAC;AAElG,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,uCAAuC;AACvC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AAEF,mCAAmC;AACnC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAsClE,sFAAsF;AACtF,wBAAsB,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAsErF"}
1
+ {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAYvF,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,mCAAoC,CAAC;AAEnE,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,gEAAiE,CAAC;AAElG,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,uCAAuC;AACvC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AAEF,mCAAmC;AACnC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAsClE,sFAAsF;AACtF,wBAAsB,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAyErF"}
@@ -6,6 +6,7 @@ import { captureDiagnosticSnapshotBestEffort, finishDiagnosticStepBestEffort, re
6
6
  import { info } from '../output.js';
7
7
  import { closeOwnedBrowser } from '../owned-browser.js';
8
8
  import { isManagedBrowserPid } from '../owned-process.js';
9
+ import { terminateBrowserSessionOwner } from '../sticky-owner.js';
9
10
  /** Stable top-level error codes returned by `close(...)`. */
10
11
  export const CLOSE_ERROR_CODES = ['browser_close_failed'];
11
12
  /** Stable outcome categories emitted by `close(...)`. */
@@ -64,6 +65,7 @@ export async function close(session) {
64
65
  },
65
66
  });
66
67
  if (isCloseableManagedSession(session)) {
68
+ await terminateBrowserSessionOwner(session);
67
69
  const closeResult = await closeOwnedBrowser(session);
68
70
  if (!closeResult.success) {
69
71
  recordCommandLifecycleEventBestEffort({
@@ -90,6 +92,9 @@ export async function close(session) {
90
92
  };
91
93
  }
92
94
  }
95
+ else if (session?.stickyOwner) {
96
+ await terminateBrowserSessionOwner(session);
97
+ }
93
98
  recordCommandLifecycleEventBestEffort({
94
99
  step: closeStep,
95
100
  phase: 'completed',
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAUzE,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAsBtB,KAAK,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,uBAAuB,GACvB,kBAAkB,EAAE,CAAC;AAEzB,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAC;CACnC;AAED,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,GAAG,CAAC,CAAC,UAAU,CAAC;AAExE,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,8MAStB,CAAC;AAEX,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,8EAKxB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,+CAA+C;AAC/C,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG;IAChD,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC,kBAAkB,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,2CAA2C;AAC3C,MAAM,MAAM,oBAAoB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAAG,qBAAqB,CAAC;AAE9E,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAibxE;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,kBAAkB,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CA8RxB;AAED,sFAAsF;AACtF,wBAAsB,OAAO,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,MAAM,GAAG,kBAAkB,EACxC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAUzE,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAsBtB,KAAK,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,uBAAuB,GACvB,kBAAkB,EAAE,CAAC;AAEzB,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAC;CACnC;AAED,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,GAAG,CAAC,CAAC,UAAU,CAAC;AAExE,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,8MAStB,CAAC;AAEX,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,8EAKxB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,+CAA+C;AAC/C,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG;IAChD,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC,kBAAkB,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,2CAA2C;AAC3C,MAAM,MAAM,oBAAoB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAAG,qBAAqB,CAAC;AAE9E,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAibxE;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,kBAAkB,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CAgSxB;AAED,sFAAsF;AACtF,wBAAsB,OAAO,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,MAAM,GAAG,kBAAkB,EACxC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
@@ -8,12 +8,14 @@ import { getSurface, getTarget, markSurfaceLifecycle, markTargetLifecycle, } fro
8
8
  import { getPageScopeEpoch, setCurrentPage } from '../runtime-page-state.js';
9
9
  import { outputContractFailure, outputJSON, } from '../output.js';
10
10
  import { captureDiagnosticSnapshotBestEffort, finishDiagnosticStepBestEffort, recordCommandLifecycleEventBestEffort, startDiagnosticStep, } from '../diagnostics.js';
11
- import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, resolvePageByRef as resolvePlaywrightPageByRef, syncSessionPage, } from '../playwright-runtime.js';
11
+ import { resolveCurrentPageContext, resolvePageByRef as resolvePlaywrightPageByRef, syncSessionPage, } from '../playwright-runtime.js';
12
12
  import { withApiTraceContext } from '../command-api-tracing.js';
13
13
  import { normalizePageSignature } from './descriptor-validation.js';
14
14
  import { readScopedDialogText } from './extract-scoped-dialog-text.js';
15
15
  import { resolveScopedExtractContext } from './extract-scope-resolution.js';
16
16
  import { executeStagehandExtract } from './extract-stagehand-executor.js';
17
+ import { describeBrowserConnectionFailure } from './browser-connection-failure.js';
18
+ import { withStickyOwnerBrowser } from '../sticky-owner.js';
17
19
  /** Stable top-level error codes returned by `extract(...)`. */
18
20
  export const EXTRACT_ERROR_CODES = [
19
21
  'browser_connection_failed',
@@ -459,180 +461,181 @@ export async function extractBrowser(session, schemaInput, scopeRef) {
459
461
  scopeRef,
460
462
  });
461
463
  }
462
- let browser = null;
463
464
  let failureMessage = null;
464
465
  let cleanupScopedExtract = null;
465
466
  let staleScope = false;
466
467
  let staleReason = null;
467
468
  try {
468
- browser = await connectPlaywright(session.cdpUrl);
469
- }
470
- catch (err) {
471
- return buildExtractContractFailureResult(session, {
472
- step: extractStep,
473
- error: 'browser_connection_failed',
474
- outcomeType: 'blocked',
475
- message: 'Extraction could not start because AgentBrowse failed to connect to the browser.',
476
- reason: err instanceof Error ? err.message : String(err),
477
- scopeRef,
478
- pageRef,
479
- });
480
- }
481
- try {
482
- let sourcePage;
483
- if (scopeTarget) {
484
- sourcePage = await resolvePlaywrightPageByRef(browser, session, pageRef);
485
- }
486
- else {
487
- const resolvedPage = await resolveCurrentPageContext(browser, session);
488
- pageRef = resolvedPage.pageRef;
489
- sourcePage = resolvedPage.page;
490
- }
491
- let page = sourcePage;
492
- let scopedResolution = null;
493
- const { url, title } = await syncSessionPage(session, pageRef, sourcePage);
494
- if (scopeTarget?.pageSignature &&
495
- normalizePageSignature(url) !== scopeTarget.pageSignature) {
496
- staleScope = true;
497
- staleReason = 'page-signature-mismatch';
498
- throw new Error('stale_scope_target_page_signature_changed');
499
- }
500
- let effectiveSelector;
501
- if (scopeTarget) {
469
+ return await withStickyOwnerBrowser(session, async (browser) => {
502
470
  try {
503
- scopedResolution = await resolveScopedExtractContext({
504
- page: sourcePage,
505
- scopeTarget,
506
- validateDomSignature: Boolean(targetScope),
471
+ let sourcePage;
472
+ if (scopeTarget) {
473
+ sourcePage = await resolvePlaywrightPageByRef(browser, session, pageRef);
474
+ }
475
+ else {
476
+ const resolvedPage = await resolveCurrentPageContext(browser, session);
477
+ pageRef = resolvedPage.pageRef;
478
+ sourcePage = resolvedPage.page;
479
+ }
480
+ let page = sourcePage;
481
+ let scopedResolution = null;
482
+ const { url, title } = await syncSessionPage(session, pageRef, sourcePage);
483
+ if (scopeTarget?.pageSignature &&
484
+ normalizePageSignature(url) !== scopeTarget.pageSignature) {
485
+ staleScope = true;
486
+ staleReason = 'page-signature-mismatch';
487
+ throw new Error('stale_scope_target_page_signature_changed');
488
+ }
489
+ let effectiveSelector;
490
+ if (scopeTarget) {
491
+ try {
492
+ scopedResolution = await resolveScopedExtractContext({
493
+ page: sourcePage,
494
+ scopeTarget,
495
+ validateDomSignature: Boolean(targetScope),
496
+ });
497
+ cleanupScopedExtract = scopedResolution.cleanup;
498
+ page = scopedResolution.page;
499
+ effectiveSelector = scopedResolution.selector;
500
+ }
501
+ catch (error) {
502
+ if (error instanceof Error &&
503
+ error.message === 'stale_scope_target_dom_signature_changed') {
504
+ staleScope = true;
505
+ staleReason = 'dom-signature-mismatch';
506
+ }
507
+ else if (surfaceScope &&
508
+ surfaceExtractScopeLifetime(surfaceScope) === 'snapshot' &&
509
+ error instanceof Error &&
510
+ error.message === 'scope_target_unresolvable') {
511
+ return buildExtractContractFailureResult(session, {
512
+ step: extractStep,
513
+ error: 'expired_extract_scope',
514
+ outcomeType: 'binding_stale',
515
+ message: 'Extraction failed because the requested snapshot scope expired before it could be rebound.',
516
+ reason: `Snapshot scope ${scopeRef} is no longer present in the current visible page state.`,
517
+ scopeRef,
518
+ pageRef,
519
+ staleScope: true,
520
+ staleReason: 'snapshot-scope-expired',
521
+ });
522
+ }
523
+ throw error;
524
+ }
525
+ }
526
+ setCurrentPage(session, pageRef);
527
+ const execution = await executeStagehandExtract({
528
+ session,
529
+ instruction,
530
+ schema: normalizedSchema.schema,
531
+ page,
532
+ selector: effectiveSelector,
533
+ degradationReason: scopedResolution?.degraded
534
+ ? scopedResolution.degradationReason
535
+ : undefined,
536
+ });
537
+ let data = execution.data;
538
+ if (scopeTarget &&
539
+ effectiveSelector &&
540
+ normalizedSchema.requestsScopedDialogText &&
541
+ data &&
542
+ typeof data === 'object' &&
543
+ !Array.isArray(data)) {
544
+ const dialogText = await readScopedDialogText(page, effectiveSelector);
545
+ if (typeof dialogText === 'string' && dialogText.trim().length > 0) {
546
+ data = {
547
+ ...data,
548
+ dialog_text: dialogText,
549
+ };
550
+ }
551
+ }
552
+ return buildExtractSuccessResult(session, extractStep, {
553
+ success: true,
554
+ ...execution,
555
+ data,
556
+ pageRef,
557
+ scopeRef,
558
+ metrics: session.runtime?.metrics,
559
+ url,
560
+ title,
507
561
  });
508
- cleanupScopedExtract = scopedResolution.cleanup;
509
- page = scopedResolution.page;
510
- effectiveSelector = scopedResolution.selector;
511
562
  }
512
- catch (error) {
513
- if (error instanceof Error &&
514
- error.message === 'stale_scope_target_dom_signature_changed') {
515
- staleScope = true;
516
- staleReason = 'dom-signature-mismatch';
563
+ catch (err) {
564
+ if (staleScope && scopeRef) {
565
+ if (targetScope) {
566
+ markTargetLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
567
+ }
568
+ else if (surfaceScope) {
569
+ markSurfaceLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
570
+ }
517
571
  }
518
- else if (surfaceScope &&
519
- surfaceExtractScopeLifetime(surfaceScope) === 'snapshot' &&
520
- error instanceof Error &&
521
- error.message === 'scope_target_unresolvable') {
572
+ if (!staleScope && err instanceof AssistiveStructuredOutputTruncatedError) {
522
573
  return buildExtractContractFailureResult(session, {
523
574
  step: extractStep,
524
- error: 'expired_extract_scope',
525
- outcomeType: 'binding_stale',
526
- message: 'Extraction failed because the requested snapshot scope expired before it could be rebound.',
527
- reason: `Snapshot scope ${scopeRef} is no longer present in the current visible page state.`,
575
+ error: 'extract_output_truncated',
576
+ outcomeType: 'blocked',
577
+ message: 'Extraction failed because the provider truncated structured output.',
578
+ reason: buildTruncationReason(err),
528
579
  scopeRef,
529
580
  pageRef,
530
- staleScope: true,
531
- staleReason: 'snapshot-scope-expired',
581
+ staleScope: false,
582
+ provider: err.provider,
583
+ model: err.model,
584
+ finishReason: err.finishReason,
585
+ maxOutputTokens: err.maxOutputTokens,
586
+ completionTokens: err.completionTokens,
532
587
  });
533
588
  }
534
- throw error;
535
- }
536
- }
537
- setCurrentPage(session, pageRef);
538
- const execution = await executeStagehandExtract({
539
- session,
540
- instruction,
541
- schema: normalizedSchema.schema,
542
- page,
543
- selector: effectiveSelector,
544
- degradationReason: scopedResolution?.degraded
545
- ? scopedResolution.degradationReason
546
- : undefined,
547
- });
548
- let data = execution.data;
549
- if (scopeTarget &&
550
- effectiveSelector &&
551
- normalizedSchema.requestsScopedDialogText &&
552
- data &&
553
- typeof data === 'object' &&
554
- !Array.isArray(data)) {
555
- const dialogText = await readScopedDialogText(page, effectiveSelector);
556
- if (typeof dialogText === 'string' && dialogText.trim().length > 0) {
557
- data = {
558
- ...data,
559
- dialog_text: dialogText,
560
- };
589
+ failureMessage = `Extract failed: ${err instanceof Error ? err.message : String(err)}`;
561
590
  }
562
- }
563
- return buildExtractSuccessResult(session, extractStep, {
564
- success: true,
565
- ...execution,
566
- data,
567
- pageRef,
568
- scopeRef,
569
- metrics: session.runtime?.metrics,
570
- url,
571
- title,
572
- });
573
- }
574
- catch (err) {
575
- if (staleScope && scopeRef) {
576
- if (targetScope) {
577
- markTargetLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
591
+ finally {
592
+ if (cleanupScopedExtract) {
593
+ await cleanupScopedExtract().catch(() => undefined);
594
+ }
578
595
  }
579
- else if (surfaceScope) {
580
- markSurfaceLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
596
+ if (failureMessage) {
597
+ return buildExtractContractFailureResult(session, {
598
+ step: extractStep,
599
+ error: staleScope ? 'stale_extract_scope' : 'extract_failed',
600
+ outcomeType: staleScope ? 'binding_stale' : 'blocked',
601
+ message: staleScope
602
+ ? 'Extraction failed because the requested scope became stale.'
603
+ : 'Extraction failed.',
604
+ reason: staleScope && scopeRef
605
+ ? `${failureMessage} (${scopeRef} marked stale: ${staleReason ?? 'stale'})`
606
+ : failureMessage.replace(/^Extract failed:\s*/, ''),
607
+ scopeRef,
608
+ pageRef,
609
+ staleScope,
610
+ staleReason: staleReason ?? undefined,
611
+ });
581
612
  }
582
- }
583
- if (!staleScope && err instanceof AssistiveStructuredOutputTruncatedError) {
584
613
  return buildExtractContractFailureResult(session, {
585
614
  step: extractStep,
586
- error: 'extract_output_truncated',
615
+ error: 'extract_failed',
587
616
  outcomeType: 'blocked',
588
- message: 'Extraction failed because the provider truncated structured output.',
589
- reason: buildTruncationReason(err),
617
+ message: 'Extraction failed.',
618
+ reason: 'Extraction did not produce a success or failure result.',
590
619
  scopeRef,
591
620
  pageRef,
592
- staleScope: false,
593
- provider: err.provider,
594
- model: err.model,
595
- finishReason: err.finishReason,
596
- maxOutputTokens: err.maxOutputTokens,
597
- completionTokens: err.completionTokens,
598
621
  });
599
- }
600
- failureMessage = `Extract failed: ${err instanceof Error ? err.message : String(err)}`;
601
- }
602
- finally {
603
- if (cleanupScopedExtract) {
604
- await cleanupScopedExtract().catch(() => undefined);
605
- }
606
- if (browser) {
607
- await disconnectPlaywright(browser);
608
- }
622
+ });
609
623
  }
610
- if (failureMessage) {
624
+ catch (err) {
625
+ const browserConnectionFailure = describeBrowserConnectionFailure(err, {
626
+ defaultMessage: 'Extraction could not start because AgentBrowse failed to connect to the browser.',
627
+ unrecoverableSessionMessage: 'Extraction could not start because the previous browser session is no longer reachable.',
628
+ });
611
629
  return buildExtractContractFailureResult(session, {
612
630
  step: extractStep,
613
- error: staleScope ? 'stale_extract_scope' : 'extract_failed',
614
- outcomeType: staleScope ? 'binding_stale' : 'blocked',
615
- message: staleScope
616
- ? 'Extraction failed because the requested scope became stale.'
617
- : 'Extraction failed.',
618
- reason: staleScope && scopeRef
619
- ? `${failureMessage} (${scopeRef} marked stale: ${staleReason ?? 'stale'})`
620
- : failureMessage.replace(/^Extract failed:\s*/, ''),
631
+ error: 'browser_connection_failed',
632
+ outcomeType: 'blocked',
633
+ message: browserConnectionFailure.message,
634
+ reason: browserConnectionFailure.reason,
621
635
  scopeRef,
622
636
  pageRef,
623
- staleScope,
624
- staleReason: staleReason ?? undefined,
625
637
  });
626
638
  }
627
- return buildExtractContractFailureResult(session, {
628
- step: extractStep,
629
- error: 'extract_failed',
630
- outcomeType: 'blocked',
631
- message: 'Extraction failed.',
632
- reason: 'Extraction did not produce a success or failure result.',
633
- scopeRef,
634
- pageRef,
635
- });
636
639
  });
637
640
  }
638
641
  /** CLI wrapper for `extractBrowser(...)` that accepts JSON text or schema objects. */