@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
@@ -0,0 +1,97 @@
1
+ import { existsSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
+ import { connectPlaywright, disconnectPlaywright } from './playwright-runtime.js';
3
+ function readArgValue(argv, name) {
4
+ const index = argv.indexOf(name);
5
+ const value = index >= 0 ? argv[index + 1] : undefined;
6
+ if (!value || value.startsWith('--')) {
7
+ throw new Error(`Missing ${name} argument.`);
8
+ }
9
+ return value;
10
+ }
11
+ function parseArgs(argv = process.argv) {
12
+ const ttlRaw = readArgValue(argv, '--ttl-ms');
13
+ const ttlMs = Number(ttlRaw);
14
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
15
+ throw new Error('Invalid --ttl-ms argument.');
16
+ }
17
+ return {
18
+ manifestPath: readArgValue(argv, '--manifest'),
19
+ cdpUrl: readArgValue(argv, '--cdp-url'),
20
+ hostId: readArgValue(argv, '--host-id'),
21
+ browserSessionId: readArgValue(argv, '--browser-session-id'),
22
+ touchPath: readArgValue(argv, '--touch-path'),
23
+ ttlMs,
24
+ };
25
+ }
26
+ function touchLeaseFile(path) {
27
+ writeFileSync(path, new Date().toISOString());
28
+ }
29
+ function readLeaseAgeMs(path) {
30
+ if (!existsSync(path)) {
31
+ return Number.POSITIVE_INFINITY;
32
+ }
33
+ try {
34
+ return Math.max(0, Date.now() - statSync(path).mtimeMs);
35
+ }
36
+ catch {
37
+ return Number.POSITIVE_INFINITY;
38
+ }
39
+ }
40
+ async function main() {
41
+ const args = parseArgs();
42
+ const browser = await connectPlaywright(args.cdpUrl);
43
+ const binding = await browser.bind(args.hostId, {
44
+ metadata: {
45
+ hostId: args.hostId,
46
+ browserSessionId: args.browserSessionId,
47
+ pid: process.pid,
48
+ startedAt: new Date().toISOString(),
49
+ },
50
+ });
51
+ touchLeaseFile(args.touchPath);
52
+ writeFileSync(args.manifestPath, JSON.stringify({
53
+ endpoint: binding.endpoint,
54
+ pid: process.pid,
55
+ startedAt: new Date().toISOString(),
56
+ }, null, 2));
57
+ let shuttingDown = false;
58
+ let ttlInterval = null;
59
+ const shutdown = async () => {
60
+ if (shuttingDown) {
61
+ return;
62
+ }
63
+ shuttingDown = true;
64
+ if (ttlInterval) {
65
+ clearInterval(ttlInterval);
66
+ ttlInterval = null;
67
+ }
68
+ await browser.unbind().catch(() => undefined);
69
+ await disconnectPlaywright(browser).catch(() => undefined);
70
+ rmSync(args.touchPath, { force: true });
71
+ process.exit(0);
72
+ };
73
+ const reapExpiredOwner = () => {
74
+ if (readLeaseAgeMs(args.touchPath) >= args.ttlMs) {
75
+ void shutdown();
76
+ }
77
+ };
78
+ browser.on('disconnected', () => {
79
+ void shutdown();
80
+ });
81
+ process.on('SIGINT', () => {
82
+ void shutdown();
83
+ });
84
+ process.on('SIGTERM', () => {
85
+ void shutdown();
86
+ });
87
+ const ttlCheckMs = Math.max(5_000, Math.min(Math.floor(args.ttlMs / 6), 60_000));
88
+ ttlInterval = setInterval(reapExpiredOwner, ttlCheckMs);
89
+ ttlInterval.unref?.();
90
+ reapExpiredOwner();
91
+ await new Promise(() => { });
92
+ }
93
+ void main().catch((error) => {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ console.error(message);
96
+ process.exit(1);
97
+ });
@@ -0,0 +1,15 @@
1
+ import { type Browser } from 'playwright-core';
2
+ import { type BrowserSessionState } from './browser-session-state.js';
3
+ export type RestoreBrowserSessionOwnerResult = {
4
+ success: true;
5
+ session: BrowserSessionState;
6
+ restored: boolean;
7
+ } | {
8
+ success: false;
9
+ reason: 'sticky_owner_unrecoverable';
10
+ };
11
+ export declare function initializeBrowserSessionOwner(session: BrowserSessionState): Promise<BrowserSessionState>;
12
+ export declare function terminateBrowserSessionOwner(session: BrowserSessionState | null): Promise<void>;
13
+ export declare function restoreBrowserSessionOwner(session: BrowserSessionState): Promise<RestoreBrowserSessionOwnerResult>;
14
+ export declare function withStickyOwnerBrowser<TResult>(session: BrowserSessionState, operation: (browser: Browser) => Promise<TResult>): Promise<TResult>;
15
+ //# sourceMappingURL=sticky-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-owner.d.ts","sourceRoot":"","sources":["../src/sticky-owner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAIL,KAAK,mBAAmB,EAGzB,MAAM,4BAA4B,CAAC;AAWpC,MAAM,MAAM,gCAAgC,GACxC;IACE,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAC;AAyVN,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAU9B;AAED,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAClC,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,gCAAgC,CAAC,CAiG3C;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAClD,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAChD,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
@@ -0,0 +1,431 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { chromium } from 'playwright-core';
8
+ import { buildStickyOwnerMetadata, isSessionAlive, resolveBrowserSessionId, } from './browser-session-state.js';
9
+ import { connectPlaywright, disconnectPlaywright } from './playwright-runtime.js';
10
+ const HOST_READY_TIMEOUT_MS = 5_000;
11
+ const HOST_READY_POLL_MS = 50;
12
+ const HOST_CONNECT_TIMEOUT_MS = 3_000;
13
+ const HOST_STOP_TIMEOUT_MS = 1_000;
14
+ const DEFAULT_STICKY_OWNER_TTL_MS = 30 * 60 * 1_000;
15
+ const STICKY_OWNER_HEARTBEAT_FLOOR_MS = 5_000;
16
+ const STICKY_OWNER_HEARTBEAT_CEILING_MS = 60_000;
17
+ const inProcessOwners = new WeakMap();
18
+ function resolveStickyOwnerBootstrapMode() {
19
+ const explicit = process.env.AGENTBROWSE_STICKY_OWNER_MODE?.trim().toLowerCase();
20
+ if (explicit === 'detached_process') {
21
+ return 'detached_process';
22
+ }
23
+ if (explicit === 'in_process') {
24
+ return 'in_process';
25
+ }
26
+ if (process.env.VITEST) {
27
+ return 'in_process';
28
+ }
29
+ return 'detached_process';
30
+ }
31
+ function createHostId() {
32
+ return `owner_${randomUUID().replace(/-/g, '')}`;
33
+ }
34
+ function resolveStickyOwnerTtlMs() {
35
+ const raw = process.env.AGENTBROWSE_STICKY_OWNER_TTL_MS?.trim();
36
+ if (!raw) {
37
+ return DEFAULT_STICKY_OWNER_TTL_MS;
38
+ }
39
+ const ttlMs = Number(raw);
40
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
41
+ return DEFAULT_STICKY_OWNER_TTL_MS;
42
+ }
43
+ return ttlMs;
44
+ }
45
+ function getStickyOwnerTouchPath(hostId) {
46
+ return join(tmpdir(), `agentbrowse-sticky-owner-${hostId}.touch`);
47
+ }
48
+ function buildInProcessStickyOwner(session, options = {}) {
49
+ return buildStickyOwnerMetadata({
50
+ hostId: options.hostId ?? session.stickyOwner?.hostId ?? createHostId(),
51
+ state: options.state ?? 'active',
52
+ startedAt: options.startedAt ?? session.stickyOwner?.startedAt ?? new Date().toISOString(),
53
+ browserSessionId: resolveBrowserSessionId(session),
54
+ transport: {
55
+ type: 'in_process',
56
+ },
57
+ });
58
+ }
59
+ function readStickyOwnerLastUsedMs(stickyOwner) {
60
+ if (stickyOwner.touchPath) {
61
+ try {
62
+ if (existsSync(stickyOwner.touchPath)) {
63
+ return statSync(stickyOwner.touchPath).mtimeMs;
64
+ }
65
+ }
66
+ catch {
67
+ // Fall through to metadata timestamps.
68
+ }
69
+ }
70
+ const candidate = stickyOwner.lastUsedAt ?? stickyOwner.startedAt;
71
+ const parsed = Date.parse(candidate);
72
+ return Number.isFinite(parsed) ? parsed : 0;
73
+ }
74
+ function isStickyOwnerExpired(stickyOwner) {
75
+ if (stickyOwner.transport.type !== 'playwright_bind') {
76
+ return false;
77
+ }
78
+ const ttlMs = stickyOwner.ttlMs ?? DEFAULT_STICKY_OWNER_TTL_MS;
79
+ return Date.now() - readStickyOwnerLastUsedMs(stickyOwner) >= ttlMs;
80
+ }
81
+ function removeStickyOwnerTouchFile(stickyOwner) {
82
+ if (!stickyOwner?.touchPath) {
83
+ return;
84
+ }
85
+ rmSync(stickyOwner.touchPath, { force: true });
86
+ }
87
+ function touchStickyOwnerLease(session) {
88
+ if (!session.stickyOwner || session.stickyOwner.transport.type !== 'playwright_bind') {
89
+ return;
90
+ }
91
+ const touchedAt = new Date().toISOString();
92
+ if (session.stickyOwner.touchPath) {
93
+ writeFileSync(session.stickyOwner.touchPath, touchedAt);
94
+ }
95
+ session.stickyOwner = {
96
+ ...session.stickyOwner,
97
+ lastUsedAt: touchedAt,
98
+ };
99
+ }
100
+ function startStickyOwnerLeaseHeartbeat(session) {
101
+ if (!session.stickyOwner || session.stickyOwner.transport.type !== 'playwright_bind') {
102
+ return () => undefined;
103
+ }
104
+ const ttlMs = session.stickyOwner.ttlMs ?? DEFAULT_STICKY_OWNER_TTL_MS;
105
+ const heartbeatMs = Math.max(STICKY_OWNER_HEARTBEAT_FLOOR_MS, Math.min(Math.floor(ttlMs / 3), STICKY_OWNER_HEARTBEAT_CEILING_MS));
106
+ const timer = setInterval(() => {
107
+ try {
108
+ touchStickyOwnerLease(session);
109
+ }
110
+ catch {
111
+ // Best-effort heartbeat; restore path will handle stale or dead owners later.
112
+ }
113
+ }, heartbeatMs);
114
+ timer.unref?.();
115
+ return () => clearInterval(timer);
116
+ }
117
+ function markStickyOwnerState(session, state) {
118
+ if (!session.stickyOwner) {
119
+ return;
120
+ }
121
+ session.stickyOwner = {
122
+ ...session.stickyOwner,
123
+ state,
124
+ };
125
+ }
126
+ function isProcessAlive(pid) {
127
+ if (typeof pid !== 'number' || !Number.isFinite(pid) || pid <= 0) {
128
+ return false;
129
+ }
130
+ try {
131
+ process.kill(pid, 0);
132
+ return true;
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
138
+ async function sleep(ms) {
139
+ await new Promise((resolve) => setTimeout(resolve, ms));
140
+ }
141
+ async function waitForProcessExit(pid, timeoutMs) {
142
+ const deadline = Date.now() + timeoutMs;
143
+ while (Date.now() < deadline) {
144
+ if (!isProcessAlive(pid)) {
145
+ return true;
146
+ }
147
+ await sleep(50);
148
+ }
149
+ return !isProcessAlive(pid);
150
+ }
151
+ async function canConnectToStickyOwnerEndpoint(endpoint) {
152
+ let browser = null;
153
+ try {
154
+ browser = await chromium.connect(endpoint, {
155
+ timeout: HOST_CONNECT_TIMEOUT_MS,
156
+ });
157
+ return true;
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ finally {
163
+ if (browser) {
164
+ await disconnectPlaywright(browser);
165
+ }
166
+ }
167
+ }
168
+ function getStickyOwnerHostEntryPath() {
169
+ const currentPath = fileURLToPath(import.meta.url);
170
+ const suffix = currentPath.endsWith('.ts') ? '.ts' : '.js';
171
+ return fileURLToPath(new URL(`./sticky-owner-host-entry${suffix}`, import.meta.url));
172
+ }
173
+ function getStickyOwnerHostSpawnArgs(entryPath, manifestPath, cdpUrl, hostId, browserSessionId, touchPath, ttlMs) {
174
+ const args = [
175
+ entryPath,
176
+ '--manifest',
177
+ manifestPath,
178
+ '--cdp-url',
179
+ cdpUrl,
180
+ '--host-id',
181
+ hostId,
182
+ '--browser-session-id',
183
+ browserSessionId,
184
+ '--touch-path',
185
+ touchPath,
186
+ '--ttl-ms',
187
+ String(ttlMs),
188
+ ];
189
+ if (entryPath.endsWith('.ts')) {
190
+ return ['--import', 'tsx', ...args];
191
+ }
192
+ return args;
193
+ }
194
+ async function waitForStickyOwnerManifest(manifestPath) {
195
+ const deadline = Date.now() + HOST_READY_TIMEOUT_MS;
196
+ while (Date.now() < deadline) {
197
+ if (existsSync(manifestPath)) {
198
+ const payload = JSON.parse(readFileSync(manifestPath, 'utf-8'));
199
+ if (typeof payload.endpoint === 'string' &&
200
+ payload.endpoint.trim().length > 0 &&
201
+ typeof payload.pid === 'number' &&
202
+ Number.isFinite(payload.pid) &&
203
+ payload.pid > 0 &&
204
+ typeof payload.startedAt === 'string' &&
205
+ payload.startedAt.trim().length > 0) {
206
+ rmSync(manifestPath, { force: true });
207
+ return {
208
+ endpoint: payload.endpoint,
209
+ pid: payload.pid,
210
+ startedAt: payload.startedAt,
211
+ };
212
+ }
213
+ }
214
+ await sleep(HOST_READY_POLL_MS);
215
+ }
216
+ throw new Error('sticky_owner_bootstrap_timeout');
217
+ }
218
+ async function bootstrapDetachedStickyOwner(session) {
219
+ const hostId = session.stickyOwner?.hostId ?? createHostId();
220
+ const browserSessionId = resolveBrowserSessionId(session);
221
+ const ttlMs = session.stickyOwner?.ttlMs ?? resolveStickyOwnerTtlMs();
222
+ const manifestPath = join(tmpdir(), `agentbrowse-sticky-owner-${hostId}.json`);
223
+ const touchPath = session.stickyOwner?.touchPath ?? getStickyOwnerTouchPath(hostId);
224
+ const touchedAt = new Date().toISOString();
225
+ writeFileSync(touchPath, touchedAt);
226
+ const entryPath = getStickyOwnerHostEntryPath();
227
+ const child = spawn(process.execPath, getStickyOwnerHostSpawnArgs(entryPath, manifestPath, session.cdpUrl, hostId, browserSessionId, touchPath, ttlMs), {
228
+ detached: true,
229
+ stdio: 'ignore',
230
+ });
231
+ child.unref();
232
+ try {
233
+ const ready = await waitForStickyOwnerManifest(manifestPath);
234
+ return buildStickyOwnerMetadata({
235
+ hostId,
236
+ state: 'active',
237
+ startedAt: ready.startedAt,
238
+ lastUsedAt: touchedAt,
239
+ ttlMs,
240
+ touchPath,
241
+ browserSessionId,
242
+ pid: ready.pid,
243
+ transport: {
244
+ type: 'playwright_bind',
245
+ endpoint: ready.endpoint,
246
+ mode: 'pipe',
247
+ },
248
+ });
249
+ }
250
+ catch (error) {
251
+ if (isProcessAlive(child.pid)) {
252
+ process.kill(child.pid, 'SIGTERM');
253
+ await waitForProcessExit(child.pid, HOST_STOP_TIMEOUT_MS).catch(() => undefined);
254
+ }
255
+ rmSync(manifestPath, { force: true });
256
+ rmSync(touchPath, { force: true });
257
+ throw error;
258
+ }
259
+ }
260
+ async function ensureInProcessOwner(session) {
261
+ const existing = inProcessOwners.get(session);
262
+ if (existing) {
263
+ return existing;
264
+ }
265
+ const browser = await connectPlaywright(session.cdpUrl);
266
+ const owner = {
267
+ browser,
268
+ hostId: session.stickyOwner?.hostId ?? createHostId(),
269
+ startedAt: session.stickyOwner?.startedAt ?? new Date().toISOString(),
270
+ };
271
+ inProcessOwners.set(session, owner);
272
+ session.stickyOwner = buildInProcessStickyOwner(session, {
273
+ hostId: owner.hostId,
274
+ startedAt: owner.startedAt,
275
+ });
276
+ return owner;
277
+ }
278
+ export async function initializeBrowserSessionOwner(session) {
279
+ if (resolveStickyOwnerBootstrapMode() === 'in_process') {
280
+ session.stickyOwner = buildInProcessStickyOwner(session, {
281
+ state: 'active',
282
+ });
283
+ return session;
284
+ }
285
+ session.stickyOwner = await bootstrapDetachedStickyOwner(session);
286
+ return session;
287
+ }
288
+ export async function terminateBrowserSessionOwner(session) {
289
+ if (!session?.stickyOwner) {
290
+ return;
291
+ }
292
+ if (session.stickyOwner.transport.type === 'in_process') {
293
+ const owner = inProcessOwners.get(session);
294
+ inProcessOwners.delete(session);
295
+ if (owner) {
296
+ await disconnectPlaywright(owner.browser);
297
+ }
298
+ markStickyOwnerState(session, 'dead');
299
+ return;
300
+ }
301
+ const ownerPid = session.stickyOwner.pid;
302
+ if (isProcessAlive(ownerPid)) {
303
+ process.kill(ownerPid, 'SIGTERM');
304
+ const exited = await waitForProcessExit(ownerPid, HOST_STOP_TIMEOUT_MS);
305
+ if (!exited && isProcessAlive(ownerPid)) {
306
+ process.kill(ownerPid, 'SIGKILL');
307
+ await waitForProcessExit(ownerPid, HOST_STOP_TIMEOUT_MS).catch(() => undefined);
308
+ }
309
+ }
310
+ removeStickyOwnerTouchFile(session.stickyOwner);
311
+ markStickyOwnerState(session, 'dead');
312
+ }
313
+ export async function restoreBrowserSessionOwner(session) {
314
+ const stickyOwner = session.stickyOwner;
315
+ if (!stickyOwner) {
316
+ if (resolveStickyOwnerBootstrapMode() === 'in_process') {
317
+ try {
318
+ await ensureInProcessOwner(session);
319
+ return {
320
+ success: true,
321
+ session,
322
+ restored: true,
323
+ };
324
+ }
325
+ catch {
326
+ return {
327
+ success: false,
328
+ reason: 'sticky_owner_unrecoverable',
329
+ };
330
+ }
331
+ }
332
+ if (!(await isSessionAlive(session))) {
333
+ return {
334
+ success: false,
335
+ reason: 'sticky_owner_unrecoverable',
336
+ };
337
+ }
338
+ try {
339
+ session.stickyOwner = await bootstrapDetachedStickyOwner(session);
340
+ return {
341
+ success: true,
342
+ session,
343
+ restored: true,
344
+ };
345
+ }
346
+ catch {
347
+ return {
348
+ success: false,
349
+ reason: 'sticky_owner_unrecoverable',
350
+ };
351
+ }
352
+ }
353
+ if (stickyOwner.transport.type === 'in_process') {
354
+ try {
355
+ await ensureInProcessOwner(session);
356
+ return {
357
+ success: true,
358
+ session,
359
+ restored: false,
360
+ };
361
+ }
362
+ catch {
363
+ return {
364
+ success: false,
365
+ reason: 'sticky_owner_unrecoverable',
366
+ };
367
+ }
368
+ }
369
+ if (stickyOwner.state === 'active' &&
370
+ !isStickyOwnerExpired(stickyOwner) &&
371
+ isProcessAlive(stickyOwner.pid) &&
372
+ (await canConnectToStickyOwnerEndpoint(stickyOwner.transport.endpoint))) {
373
+ return {
374
+ success: true,
375
+ session,
376
+ restored: false,
377
+ };
378
+ }
379
+ await terminateBrowserSessionOwner(session);
380
+ if (!(await isSessionAlive(session))) {
381
+ return {
382
+ success: false,
383
+ reason: 'sticky_owner_unrecoverable',
384
+ };
385
+ }
386
+ try {
387
+ markStickyOwnerState(session, 'recovering');
388
+ session.stickyOwner = await bootstrapDetachedStickyOwner(session);
389
+ return {
390
+ success: true,
391
+ session,
392
+ restored: true,
393
+ };
394
+ }
395
+ catch {
396
+ session.stickyOwner = {
397
+ ...stickyOwner,
398
+ state: 'dead',
399
+ };
400
+ return {
401
+ success: false,
402
+ reason: 'sticky_owner_unrecoverable',
403
+ };
404
+ }
405
+ }
406
+ export async function withStickyOwnerBrowser(session, operation) {
407
+ const stickyOwner = session.stickyOwner;
408
+ if (!stickyOwner || stickyOwner.transport.type === 'playwright_bind') {
409
+ const restored = await restoreBrowserSessionOwner(session);
410
+ if (!restored.success || !session.stickyOwner) {
411
+ throw new Error('sticky_owner_unrecoverable');
412
+ }
413
+ }
414
+ if (session.stickyOwner?.transport.type === 'playwright_bind') {
415
+ touchStickyOwnerLease(session);
416
+ const browser = await chromium.connect(session.stickyOwner.transport.endpoint, {
417
+ timeout: HOST_CONNECT_TIMEOUT_MS,
418
+ });
419
+ const stopHeartbeat = startStickyOwnerLeaseHeartbeat(session);
420
+ try {
421
+ return await operation(browser);
422
+ }
423
+ finally {
424
+ stopHeartbeat();
425
+ touchStickyOwnerLease(session);
426
+ await disconnectPlaywright(browser);
427
+ }
428
+ }
429
+ const owner = await ensureInProcessOwner(session);
430
+ return operation(owner.browser);
431
+ }
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Stable testing helpers for packages that wrap AgentBrowse.
3
3
  */
4
+ export { createFixtureGroupStore, createFixtureMatchStore, createFixtureResolver, fixtureResolvedArtifact, fixtureResolvedValue, } from './match-resolve-fill.test-support.js';
4
5
  export { installFetchBackedTestAssistiveRuntime, uninstallTestAssistiveRuntime, } from './assistive-runtime.test-support.js';
5
6
  //# sourceMappingURL=testing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,sCAAsC,EACtC,6BAA6B,GAC9B,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,sCAAsC,EACtC,6BAA6B,GAC9B,MAAM,qCAAqC,CAAC"}
package/dist/testing.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /**
2
2
  * Stable testing helpers for packages that wrap AgentBrowse.
3
3
  */
4
+ export { createFixtureGroupStore, createFixtureMatchStore, createFixtureResolver, fixtureResolvedArtifact, fixtureResolvedValue, } from './match-resolve-fill.test-support.js';
4
5
  export { installFetchBackedTestAssistiveRuntime, uninstallTestAssistiveRuntime, } from './assistive-runtime.test-support.js';
package/docs/README.md CHANGED
@@ -9,30 +9,47 @@ Public guides for `@mercuryo-ai/agentbrowse`.
9
9
  turns the Quick Start into a working model for every main API.
10
10
  3. [api-reference.md](./api-reference.md) — lookup for types, result
11
11
  shapes, and error codes (keep open while coding).
12
- 4. Only-when-needed: [configuration.md](./configuration.md),
12
+ 4. [match-resolve-fill.md](./match-resolve-fill.md) when your runtime
13
+ needs to decide which caller-supplied value belongs in which
14
+ observed field.
15
+ 5. Only-when-needed: [configuration.md](./configuration.md),
13
16
  [assistive-runtime.md](./assistive-runtime.md),
14
17
  [protected-fill.md](./protected-fill.md).
15
- 5. Before shipping: [integration-checklist.md](./integration-checklist.md)
18
+ 6. Before shipping: [integration-checklist.md](./integration-checklist.md)
16
19
  and [troubleshooting.md](./troubleshooting.md).
17
20
 
18
21
  Full list:
19
22
 
20
23
  - [getting-started.md](./getting-started.md)
21
24
  The first guide to read after the package README. It explains the normal
22
- session flow and what each main API is for.
25
+ session flow and what each main API is for, including the
26
+ `match` / `resolve` / `fill` primitives.
23
27
  - [api-reference.md](./api-reference.md)
24
- Compact reference for the public API, result shapes, observe payloads, and
25
- extraction schema input.
28
+ Compact reference for the public API, result shapes, observe payloads,
29
+ match/resolve/fill types, and extraction schema input.
30
+
31
+ ## Match / resolve / fill
32
+
33
+ - [match-resolve-fill.md](./match-resolve-fill.md)
34
+ How the three primitives decide which value fits which observed field,
35
+ how to wire a resolver adapter, and the design rules (no raw values in
36
+ public results, stable resolved refs, adapter boundary).
37
+ - [protected-fill.md](./protected-fill.md)
38
+ How the low-level `fillProtectedForm(...)` path works and when to
39
+ prefer it over the `fill(session, form, plan, { resolver })` route.
40
+ - [testing.md](./testing.md)
41
+ Stable fixture builders for match/resolve/fill unit tests, plus the
42
+ fetch-backed assistive runtime helper for packages that wrap
43
+ AgentBrowse.
44
+
45
+ ## Runtime and integration
46
+
26
47
  - [configuration.md](./configuration.md)
27
48
  Persistence, proxy, diagnostics, and client configuration.
28
49
  - [assistive-runtime.md](./assistive-runtime.md)
29
- How to connect an OpenAI-compatible LLM backend for extraction and better
30
- goal-based page understanding.
31
- - [protected-fill.md](./protected-fill.md)
32
- How protected fill works and when to use it instead of a normal fill action.
50
+ How to connect an OpenAI-compatible LLM backend for extraction and
51
+ better goal-based page understanding.
33
52
  - [integration-checklist.md](./integration-checklist.md)
34
53
  Checklist for packages and services that integrate AgentBrowse.
35
- - [testing.md](./testing.md)
36
- Stable testing helpers for packages that wrap AgentBrowse.
37
54
  - [troubleshooting.md](./troubleshooting.md)
38
55
  Common launch, observe, extract, and anti-bot issues.