@ank1015/llm-extension 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +214 -0
  2. package/dist/chrome/background.js +244 -0
  3. package/dist/chrome/manifest.json +23 -0
  4. package/dist/index.d.ts +8 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +8 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/native/host-wrapper.sh +11 -0
  9. package/dist/native/host.d.ts +2 -0
  10. package/dist/native/host.d.ts.map +1 -0
  11. package/dist/native/host.js +21 -0
  12. package/dist/native/host.js.map +1 -0
  13. package/dist/native/server.d.ts +28 -0
  14. package/dist/native/server.d.ts.map +1 -0
  15. package/dist/native/server.js +112 -0
  16. package/dist/native/server.js.map +1 -0
  17. package/dist/native/stdio.d.ts +21 -0
  18. package/dist/native/stdio.d.ts.map +1 -0
  19. package/dist/native/stdio.js +94 -0
  20. package/dist/native/stdio.js.map +1 -0
  21. package/dist/protocol/constants.d.ts +26 -0
  22. package/dist/protocol/constants.d.ts.map +1 -0
  23. package/dist/protocol/constants.js +26 -0
  24. package/dist/protocol/constants.js.map +1 -0
  25. package/dist/protocol/types.d.ts +39 -0
  26. package/dist/protocol/types.d.ts.map +1 -0
  27. package/dist/protocol/types.js +3 -0
  28. package/dist/protocol/types.js.map +1 -0
  29. package/dist/sdk/action/diff.d.ts +7 -0
  30. package/dist/sdk/action/diff.d.ts.map +1 -0
  31. package/dist/sdk/action/diff.js +54 -0
  32. package/dist/sdk/action/diff.js.map +1 -0
  33. package/dist/sdk/action/helpers.d.ts +23 -0
  34. package/dist/sdk/action/helpers.d.ts.map +1 -0
  35. package/dist/sdk/action/helpers.js +93 -0
  36. package/dist/sdk/action/helpers.js.map +1 -0
  37. package/dist/sdk/action/index.d.ts +6 -0
  38. package/dist/sdk/action/index.d.ts.map +1 -0
  39. package/dist/sdk/action/index.js +4 -0
  40. package/dist/sdk/action/index.js.map +1 -0
  41. package/dist/sdk/action/script.d.ts +4 -0
  42. package/dist/sdk/action/script.d.ts.map +1 -0
  43. package/dist/sdk/action/script.js +359 -0
  44. package/dist/sdk/action/script.js.map +1 -0
  45. package/dist/sdk/action/types.d.ts +47 -0
  46. package/dist/sdk/action/types.d.ts.map +1 -0
  47. package/dist/sdk/action/types.js +2 -0
  48. package/dist/sdk/action/types.js.map +1 -0
  49. package/dist/sdk/client.d.ts +42 -0
  50. package/dist/sdk/client.d.ts.map +1 -0
  51. package/dist/sdk/client.js +118 -0
  52. package/dist/sdk/client.js.map +1 -0
  53. package/dist/sdk/connect.d.ts +29 -0
  54. package/dist/sdk/connect.d.ts.map +1 -0
  55. package/dist/sdk/connect.js +99 -0
  56. package/dist/sdk/connect.js.map +1 -0
  57. package/dist/sdk/index.d.ts +17 -0
  58. package/dist/sdk/index.d.ts.map +1 -0
  59. package/dist/sdk/index.js +19 -0
  60. package/dist/sdk/index.js.map +1 -0
  61. package/dist/sdk/observe/helpers.d.ts +16 -0
  62. package/dist/sdk/observe/helpers.d.ts.map +1 -0
  63. package/dist/sdk/observe/helpers.js +278 -0
  64. package/dist/sdk/observe/helpers.js.map +1 -0
  65. package/dist/sdk/observe/index.d.ts +6 -0
  66. package/dist/sdk/observe/index.d.ts.map +1 -0
  67. package/dist/sdk/observe/index.js +4 -0
  68. package/dist/sdk/observe/index.js.map +1 -0
  69. package/dist/sdk/observe/script.d.ts +7 -0
  70. package/dist/sdk/observe/script.d.ts.map +1 -0
  71. package/dist/sdk/observe/script.js +680 -0
  72. package/dist/sdk/observe/script.js.map +1 -0
  73. package/dist/sdk/observe/storage.d.ts +39 -0
  74. package/dist/sdk/observe/storage.d.ts.map +1 -0
  75. package/dist/sdk/observe/storage.js +83 -0
  76. package/dist/sdk/observe/storage.js.map +1 -0
  77. package/dist/sdk/observe/types.d.ts +140 -0
  78. package/dist/sdk/observe/types.d.ts.map +1 -0
  79. package/dist/sdk/observe/types.js +2 -0
  80. package/dist/sdk/observe/types.js.map +1 -0
  81. package/dist/sdk/window.d.ts +97 -0
  82. package/dist/sdk/window.d.ts.map +1 -0
  83. package/dist/sdk/window.js +795 -0
  84. package/dist/sdk/window.js.map +1 -0
  85. package/package.json +55 -0
@@ -0,0 +1,795 @@
1
+ import { OBSERVE_BEFORE_ACT_MESSAGE, buildActionScript, buildSelectorCandidates, parseActionExecutionResult, resolveObservedTarget, summarizeActionDomChanges, } from './action/index.js';
2
+ import { connect } from './connect.js';
3
+ import { buildObserveScript, createObserveView, normalizeObserveOptions, parseObserveSnapshot, persistObserveSnapshot, readLatestObserveSnapshot, renderObserveMarkdown, } from './observe/index.js';
4
+ const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
5
+ const DEFAULT_ACTION_TIMEOUT_MS = 15_000;
6
+ const DEFAULT_DOWNLOAD_TIMEOUT_MS = 120_000;
7
+ const DEFAULT_GET_PAGE_TIMEOUT_MS = 30_000;
8
+ const DEFAULT_HTML_CONVERTER_URL = 'http://localhost:8080/convert';
9
+ const GET_PAGE_SERVICE_ERROR = 'service not running use observe tool';
10
+ const TAB_POLL_INTERVAL_MS = 100;
11
+ const TAB_SETTLE_DELAY_MS = 200;
12
+ /**
13
+ * Agent-facing window wrapper.
14
+ *
15
+ * If a window ID is provided, it is used directly.
16
+ * Otherwise, a new Chrome window is created during initialization.
17
+ */
18
+ export class Window {
19
+ chromePromise = null;
20
+ semanticFilter;
21
+ windowId = null;
22
+ /** Resolves when constructor initialization completes. */
23
+ ready;
24
+ constructor(windowId, semanticFilter) {
25
+ const resolvedWindowId = typeof windowId === 'number' ? windowId : undefined;
26
+ const resolvedSemanticFilter = (typeof windowId === 'function' ? windowId : semanticFilter) ?? ((input) => input);
27
+ this.semanticFilter = resolvedSemanticFilter;
28
+ if (typeof resolvedWindowId === 'number') {
29
+ this.windowId = resolvedWindowId;
30
+ this.ready = Promise.resolve();
31
+ return;
32
+ }
33
+ this.ready = (async () => {
34
+ const chrome = await this.getChrome();
35
+ const created = (await chrome.call('windows.create', {}));
36
+ if (typeof created?.id !== 'number') {
37
+ throw new Error('Failed to create Chrome window');
38
+ }
39
+ this.windowId = created.id;
40
+ })();
41
+ }
42
+ async open(url, options) {
43
+ if (!url) {
44
+ throw new Error('open requires a non-empty URL');
45
+ }
46
+ const chrome = await this.getChrome();
47
+ const windowId = await this.getWindowId();
48
+ const active = options?.active ?? true;
49
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
50
+ let targetTabId;
51
+ if (typeof options?.tabId === 'number') {
52
+ targetTabId = options.tabId;
53
+ await this.assertTabInWindow(targetTabId);
54
+ await chrome.call('tabs.update', targetTabId, { url, active });
55
+ }
56
+ else if (options?.newTab === true) {
57
+ const created = (await chrome.call('tabs.create', {
58
+ windowId,
59
+ url,
60
+ active,
61
+ }));
62
+ if (typeof created.id !== 'number') {
63
+ throw new Error('Failed to create tab');
64
+ }
65
+ targetTabId = created.id;
66
+ }
67
+ else {
68
+ const current = await this.current();
69
+ if (typeof current?.id === 'number') {
70
+ targetTabId = current.id;
71
+ await chrome.call('tabs.update', targetTabId, { url, active });
72
+ }
73
+ else {
74
+ const created = (await chrome.call('tabs.create', {
75
+ windowId,
76
+ url,
77
+ active,
78
+ }));
79
+ if (typeof created.id !== 'number') {
80
+ throw new Error('Failed to create tab');
81
+ }
82
+ targetTabId = created.id;
83
+ }
84
+ }
85
+ await this.waitForTabLoad(targetTabId, timeoutMs);
86
+ return this.assertTabInWindow(targetTabId);
87
+ }
88
+ async tabs() {
89
+ const chrome = await this.getChrome();
90
+ const windowId = await this.getWindowId();
91
+ return (await chrome.call('tabs.query', { windowId }));
92
+ }
93
+ async switchTab(tabId) {
94
+ const chrome = await this.getChrome();
95
+ await this.assertTabInWindow(tabId);
96
+ await chrome.call('tabs.update', tabId, { active: true });
97
+ await this.waitForTabLoad(tabId);
98
+ return this.assertTabInWindow(tabId);
99
+ }
100
+ async closeTab(tabId) {
101
+ const chrome = await this.getChrome();
102
+ const targetTabId = await this.resolveTargetTabId(tabId);
103
+ await chrome.call('tabs.remove', targetTabId);
104
+ await this.waitForTabClosed(targetTabId);
105
+ }
106
+ async back(tabId) {
107
+ const chrome = await this.getChrome();
108
+ const targetTabId = await this.resolveTargetTabId(tabId);
109
+ try {
110
+ await chrome.call('tabs.goBack', targetTabId);
111
+ }
112
+ catch (error) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ // `tabs.goBack` may fail even when history APIs are available.
115
+ // Fall back to a direct history.back() in the page context.
116
+ if (message.includes('Cannot find a previous page in history') ||
117
+ message.includes('Cannot find a next page in history')) {
118
+ await chrome.call('debugger.evaluate', {
119
+ tabId: targetTabId,
120
+ code: 'history.back(); "ok";',
121
+ });
122
+ }
123
+ else {
124
+ throw error;
125
+ }
126
+ }
127
+ await this.waitForTabLoad(targetTabId);
128
+ return this.assertTabInWindow(targetTabId);
129
+ }
130
+ async reload(tabId) {
131
+ const chrome = await this.getChrome();
132
+ const targetTabId = await this.resolveTargetTabId(tabId);
133
+ await chrome.call('tabs.reload', targetTabId);
134
+ await this.waitForTabLoad(targetTabId);
135
+ return this.assertTabInWindow(targetTabId);
136
+ }
137
+ async screenshot(options) {
138
+ const chrome = await this.getChrome();
139
+ const targetTabId = await this.resolveTargetTabId(options?.tabId);
140
+ const fullPage = options?.fullPage ?? false;
141
+ await this.waitForTabLoad(targetTabId);
142
+ let attachedByThisMethod = false;
143
+ try {
144
+ const attachResult = (await chrome.call('debugger.attach', {
145
+ tabId: targetTabId,
146
+ }));
147
+ attachedByThisMethod = attachResult.attached === true;
148
+ await chrome.call('debugger.sendCommand', {
149
+ tabId: targetTabId,
150
+ method: 'Page.enable',
151
+ });
152
+ const params = fullPage
153
+ ? await this.getFullPageCaptureParams(targetTabId)
154
+ : {
155
+ format: 'png',
156
+ };
157
+ const result = (await chrome.call('debugger.sendCommand', {
158
+ tabId: targetTabId,
159
+ method: 'Page.captureScreenshot',
160
+ params,
161
+ }));
162
+ if (typeof result.data !== 'string' || result.data.length === 0) {
163
+ throw new Error('Screenshot capture returned no image data');
164
+ }
165
+ return result.data;
166
+ }
167
+ finally {
168
+ if (attachedByThisMethod) {
169
+ try {
170
+ await chrome.call('debugger.detach', { tabId: targetTabId });
171
+ }
172
+ catch {
173
+ // Ignore detach errors (tab may have closed)
174
+ }
175
+ }
176
+ }
177
+ }
178
+ async download(url, downloadPath, options) {
179
+ if (!url.trim()) {
180
+ throw new Error('download requires a non-empty url');
181
+ }
182
+ if (!downloadPath.trim()) {
183
+ throw new Error('download requires a non-empty downloadPath');
184
+ }
185
+ const chrome = await this.getChrome();
186
+ const downloadId = (await chrome.call('downloads.download', {
187
+ url,
188
+ filename: downloadPath,
189
+ saveAs: options?.saveAs ?? false,
190
+ }));
191
+ if (typeof downloadId !== 'number') {
192
+ throw new Error('Failed to start download');
193
+ }
194
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS;
195
+ const item = await this.waitForDownloadCompletion(downloadId, timeoutMs);
196
+ if (item.state === 'complete') {
197
+ const resolvedPath = item.filename ?? downloadPath;
198
+ return `Downloaded to ${resolvedPath}`;
199
+ }
200
+ if (item.state === 'interrupted') {
201
+ const reason = item.error ? `: ${item.error}` : '';
202
+ return `Download interrupted${reason}`;
203
+ }
204
+ return 'Download state unknown';
205
+ }
206
+ async evaluate(code, options) {
207
+ if (!code.trim()) {
208
+ throw new Error('evaluate requires non-empty code');
209
+ }
210
+ const chrome = await this.getChrome();
211
+ const targetTabId = await this.resolveTargetTabId(options?.tabId);
212
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS;
213
+ await this.waitForTabLoad(targetTabId, timeoutMs);
214
+ const evaluation = (await chrome.call('debugger.evaluate', {
215
+ tabId: targetTabId,
216
+ code,
217
+ awaitPromise: options?.awaitPromise ?? false,
218
+ userGesture: options?.userGesture ?? false,
219
+ }));
220
+ return evaluation.result;
221
+ }
222
+ async getPage(input) {
223
+ const normalizedInput = this.normalizeGetPageInput(input);
224
+ const timeoutMs = normalizedInput.timeoutMs ?? DEFAULT_GET_PAGE_TIMEOUT_MS;
225
+ const converterUrl = normalizedInput.converterUrl ?? DEFAULT_HTML_CONVERTER_URL;
226
+ let targetTabId;
227
+ let createdTabId = null;
228
+ if (typeof normalizedInput.tabId === 'number') {
229
+ targetTabId = await this.resolveTargetTabId(normalizedInput.tabId);
230
+ }
231
+ else if (normalizedInput.url) {
232
+ const opened = await this.open(normalizedInput.url, {
233
+ newTab: true,
234
+ active: false,
235
+ timeoutMs,
236
+ });
237
+ if (typeof opened.id !== 'number') {
238
+ throw new Error('Failed to open temporary tab for getPage');
239
+ }
240
+ targetTabId = opened.id;
241
+ createdTabId = opened.id;
242
+ }
243
+ else {
244
+ targetTabId = await this.resolveTargetTabId();
245
+ }
246
+ try {
247
+ const html = await this.getCompleteHtml(targetTabId, timeoutMs);
248
+ return await this.convertHtmlToMarkdown(html, converterUrl);
249
+ }
250
+ finally {
251
+ if (createdTabId !== null) {
252
+ await this.closeTabIfPresent(createdTabId);
253
+ }
254
+ }
255
+ }
256
+ async observe(options) {
257
+ const chrome = await this.getChrome();
258
+ const windowId = await this.getWindowId();
259
+ const targetTabId = await this.resolveTargetTabId(options?.tabId);
260
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
261
+ await this.waitForTabLoad(targetTabId, timeoutMs);
262
+ const normalizedOptions = normalizeObserveOptions(options);
263
+ const script = buildObserveScript({
264
+ maxInteractive: normalizedOptions.max,
265
+ maxTextBlocks: Math.min(normalizedOptions.max, 120),
266
+ });
267
+ const evaluation = (await chrome.call('debugger.evaluate', {
268
+ tabId: targetTabId,
269
+ code: script,
270
+ }));
271
+ const snapshot = parseObserveSnapshot(evaluation.result);
272
+ const persisted = await persistObserveSnapshot({
273
+ windowId,
274
+ tabId: targetTabId,
275
+ snapshot,
276
+ options: normalizedOptions,
277
+ });
278
+ const view = createObserveView(snapshot, normalizedOptions);
279
+ const markdown = renderObserveMarkdown({
280
+ windowId,
281
+ tabId: targetTabId,
282
+ snapshotId: persisted.snapshotId,
283
+ snapshotPath: persisted.snapshotPath,
284
+ options: normalizedOptions,
285
+ snapshot,
286
+ view,
287
+ });
288
+ if (normalizedOptions.semanticFilter) {
289
+ const semanticInput = [
290
+ `Semantic Filter Query: ${normalizedOptions.semanticFilter}`,
291
+ '',
292
+ markdown,
293
+ ].join('\n');
294
+ return await this.semanticFilter(semanticInput);
295
+ }
296
+ return markdown;
297
+ }
298
+ async click(targetId, options) {
299
+ return this.runTargetAction('click', targetId, undefined, options);
300
+ }
301
+ async hover(targetId, options) {
302
+ return this.runTargetAction('hover', targetId, undefined, options);
303
+ }
304
+ async focus(targetId, options) {
305
+ return this.runTargetAction('focus', targetId, undefined, options);
306
+ }
307
+ async pressEnter(targetId, options) {
308
+ return this.runTargetAction('pressEnter', targetId, undefined, options);
309
+ }
310
+ async clear(targetId, options) {
311
+ return this.runTargetAction('clear', targetId, undefined, options);
312
+ }
313
+ async toggle(targetId, options) {
314
+ return this.runTargetAction('toggle', targetId, undefined, options);
315
+ }
316
+ async type(targetId, value, options) {
317
+ if (typeof value !== 'string') {
318
+ throw new Error('type requires value to be a string');
319
+ }
320
+ return this.runTargetAction('type', targetId, {
321
+ value,
322
+ clearBeforeType: options?.clearBeforeType ?? true,
323
+ pressEnter: options?.pressEnter ?? false,
324
+ }, options);
325
+ }
326
+ async select(targetId, value, options) {
327
+ if (typeof value !== 'string') {
328
+ throw new Error('select requires value to be a string');
329
+ }
330
+ return this.runTargetAction('select', targetId, { value }, options);
331
+ }
332
+ async scroll(options) {
333
+ const scrollPayload = this.buildScrollPayload(options);
334
+ if (options?.targetId) {
335
+ return this.runTargetAction('scroll', options.targetId, { scroll: scrollPayload }, options);
336
+ }
337
+ return this.runPageAction({
338
+ action: 'scroll',
339
+ selectors: [],
340
+ scroll: scrollPayload,
341
+ }, options);
342
+ }
343
+ async current() {
344
+ const chrome = await this.getChrome();
345
+ const windowId = await this.getWindowId();
346
+ const tabs = (await chrome.call('tabs.query', {
347
+ windowId,
348
+ active: true,
349
+ }));
350
+ return tabs[0] ?? null;
351
+ }
352
+ async runTargetAction(action, targetId, payload = {}, options) {
353
+ const normalizedTargetId = targetId.trim();
354
+ if (!normalizedTargetId) {
355
+ throw new Error(`${action} requires a non-empty targetId`);
356
+ }
357
+ const targetTabId = await this.resolveTargetTabId(options?.tabId);
358
+ await this.waitForTabLoad(targetTabId, options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS);
359
+ const windowId = await this.getWindowId();
360
+ const baselineRecord = await readLatestObserveSnapshot(windowId, targetTabId);
361
+ const resolved = await resolveObservedTarget({
362
+ windowId,
363
+ tabId: targetTabId,
364
+ targetId: normalizedTargetId,
365
+ });
366
+ if (resolved.status === 'observe_required') {
367
+ return OBSERVE_BEFORE_ACT_MESSAGE;
368
+ }
369
+ if (resolved.status === 'target_not_found') {
370
+ return resolved.message;
371
+ }
372
+ const beforeTab = await this.assertTabInWindow(targetTabId);
373
+ const actionPayload = {
374
+ action,
375
+ selectors: buildSelectorCandidates(resolved.target),
376
+ };
377
+ if (typeof payload.value === 'string') {
378
+ actionPayload.value = payload.value;
379
+ }
380
+ if (typeof payload.clearBeforeType === 'boolean') {
381
+ actionPayload.clearBeforeType = payload.clearBeforeType;
382
+ }
383
+ if (typeof payload.pressEnter === 'boolean') {
384
+ actionPayload.pressEnter = payload.pressEnter;
385
+ }
386
+ if (payload.scroll) {
387
+ actionPayload.scroll = payload.scroll;
388
+ }
389
+ const script = buildActionScript(actionPayload);
390
+ const chrome = await this.getChrome();
391
+ const evaluation = (await chrome.call('debugger.evaluate', {
392
+ tabId: targetTabId,
393
+ code: script,
394
+ }));
395
+ const execution = parseActionExecutionResult(evaluation.result);
396
+ await this.waitForPostActionLoad(targetTabId, options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS);
397
+ const afterTab = await this.getTabIfPresent(targetTabId);
398
+ if (!afterTab) {
399
+ return this.renderActionMarkdown({
400
+ action: execution.action || action,
401
+ targetId: normalizedTargetId,
402
+ selectorUsed: execution.selectorUsed,
403
+ success: execution.success,
404
+ actionMessage: execution.message,
405
+ urlBefore: beforeTab.url,
406
+ urlAfter: undefined,
407
+ domDiffLines: ['- Tab is no longer available after action.'],
408
+ });
409
+ }
410
+ if (afterTab.url !== beforeTab.url) {
411
+ return this.renderActionMarkdown({
412
+ action: execution.action || action,
413
+ targetId: normalizedTargetId,
414
+ selectorUsed: execution.selectorUsed,
415
+ success: execution.success,
416
+ actionMessage: execution.message,
417
+ urlBefore: beforeTab.url,
418
+ urlAfter: afterTab.url,
419
+ });
420
+ }
421
+ let domDiffLines;
422
+ if (baselineRecord) {
423
+ const afterSnapshot = await this.captureActionSnapshot(targetTabId);
424
+ if (afterSnapshot) {
425
+ domDiffLines = summarizeActionDomChanges(baselineRecord.snapshot, afterSnapshot).lines;
426
+ }
427
+ }
428
+ return this.renderActionMarkdown({
429
+ action: execution.action || action,
430
+ targetId: normalizedTargetId,
431
+ selectorUsed: execution.selectorUsed,
432
+ success: execution.success,
433
+ actionMessage: execution.message,
434
+ urlBefore: beforeTab.url,
435
+ urlAfter: afterTab.url,
436
+ domDiffLines,
437
+ });
438
+ }
439
+ async runPageAction(payload, options) {
440
+ const targetTabId = await this.resolveTargetTabId(options?.tabId);
441
+ await this.waitForTabLoad(targetTabId, options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS);
442
+ const beforeTab = await this.assertTabInWindow(targetTabId);
443
+ const windowId = await this.getWindowId();
444
+ const baselineRecord = await readLatestObserveSnapshot(windowId, targetTabId);
445
+ const script = buildActionScript(payload);
446
+ const chrome = await this.getChrome();
447
+ const evaluation = (await chrome.call('debugger.evaluate', {
448
+ tabId: targetTabId,
449
+ code: script,
450
+ }));
451
+ const execution = parseActionExecutionResult(evaluation.result);
452
+ await this.waitForPostActionLoad(targetTabId, options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS);
453
+ const afterTab = await this.getTabIfPresent(targetTabId);
454
+ if (!afterTab) {
455
+ return this.renderActionMarkdown({
456
+ action: execution.action || payload.action,
457
+ success: execution.success,
458
+ actionMessage: execution.message,
459
+ urlBefore: beforeTab.url,
460
+ urlAfter: undefined,
461
+ domDiffLines: ['- Tab is no longer available after action.'],
462
+ });
463
+ }
464
+ if (afterTab.url !== beforeTab.url) {
465
+ return this.renderActionMarkdown({
466
+ action: execution.action || payload.action,
467
+ success: execution.success,
468
+ actionMessage: execution.message,
469
+ urlBefore: beforeTab.url,
470
+ urlAfter: afterTab.url,
471
+ });
472
+ }
473
+ let domDiffLines;
474
+ if (baselineRecord) {
475
+ const afterSnapshot = await this.captureActionSnapshot(targetTabId);
476
+ if (afterSnapshot) {
477
+ domDiffLines = summarizeActionDomChanges(baselineRecord.snapshot, afterSnapshot).lines;
478
+ }
479
+ }
480
+ return this.renderActionMarkdown({
481
+ action: execution.action || payload.action,
482
+ success: execution.success,
483
+ actionMessage: execution.message,
484
+ urlBefore: beforeTab.url,
485
+ urlAfter: afterTab.url,
486
+ domDiffLines,
487
+ });
488
+ }
489
+ async waitForPostActionLoad(tabId, timeoutMs) {
490
+ try {
491
+ await this.waitForTabLoad(tabId, timeoutMs);
492
+ }
493
+ catch (error) {
494
+ const message = error instanceof Error ? error.message : String(error);
495
+ if (message.includes('No tab with id') ||
496
+ message.includes('No tab exists with id') ||
497
+ message.includes('Tab closed')) {
498
+ return;
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ async captureActionSnapshot(tabId) {
504
+ try {
505
+ const chrome = await this.getChrome();
506
+ const script = buildObserveScript({
507
+ maxInteractive: 120,
508
+ maxTextBlocks: 80,
509
+ });
510
+ const evaluation = (await chrome.call('debugger.evaluate', {
511
+ tabId,
512
+ code: script,
513
+ }));
514
+ return parseObserveSnapshot(evaluation.result);
515
+ }
516
+ catch {
517
+ return null;
518
+ }
519
+ }
520
+ async getTabIfPresent(tabId) {
521
+ try {
522
+ return await this.assertTabInWindow(tabId);
523
+ }
524
+ catch (error) {
525
+ const message = error instanceof Error ? error.message : String(error);
526
+ if (message.includes('No tab with id') ||
527
+ message.includes('No tab exists with id') ||
528
+ message.includes('Tab closed')) {
529
+ return null;
530
+ }
531
+ throw error;
532
+ }
533
+ }
534
+ renderActionMarkdown(input) {
535
+ const lines = [];
536
+ const actionTarget = input.targetId ? ` ${input.targetId}` : '';
537
+ const selectorSuffix = input.selectorUsed ? ` (resolved via \`${input.selectorUsed}\`)` : '';
538
+ const status = input.success ? 'success' : 'failed';
539
+ const urlChanged = input.urlAfter !== undefined && input.urlAfter !== input.urlBefore;
540
+ lines.push('### Action Result');
541
+ lines.push(`- Action: ${input.action}${actionTarget}`);
542
+ lines.push(`- Status: ${status}`);
543
+ lines.push(`- Message: ${input.actionMessage}${selectorSuffix}`);
544
+ if (urlChanged) {
545
+ lines.push(`- URL changed to: ${this.formatUrlForOutput(input.urlAfter)}`);
546
+ return lines.join('\n');
547
+ }
548
+ lines.push('- URL: unchanged');
549
+ if (input.domDiffLines && input.domDiffLines.length > 0) {
550
+ lines.push('');
551
+ lines.push('### Detected Changes');
552
+ lines.push(...input.domDiffLines);
553
+ }
554
+ return lines.join('\n');
555
+ }
556
+ formatUrlForOutput(url) {
557
+ if (!url) {
558
+ return '(unknown)';
559
+ }
560
+ const trimmed = url.trim();
561
+ if (!trimmed) {
562
+ return '(unknown)';
563
+ }
564
+ if (trimmed.length <= 180) {
565
+ return trimmed;
566
+ }
567
+ return `${trimmed.slice(0, 177)}...`;
568
+ }
569
+ buildScrollPayload(options) {
570
+ const payload = {};
571
+ if (typeof options?.x === 'number') {
572
+ payload.x = options.x;
573
+ }
574
+ if (typeof options?.y === 'number') {
575
+ payload.y = options.y;
576
+ }
577
+ if (options?.to) {
578
+ payload.to = options.to;
579
+ }
580
+ if (options?.behavior) {
581
+ payload.behavior = options.behavior;
582
+ }
583
+ return payload;
584
+ }
585
+ normalizeGetPageInput(input) {
586
+ if (typeof input === 'number') {
587
+ return { tabId: input };
588
+ }
589
+ if (typeof input === 'string') {
590
+ return { url: input };
591
+ }
592
+ if (!input) {
593
+ return {};
594
+ }
595
+ const normalized = {};
596
+ if (typeof input.tabId === 'number') {
597
+ normalized.tabId = input.tabId;
598
+ }
599
+ if (typeof input.url === 'string') {
600
+ normalized.url = input.url;
601
+ }
602
+ if (typeof input.timeoutMs === 'number') {
603
+ normalized.timeoutMs = input.timeoutMs;
604
+ }
605
+ if (typeof input.converterUrl === 'string') {
606
+ normalized.converterUrl = input.converterUrl;
607
+ }
608
+ if (normalized.tabId !== undefined && normalized.url !== undefined) {
609
+ throw new Error('getPage accepts either tabId or url, not both');
610
+ }
611
+ return normalized;
612
+ }
613
+ async getCompleteHtml(tabId, timeoutMs) {
614
+ const html = await this.evaluate(`
615
+ (() => {
616
+ const doctype = document.doctype
617
+ ? "<!DOCTYPE " + document.doctype.name + ">"
618
+ : "";
619
+ return doctype + "\\n" + document.documentElement.outerHTML;
620
+ })()
621
+ `.trim(), { tabId, timeoutMs });
622
+ if (typeof html !== 'string') {
623
+ throw new Error('Failed to read page HTML');
624
+ }
625
+ return html;
626
+ }
627
+ async convertHtmlToMarkdown(html, converterUrl) {
628
+ try {
629
+ const response = await fetch(converterUrl, {
630
+ method: 'POST',
631
+ headers: {
632
+ 'Content-Type': 'application/json',
633
+ },
634
+ body: JSON.stringify({ html }),
635
+ });
636
+ if (!response.ok) {
637
+ return GET_PAGE_SERVICE_ERROR;
638
+ }
639
+ const raw = await response.text();
640
+ return this.parseConvertedMarkdown(raw);
641
+ }
642
+ catch {
643
+ return GET_PAGE_SERVICE_ERROR;
644
+ }
645
+ }
646
+ parseConvertedMarkdown(raw) {
647
+ const trimmed = raw.trim();
648
+ if (!trimmed) {
649
+ return '';
650
+ }
651
+ try {
652
+ const parsed = JSON.parse(trimmed);
653
+ if (typeof parsed === 'string') {
654
+ return parsed;
655
+ }
656
+ if (parsed && typeof parsed === 'object') {
657
+ const parsedRecord = parsed;
658
+ if (typeof parsedRecord.markdown === 'string') {
659
+ return parsedRecord.markdown;
660
+ }
661
+ if (typeof parsedRecord.content === 'string') {
662
+ return parsedRecord.content;
663
+ }
664
+ if (typeof parsedRecord.result === 'string') {
665
+ return parsedRecord.result;
666
+ }
667
+ }
668
+ }
669
+ catch {
670
+ // Converter may return plain text markdown.
671
+ }
672
+ return raw;
673
+ }
674
+ async closeTabIfPresent(tabId) {
675
+ try {
676
+ const chrome = await this.getChrome();
677
+ await chrome.call('tabs.remove', tabId);
678
+ await this.waitForTabClosed(tabId);
679
+ }
680
+ catch {
681
+ // Ignore close cleanup errors.
682
+ }
683
+ }
684
+ getChrome() {
685
+ if (!this.chromePromise) {
686
+ this.chromePromise = connect({ launch: true });
687
+ }
688
+ return this.chromePromise;
689
+ }
690
+ async getWindowId() {
691
+ await this.ready;
692
+ if (this.windowId === null) {
693
+ throw new Error('Window is not initialized');
694
+ }
695
+ return this.windowId;
696
+ }
697
+ async resolveTargetTabId(tabId) {
698
+ if (typeof tabId === 'number') {
699
+ await this.assertTabInWindow(tabId);
700
+ return tabId;
701
+ }
702
+ const current = await this.current();
703
+ if (typeof current?.id !== 'number') {
704
+ throw new Error('No active tab found in window');
705
+ }
706
+ return current.id;
707
+ }
708
+ async assertTabInWindow(tabId) {
709
+ const chrome = await this.getChrome();
710
+ const windowId = await this.getWindowId();
711
+ const tab = (await chrome.call('tabs.get', tabId));
712
+ if (tab.windowId !== windowId) {
713
+ throw new Error(`Tab ${tabId} is not in window ${windowId}`);
714
+ }
715
+ return tab;
716
+ }
717
+ async waitForTabLoad(tabId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
718
+ const chrome = await this.getChrome();
719
+ const deadline = Date.now() + timeoutMs;
720
+ while (Date.now() < deadline) {
721
+ const tab = (await chrome.call('tabs.get', tabId));
722
+ if (tab.status === 'complete') {
723
+ await sleep(TAB_SETTLE_DELAY_MS);
724
+ const settled = (await chrome.call('tabs.get', tabId));
725
+ if (settled.status === 'complete') {
726
+ return;
727
+ }
728
+ }
729
+ await sleep(TAB_POLL_INTERVAL_MS);
730
+ }
731
+ throw new Error(`Tab ${tabId} did not finish loading within ${timeoutMs}ms`);
732
+ }
733
+ async waitForTabClosed(tabId, timeoutMs = 5000) {
734
+ const chrome = await this.getChrome();
735
+ const deadline = Date.now() + timeoutMs;
736
+ while (Date.now() < deadline) {
737
+ try {
738
+ await chrome.call('tabs.get', tabId);
739
+ }
740
+ catch {
741
+ return;
742
+ }
743
+ await sleep(TAB_POLL_INTERVAL_MS);
744
+ }
745
+ throw new Error(`Tab ${tabId} was not closed within ${timeoutMs}ms`);
746
+ }
747
+ async waitForDownloadCompletion(downloadId, timeoutMs) {
748
+ const chrome = await this.getChrome();
749
+ const deadline = Date.now() + timeoutMs;
750
+ while (Date.now() < deadline) {
751
+ const results = (await chrome.call('downloads.search', {
752
+ id: downloadId,
753
+ limit: 1,
754
+ }));
755
+ const item = results[0];
756
+ if (!item) {
757
+ await sleep(TAB_POLL_INTERVAL_MS);
758
+ continue;
759
+ }
760
+ if (item.state === 'complete' || item.state === 'interrupted') {
761
+ return item;
762
+ }
763
+ await sleep(TAB_POLL_INTERVAL_MS);
764
+ }
765
+ throw new Error(`Download ${downloadId} did not finish within ${timeoutMs}ms`);
766
+ }
767
+ async getFullPageCaptureParams(tabId) {
768
+ const chrome = await this.getChrome();
769
+ const metrics = (await chrome.call('debugger.sendCommand', {
770
+ tabId,
771
+ method: 'Page.getLayoutMetrics',
772
+ }));
773
+ const contentSize = metrics.cssContentSize ?? metrics.contentSize;
774
+ if (!contentSize ||
775
+ typeof contentSize.width !== 'number' ||
776
+ typeof contentSize.height !== 'number') {
777
+ throw new Error('Failed to determine page dimensions for full-page screenshot');
778
+ }
779
+ return {
780
+ format: 'png',
781
+ captureBeyondViewport: true,
782
+ clip: {
783
+ x: contentSize.x ?? 0,
784
+ y: contentSize.y ?? 0,
785
+ width: Math.max(1, Math.ceil(contentSize.width)),
786
+ height: Math.max(1, Math.ceil(contentSize.height)),
787
+ scale: 1,
788
+ },
789
+ };
790
+ }
791
+ }
792
+ function sleep(ms) {
793
+ return new Promise((resolve) => setTimeout(resolve, ms));
794
+ }
795
+ //# sourceMappingURL=window.js.map