haltija 1.1.3 → 1.1.4

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija-desktop",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Haltija Desktop - God Mode Browser for AI Agents",
5
5
  "homepage": "https://github.com/tonioloewald/haltija",
6
6
  "author": {
@@ -36,7 +36,57 @@
36
36
  });
37
37
 
38
38
  // src/version.ts
39
- var VERSION = "1.1.3";
39
+ var VERSION = "1.1.4";
40
+
41
+ // src/text-selector.ts
42
+ var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
43
+ function parseTextSelector(selector) {
44
+ const match = selector.match(/:(?:text-is|has-text|text)\(/);
45
+ if (!match || match.index === undefined)
46
+ return null;
47
+ const pseudoStart = match.index;
48
+ const pseudoName = match[0].slice(1, -1);
49
+ let depth = 1;
50
+ let i = pseudoStart + match[0].length;
51
+ while (i < selector.length && depth > 0) {
52
+ if (selector[i] === "(")
53
+ depth++;
54
+ else if (selector[i] === ")")
55
+ depth--;
56
+ i++;
57
+ }
58
+ if (depth !== 0)
59
+ return null;
60
+ const rawArg = selector.slice(pseudoStart + match[0].length, i - 1).trim();
61
+ const baseSelector = (selector.slice(0, pseudoStart) + selector.slice(i)).trim() || "*";
62
+ const regexMatch = rawArg.match(/^\/(.+)\/([gimsuy]*)$/);
63
+ if (regexMatch) {
64
+ try {
65
+ return {
66
+ baseSelector,
67
+ pseudoType: pseudoName,
68
+ searchRegex: new RegExp(regexMatch[1], regexMatch[2])
69
+ };
70
+ } catch {}
71
+ }
72
+ const unquoted = rawArg.startsWith('"') && rawArg.endsWith('"') || rawArg.startsWith("'") && rawArg.endsWith("'") ? rawArg.slice(1, -1) : rawArg;
73
+ return {
74
+ baseSelector,
75
+ pseudoType: pseudoName,
76
+ searchText: unquoted.toLowerCase()
77
+ };
78
+ }
79
+ function textMatches(elementText, parsed) {
80
+ const text = elementText.trim();
81
+ if (parsed.searchRegex) {
82
+ return parsed.searchRegex.test(text);
83
+ }
84
+ const lower = text.toLowerCase();
85
+ if (parsed.pseudoType === "text-is") {
86
+ return lower === parsed.searchText;
87
+ }
88
+ return lower.includes(parsed.searchText);
89
+ }
40
90
 
41
91
  // src/component.ts
42
92
  var VERSION2 = VERSION;
@@ -299,7 +349,7 @@
299
349
  const allSimilar = document.querySelectorAll(selector);
300
350
  const matchingText = Array.from(allSimilar).filter((e) => e.innerText?.trim() === text);
301
351
  if (matchingText.length === 1) {
302
- return `${tag}:has-text("${text}")`;
352
+ return `${tag}:text-is("${text}")`;
303
353
  }
304
354
  }
305
355
  }
@@ -325,6 +375,34 @@
325
375
  }
326
376
  return getSelector(el);
327
377
  }
378
+ function elementTextMatches(el, parsed) {
379
+ const text = el.innerText ?? "";
380
+ return textMatches(text, parsed);
381
+ }
382
+ function resolveSelector(selector) {
383
+ if (!TEXT_PSEUDO_RE.test(selector)) {
384
+ return document.querySelector(selector);
385
+ }
386
+ const parsed = parseTextSelector(selector);
387
+ if (!parsed)
388
+ return document.querySelector(selector);
389
+ const candidates = document.querySelectorAll(parsed.baseSelector);
390
+ for (const el of candidates) {
391
+ if (elementTextMatches(el, parsed))
392
+ return el;
393
+ }
394
+ return null;
395
+ }
396
+ function resolveSelectorAll(selector) {
397
+ if (!TEXT_PSEUDO_RE.test(selector)) {
398
+ return Array.from(document.querySelectorAll(selector));
399
+ }
400
+ const parsed = parseTextSelector(selector);
401
+ if (!parsed)
402
+ return Array.from(document.querySelectorAll(selector));
403
+ const candidates = document.querySelectorAll(parsed.baseSelector);
404
+ return Array.from(candidates).filter((el) => elementTextMatches(el, parsed));
405
+ }
328
406
  function extractElement(el) {
329
407
  const rect = el.getBoundingClientRect();
330
408
  const attrs = {};
@@ -4905,10 +4983,10 @@ ${elementSummary}${moreText}`;
4905
4983
  const req = payload2;
4906
4984
  try {
4907
4985
  if (req.all) {
4908
- const elements = document.querySelectorAll(req.selector);
4909
- this.respond(msg2.id, true, Array.from(elements).map(extractElement));
4986
+ const elements = resolveSelectorAll(req.selector);
4987
+ this.respond(msg2.id, true, elements.map(extractElement));
4910
4988
  } else {
4911
- const el = document.querySelector(req.selector);
4989
+ const el = resolveSelector(req.selector);
4912
4990
  this.respond(msg2.id, true, el ? extractElement(el) : null);
4913
4991
  }
4914
4992
  } catch (err) {
@@ -4916,7 +4994,7 @@ ${elementSummary}${moreText}`;
4916
4994
  }
4917
4995
  } else if (action2 === "inspect") {
4918
4996
  try {
4919
- const el = document.querySelector(payload2.selector);
4997
+ const el = resolveSelector(payload2.selector);
4920
4998
  if (!el) {
4921
4999
  this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
4922
5000
  return;
@@ -4931,19 +5009,19 @@ ${elementSummary}${moreText}`;
4931
5009
  }
4932
5010
  } else if (action2 === "inspectAll") {
4933
5011
  try {
4934
- const elements = document.querySelectorAll(payload2.selector);
5012
+ const elements = resolveSelectorAll(payload2.selector);
4935
5013
  const opts = {
4936
5014
  fullStyles: payload2.fullStyles,
4937
5015
  matchedRules: payload2.matchedRules
4938
5016
  };
4939
- const results = Array.from(elements).slice(0, payload2.limit || 10).map((el) => inspectElement(el, opts));
5017
+ const results = elements.slice(0, payload2.limit || 10).map((el) => inspectElement(el, opts));
4940
5018
  this.respond(msg2.id, true, results);
4941
5019
  } catch (err) {
4942
5020
  this.respond(msg2.id, false, null, err.message);
4943
5021
  }
4944
5022
  } else if (action2 === "highlight") {
4945
5023
  try {
4946
- const el = document.querySelector(payload2.selector);
5024
+ const el = resolveSelector(payload2.selector);
4947
5025
  if (!el) {
4948
5026
  this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
4949
5027
  return;
@@ -4963,7 +5041,7 @@ ${elementSummary}${moreText}`;
4963
5041
  } else if (action2 === "tree") {
4964
5042
  try {
4965
5043
  const request = payload2;
4966
- const el = document.querySelector(request.selector);
5044
+ const el = resolveSelector(request.selector);
4967
5045
  if (!el) {
4968
5046
  this.respond(msg2.id, false, null, `Element not found: ${request.selector}`);
4969
5047
  return;
@@ -5108,7 +5186,7 @@ ${elementSummary}${moreText}`;
5108
5186
  }
5109
5187
  const html2canvas = window.html2canvas;
5110
5188
  if (html2canvas) {
5111
- const target = payload2?.selector ? document.querySelector(payload2.selector) : document.body;
5189
+ const target = payload2?.selector ? resolveSelector(payload2.selector) : document.body;
5112
5190
  if (!target) {
5113
5191
  this.respond(msg2.id, false, null, `Element not found: ${payload2?.selector}`);
5114
5192
  return;
@@ -5262,7 +5340,7 @@ ${elementSummary}${moreText}`;
5262
5340
  return;
5263
5341
  }
5264
5342
  } else if (selector) {
5265
- el = document.querySelector(selector);
5343
+ el = resolveSelector(selector);
5266
5344
  targetDesc = selector;
5267
5345
  }
5268
5346
  if (!el) {
@@ -5614,7 +5692,7 @@ ${elementSummary}${moreText}`;
5614
5692
  return;
5615
5693
  }
5616
5694
  } else if (payload2.selector) {
5617
- el = document.querySelector(payload2.selector);
5695
+ el = resolveSelector(payload2.selector);
5618
5696
  targetDesc = payload2.selector;
5619
5697
  }
5620
5698
  if (!el) {
@@ -5689,7 +5767,7 @@ ${elementSummary}${moreText}`;
5689
5767
  return;
5690
5768
  }
5691
5769
  } else if (selector) {
5692
- target = document.querySelector(selector);
5770
+ target = resolveSelector(selector);
5693
5771
  targetDesc = selector;
5694
5772
  if (!target) {
5695
5773
  this.respond(responseId, false, null, `Element not found: ${selector}`);
@@ -6095,7 +6173,7 @@ ${elementSummary}${moreText}`;
6095
6173
  startMutationWatch(config) {
6096
6174
  this.stopMutationWatch();
6097
6175
  this.mutationConfig = config;
6098
- const root = config.root ? document.querySelector(config.root) : document.body;
6176
+ const root = config.root ? resolveSelector(config.root) : document.body;
6099
6177
  if (!root) {
6100
6178
  this.send("mutations", "error", {
6101
6179
  error: `Root element not found: ${config.root}`
@@ -6369,7 +6447,7 @@ ${elementSummary}${moreText}`;
6369
6447
  this.send("mutations", "batch", batch);
6370
6448
  }
6371
6449
  watchEvents(req, watchId) {
6372
- const target = req.selector ? document.querySelector(req.selector) : document;
6450
+ const target = req.selector ? resolveSelector(req.selector) : document;
6373
6451
  if (!target) {
6374
6452
  this.respond(watchId, false, null, `Element not found: ${req.selector}`);
6375
6453
  return;
@@ -6444,7 +6522,7 @@ ${elementSummary}${moreText}`;
6444
6522
  this.eventWatchers.clear();
6445
6523
  }
6446
6524
  dispatchSyntheticEvent(req, responseId) {
6447
- const el = document.querySelector(req.selector);
6525
+ const el = resolveSelector(req.selector);
6448
6526
  if (!el) {
6449
6527
  this.respond(responseId, false, null, `Element not found: ${req.selector}`);
6450
6528
  return;
@@ -6588,6 +6666,8 @@ ${elementSummary}${moreText}`;
6588
6666
  }
6589
6667
  customElements.define(TAG_NAME, DevChannel);
6590
6668
  currentTagName = TAG_NAME;
6669
+ window.__haltija_resolveSelector = resolveSelector;
6670
+ window.__haltija_resolveSelectorAll = resolveSelectorAll;
6591
6671
  }
6592
6672
  registerDevChannel();
6593
6673
  var WIDGET_ID = "haltija-widget";
File without changes
package/dist/component.js CHANGED
@@ -36,7 +36,57 @@
36
36
  });
37
37
 
38
38
  // src/version.ts
39
- var VERSION = "1.1.3";
39
+ var VERSION = "1.1.4";
40
+
41
+ // src/text-selector.ts
42
+ var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
43
+ function parseTextSelector(selector) {
44
+ const match = selector.match(/:(?:text-is|has-text|text)\(/);
45
+ if (!match || match.index === undefined)
46
+ return null;
47
+ const pseudoStart = match.index;
48
+ const pseudoName = match[0].slice(1, -1);
49
+ let depth = 1;
50
+ let i = pseudoStart + match[0].length;
51
+ while (i < selector.length && depth > 0) {
52
+ if (selector[i] === "(")
53
+ depth++;
54
+ else if (selector[i] === ")")
55
+ depth--;
56
+ i++;
57
+ }
58
+ if (depth !== 0)
59
+ return null;
60
+ const rawArg = selector.slice(pseudoStart + match[0].length, i - 1).trim();
61
+ const baseSelector = (selector.slice(0, pseudoStart) + selector.slice(i)).trim() || "*";
62
+ const regexMatch = rawArg.match(/^\/(.+)\/([gimsuy]*)$/);
63
+ if (regexMatch) {
64
+ try {
65
+ return {
66
+ baseSelector,
67
+ pseudoType: pseudoName,
68
+ searchRegex: new RegExp(regexMatch[1], regexMatch[2])
69
+ };
70
+ } catch {}
71
+ }
72
+ const unquoted = rawArg.startsWith('"') && rawArg.endsWith('"') || rawArg.startsWith("'") && rawArg.endsWith("'") ? rawArg.slice(1, -1) : rawArg;
73
+ return {
74
+ baseSelector,
75
+ pseudoType: pseudoName,
76
+ searchText: unquoted.toLowerCase()
77
+ };
78
+ }
79
+ function textMatches(elementText, parsed) {
80
+ const text = elementText.trim();
81
+ if (parsed.searchRegex) {
82
+ return parsed.searchRegex.test(text);
83
+ }
84
+ const lower = text.toLowerCase();
85
+ if (parsed.pseudoType === "text-is") {
86
+ return lower === parsed.searchText;
87
+ }
88
+ return lower.includes(parsed.searchText);
89
+ }
40
90
 
41
91
  // src/component.ts
42
92
  var VERSION2 = VERSION;
@@ -299,7 +349,7 @@
299
349
  const allSimilar = document.querySelectorAll(selector);
300
350
  const matchingText = Array.from(allSimilar).filter((e) => e.innerText?.trim() === text);
301
351
  if (matchingText.length === 1) {
302
- return `${tag}:has-text("${text}")`;
352
+ return `${tag}:text-is("${text}")`;
303
353
  }
304
354
  }
305
355
  }
@@ -325,6 +375,34 @@
325
375
  }
326
376
  return getSelector(el);
327
377
  }
378
+ function elementTextMatches(el, parsed) {
379
+ const text = el.innerText ?? "";
380
+ return textMatches(text, parsed);
381
+ }
382
+ function resolveSelector(selector) {
383
+ if (!TEXT_PSEUDO_RE.test(selector)) {
384
+ return document.querySelector(selector);
385
+ }
386
+ const parsed = parseTextSelector(selector);
387
+ if (!parsed)
388
+ return document.querySelector(selector);
389
+ const candidates = document.querySelectorAll(parsed.baseSelector);
390
+ for (const el of candidates) {
391
+ if (elementTextMatches(el, parsed))
392
+ return el;
393
+ }
394
+ return null;
395
+ }
396
+ function resolveSelectorAll(selector) {
397
+ if (!TEXT_PSEUDO_RE.test(selector)) {
398
+ return Array.from(document.querySelectorAll(selector));
399
+ }
400
+ const parsed = parseTextSelector(selector);
401
+ if (!parsed)
402
+ return Array.from(document.querySelectorAll(selector));
403
+ const candidates = document.querySelectorAll(parsed.baseSelector);
404
+ return Array.from(candidates).filter((el) => elementTextMatches(el, parsed));
405
+ }
328
406
  function extractElement(el) {
329
407
  const rect = el.getBoundingClientRect();
330
408
  const attrs = {};
@@ -4905,10 +4983,10 @@ ${elementSummary}${moreText}`;
4905
4983
  const req = payload2;
4906
4984
  try {
4907
4985
  if (req.all) {
4908
- const elements = document.querySelectorAll(req.selector);
4909
- this.respond(msg2.id, true, Array.from(elements).map(extractElement));
4986
+ const elements = resolveSelectorAll(req.selector);
4987
+ this.respond(msg2.id, true, elements.map(extractElement));
4910
4988
  } else {
4911
- const el = document.querySelector(req.selector);
4989
+ const el = resolveSelector(req.selector);
4912
4990
  this.respond(msg2.id, true, el ? extractElement(el) : null);
4913
4991
  }
4914
4992
  } catch (err) {
@@ -4916,7 +4994,7 @@ ${elementSummary}${moreText}`;
4916
4994
  }
4917
4995
  } else if (action2 === "inspect") {
4918
4996
  try {
4919
- const el = document.querySelector(payload2.selector);
4997
+ const el = resolveSelector(payload2.selector);
4920
4998
  if (!el) {
4921
4999
  this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
4922
5000
  return;
@@ -4931,19 +5009,19 @@ ${elementSummary}${moreText}`;
4931
5009
  }
4932
5010
  } else if (action2 === "inspectAll") {
4933
5011
  try {
4934
- const elements = document.querySelectorAll(payload2.selector);
5012
+ const elements = resolveSelectorAll(payload2.selector);
4935
5013
  const opts = {
4936
5014
  fullStyles: payload2.fullStyles,
4937
5015
  matchedRules: payload2.matchedRules
4938
5016
  };
4939
- const results = Array.from(elements).slice(0, payload2.limit || 10).map((el) => inspectElement(el, opts));
5017
+ const results = elements.slice(0, payload2.limit || 10).map((el) => inspectElement(el, opts));
4940
5018
  this.respond(msg2.id, true, results);
4941
5019
  } catch (err) {
4942
5020
  this.respond(msg2.id, false, null, err.message);
4943
5021
  }
4944
5022
  } else if (action2 === "highlight") {
4945
5023
  try {
4946
- const el = document.querySelector(payload2.selector);
5024
+ const el = resolveSelector(payload2.selector);
4947
5025
  if (!el) {
4948
5026
  this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
4949
5027
  return;
@@ -4963,7 +5041,7 @@ ${elementSummary}${moreText}`;
4963
5041
  } else if (action2 === "tree") {
4964
5042
  try {
4965
5043
  const request = payload2;
4966
- const el = document.querySelector(request.selector);
5044
+ const el = resolveSelector(request.selector);
4967
5045
  if (!el) {
4968
5046
  this.respond(msg2.id, false, null, `Element not found: ${request.selector}`);
4969
5047
  return;
@@ -5108,7 +5186,7 @@ ${elementSummary}${moreText}`;
5108
5186
  }
5109
5187
  const html2canvas = window.html2canvas;
5110
5188
  if (html2canvas) {
5111
- const target = payload2?.selector ? document.querySelector(payload2.selector) : document.body;
5189
+ const target = payload2?.selector ? resolveSelector(payload2.selector) : document.body;
5112
5190
  if (!target) {
5113
5191
  this.respond(msg2.id, false, null, `Element not found: ${payload2?.selector}`);
5114
5192
  return;
@@ -5262,7 +5340,7 @@ ${elementSummary}${moreText}`;
5262
5340
  return;
5263
5341
  }
5264
5342
  } else if (selector) {
5265
- el = document.querySelector(selector);
5343
+ el = resolveSelector(selector);
5266
5344
  targetDesc = selector;
5267
5345
  }
5268
5346
  if (!el) {
@@ -5614,7 +5692,7 @@ ${elementSummary}${moreText}`;
5614
5692
  return;
5615
5693
  }
5616
5694
  } else if (payload2.selector) {
5617
- el = document.querySelector(payload2.selector);
5695
+ el = resolveSelector(payload2.selector);
5618
5696
  targetDesc = payload2.selector;
5619
5697
  }
5620
5698
  if (!el) {
@@ -5689,7 +5767,7 @@ ${elementSummary}${moreText}`;
5689
5767
  return;
5690
5768
  }
5691
5769
  } else if (selector) {
5692
- target = document.querySelector(selector);
5770
+ target = resolveSelector(selector);
5693
5771
  targetDesc = selector;
5694
5772
  if (!target) {
5695
5773
  this.respond(responseId, false, null, `Element not found: ${selector}`);
@@ -6095,7 +6173,7 @@ ${elementSummary}${moreText}`;
6095
6173
  startMutationWatch(config) {
6096
6174
  this.stopMutationWatch();
6097
6175
  this.mutationConfig = config;
6098
- const root = config.root ? document.querySelector(config.root) : document.body;
6176
+ const root = config.root ? resolveSelector(config.root) : document.body;
6099
6177
  if (!root) {
6100
6178
  this.send("mutations", "error", {
6101
6179
  error: `Root element not found: ${config.root}`
@@ -6369,7 +6447,7 @@ ${elementSummary}${moreText}`;
6369
6447
  this.send("mutations", "batch", batch);
6370
6448
  }
6371
6449
  watchEvents(req, watchId) {
6372
- const target = req.selector ? document.querySelector(req.selector) : document;
6450
+ const target = req.selector ? resolveSelector(req.selector) : document;
6373
6451
  if (!target) {
6374
6452
  this.respond(watchId, false, null, `Element not found: ${req.selector}`);
6375
6453
  return;
@@ -6444,7 +6522,7 @@ ${elementSummary}${moreText}`;
6444
6522
  this.eventWatchers.clear();
6445
6523
  }
6446
6524
  dispatchSyntheticEvent(req, responseId) {
6447
- const el = document.querySelector(req.selector);
6525
+ const el = resolveSelector(req.selector);
6448
6526
  if (!el) {
6449
6527
  this.respond(responseId, false, null, `Element not found: ${req.selector}`);
6450
6528
  return;
@@ -6588,6 +6666,8 @@ ${elementSummary}${moreText}`;
6588
6666
  }
6589
6667
  customElements.define(TAG_NAME, DevChannel);
6590
6668
  currentTagName = TAG_NAME;
6669
+ window.__haltija_resolveSelector = resolveSelector;
6670
+ window.__haltija_resolveSelectorAll = resolveSelectorAll;
6591
6671
  }
6592
6672
  registerDevChannel();
6593
6673
  var WIDGET_ID = "haltija-widget";
package/dist/index.js CHANGED
@@ -685,7 +685,7 @@ var injectorCode = `
685
685
  `;
686
686
 
687
687
  // src/version.ts
688
- var VERSION = "1.1.3";
688
+ var VERSION = "1.1.4";
689
689
 
690
690
  // src/embedded-assets.ts
691
691
  var APP_MD = `# Haltija App
@@ -5391,6 +5391,13 @@ function registerHandler(endpoint2, handler) {
5391
5391
  handlers.set(endpoint2.path, handler);
5392
5392
  }
5393
5393
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5394
+ function qs(selector) {
5395
+ const s = JSON.stringify(selector);
5396
+ return `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${s})`;
5397
+ }
5398
+ function qsVisible(selector) {
5399
+ return `(function(){var el=${qs(selector)};return !!el && el.offsetParent !== null})()`;
5400
+ }
5394
5401
  var SNAPSHOT_CODE = `(function() {
5395
5402
  const snapshot = { elements: {}, focused: null, scrollY: window.scrollY, scrollX: window.scrollX };
5396
5403
 
@@ -5576,7 +5583,7 @@ registerHandler(click, async (body, ctx) => {
5576
5583
  if (autoWait) {
5577
5584
  const startTime = Date.now();
5578
5585
  const pollInterval = 100;
5579
- const checkCode = `!!document.querySelector(${JSON.stringify(selector)}) && document.querySelector(${JSON.stringify(selector)}).offsetParent !== null`;
5586
+ const checkCode = qsVisible(selector);
5580
5587
  while (Date.now() - startTime < timeout) {
5581
5588
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5582
5589
  if (checkResponse.success && checkResponse.data === true) {
@@ -5621,13 +5628,14 @@ registerHandler(fetchUrl, async (body, ctx) => {
5621
5628
  registerHandler(call, async (body, ctx) => {
5622
5629
  const windowId = body.window || ctx.targetWindowId;
5623
5630
  const selector = JSON.stringify(body.selector);
5631
+ const resolveExpr = `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${selector})`;
5624
5632
  const method = body.method;
5625
5633
  const args = body.args;
5626
5634
  let code;
5627
5635
  if (args !== undefined) {
5628
5636
  const argsJson = JSON.stringify(args);
5629
5637
  code = `(function() {
5630
- const el = document.querySelector(${selector});
5638
+ const el = ${resolveExpr};
5631
5639
  if (!el) return { success: false, error: 'Element not found: ${body.selector.replace(/'/g, "\\'")}' };
5632
5640
  if (typeof el[${JSON.stringify(method)}] !== 'function') {
5633
5641
  return { success: false, error: 'Method not found: ${method}' };
@@ -5641,7 +5649,7 @@ registerHandler(call, async (body, ctx) => {
5641
5649
  })()`;
5642
5650
  } else {
5643
5651
  code = `(function() {
5644
- const el = document.querySelector(${selector});
5652
+ const el = ${resolveExpr};
5645
5653
  if (!el) return { success: false, error: 'Element not found: ${body.selector.replace(/'/g, "\\'")}' };
5646
5654
  try {
5647
5655
  const value = el[${JSON.stringify(method)}];
@@ -5665,7 +5673,7 @@ registerHandler(drag, async (body, ctx) => {
5665
5673
  const steps = Math.max(5, Math.floor(duration / 16));
5666
5674
  const windowId = body.window || ctx.targetWindowId;
5667
5675
  await ctx.requestFromBrowser("eval", "exec", {
5668
- code: `document.querySelector(${JSON.stringify(selector)})?.scrollIntoView({behavior: "smooth", block: "center"})`
5676
+ code: `${qs(selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
5669
5677
  }, 5000, windowId);
5670
5678
  await sleep(100);
5671
5679
  const inspectResponse = await ctx.requestFromBrowser("dom", "inspect", { selector }, 5000, windowId);
@@ -5711,7 +5719,7 @@ registerHandler(type, async (body, ctx) => {
5711
5719
  if (autoWait && body.selector) {
5712
5720
  const startTime = Date.now();
5713
5721
  const pollInterval = 100;
5714
- const checkCode = `!!document.querySelector(${JSON.stringify(body.selector)}) && document.querySelector(${JSON.stringify(body.selector)}).offsetParent !== null`;
5722
+ const checkCode = qsVisible(body.selector);
5715
5723
  while (Date.now() - startTime < waitTimeout) {
5716
5724
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5717
5725
  if (checkResponse.success && checkResponse.data === true) {
@@ -5786,7 +5794,7 @@ registerHandler(inspectAll, async (body, ctx) => {
5786
5794
  registerHandler(highlight, async (body, ctx) => {
5787
5795
  const windowId = body.window || ctx.targetWindowId;
5788
5796
  await ctx.requestFromBrowser("eval", "exec", {
5789
- code: `document.querySelector(${JSON.stringify(body.selector)})?.scrollIntoView({behavior: "smooth", block: "center"})`
5797
+ code: `${qs(body.selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
5790
5798
  }, 5000, windowId);
5791
5799
  await sleep(100);
5792
5800
  const response = await ctx.requestFromBrowser("dom", "highlight", {
@@ -5958,7 +5966,7 @@ registerHandler(wait, async (body, ctx) => {
5958
5966
  const startTime = Date.now();
5959
5967
  if (body.forElement) {
5960
5968
  const selector = body.forElement;
5961
- const checkCode = hidden ? `!document.querySelector(${JSON.stringify(selector)}) || document.querySelector(${JSON.stringify(selector)}).offsetParent === null` : `!!document.querySelector(${JSON.stringify(selector)}) && document.querySelector(${JSON.stringify(selector)}).offsetParent !== null`;
5969
+ const checkCode = hidden ? `(function(){var el=${qs(selector)};return !el || el.offsetParent === null})()` : qsVisible(selector);
5962
5970
  while (Date.now() - startTime < timeout) {
5963
5971
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5964
5972
  if (checkResponse.success && checkResponse.data === true) {
@@ -6055,7 +6063,7 @@ registerHandler(formData, async (body, ctx) => {
6055
6063
  const includeHidden = body.includeHidden ?? false;
6056
6064
  const selector = body.selector || "form";
6057
6065
  const formCode = `(function() {
6058
- const form = document.querySelector(${JSON.stringify(selector)});
6066
+ const form = (window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(selector)});
6059
6067
  if (!form) return { success: false, error: 'Form not found: ${selector.replace(/'/g, "\\'")}' };
6060
6068
 
6061
6069
  const fields = {};
@@ -6175,7 +6183,7 @@ registerHandler(scroll, async (body, ctx) => {
6175
6183
  if (body.selector) {
6176
6184
  const code = `
6177
6185
  (async () => {
6178
- const el = document.querySelector(${JSON.stringify(body.selector)});
6186
+ const el = (window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(body.selector)});
6179
6187
  if (!el) return { success: false, error: 'Element not found' };
6180
6188
  const rect = el.getBoundingClientRect();
6181
6189
  const blockAlign = ${JSON.stringify(block)};
package/dist/server.js CHANGED
@@ -685,7 +685,7 @@ var injectorCode = `
685
685
  `;
686
686
 
687
687
  // src/version.ts
688
- var VERSION = "1.1.3";
688
+ var VERSION = "1.1.4";
689
689
 
690
690
  // src/embedded-assets.ts
691
691
  var APP_MD = `# Haltija App
@@ -5391,6 +5391,13 @@ function registerHandler(endpoint2, handler) {
5391
5391
  handlers.set(endpoint2.path, handler);
5392
5392
  }
5393
5393
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5394
+ function qs(selector) {
5395
+ const s = JSON.stringify(selector);
5396
+ return `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${s})`;
5397
+ }
5398
+ function qsVisible(selector) {
5399
+ return `(function(){var el=${qs(selector)};return !!el && el.offsetParent !== null})()`;
5400
+ }
5394
5401
  var SNAPSHOT_CODE = `(function() {
5395
5402
  const snapshot = { elements: {}, focused: null, scrollY: window.scrollY, scrollX: window.scrollX };
5396
5403
 
@@ -5576,7 +5583,7 @@ registerHandler(click, async (body, ctx) => {
5576
5583
  if (autoWait) {
5577
5584
  const startTime = Date.now();
5578
5585
  const pollInterval = 100;
5579
- const checkCode = `!!document.querySelector(${JSON.stringify(selector)}) && document.querySelector(${JSON.stringify(selector)}).offsetParent !== null`;
5586
+ const checkCode = qsVisible(selector);
5580
5587
  while (Date.now() - startTime < timeout) {
5581
5588
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5582
5589
  if (checkResponse.success && checkResponse.data === true) {
@@ -5621,13 +5628,14 @@ registerHandler(fetchUrl, async (body, ctx) => {
5621
5628
  registerHandler(call, async (body, ctx) => {
5622
5629
  const windowId = body.window || ctx.targetWindowId;
5623
5630
  const selector = JSON.stringify(body.selector);
5631
+ const resolveExpr = `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${selector})`;
5624
5632
  const method = body.method;
5625
5633
  const args = body.args;
5626
5634
  let code;
5627
5635
  if (args !== undefined) {
5628
5636
  const argsJson = JSON.stringify(args);
5629
5637
  code = `(function() {
5630
- const el = document.querySelector(${selector});
5638
+ const el = ${resolveExpr};
5631
5639
  if (!el) return { success: false, error: 'Element not found: ${body.selector.replace(/'/g, "\\'")}' };
5632
5640
  if (typeof el[${JSON.stringify(method)}] !== 'function') {
5633
5641
  return { success: false, error: 'Method not found: ${method}' };
@@ -5641,7 +5649,7 @@ registerHandler(call, async (body, ctx) => {
5641
5649
  })()`;
5642
5650
  } else {
5643
5651
  code = `(function() {
5644
- const el = document.querySelector(${selector});
5652
+ const el = ${resolveExpr};
5645
5653
  if (!el) return { success: false, error: 'Element not found: ${body.selector.replace(/'/g, "\\'")}' };
5646
5654
  try {
5647
5655
  const value = el[${JSON.stringify(method)}];
@@ -5665,7 +5673,7 @@ registerHandler(drag, async (body, ctx) => {
5665
5673
  const steps = Math.max(5, Math.floor(duration / 16));
5666
5674
  const windowId = body.window || ctx.targetWindowId;
5667
5675
  await ctx.requestFromBrowser("eval", "exec", {
5668
- code: `document.querySelector(${JSON.stringify(selector)})?.scrollIntoView({behavior: "smooth", block: "center"})`
5676
+ code: `${qs(selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
5669
5677
  }, 5000, windowId);
5670
5678
  await sleep(100);
5671
5679
  const inspectResponse = await ctx.requestFromBrowser("dom", "inspect", { selector }, 5000, windowId);
@@ -5711,7 +5719,7 @@ registerHandler(type, async (body, ctx) => {
5711
5719
  if (autoWait && body.selector) {
5712
5720
  const startTime = Date.now();
5713
5721
  const pollInterval = 100;
5714
- const checkCode = `!!document.querySelector(${JSON.stringify(body.selector)}) && document.querySelector(${JSON.stringify(body.selector)}).offsetParent !== null`;
5722
+ const checkCode = qsVisible(body.selector);
5715
5723
  while (Date.now() - startTime < waitTimeout) {
5716
5724
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5717
5725
  if (checkResponse.success && checkResponse.data === true) {
@@ -5786,7 +5794,7 @@ registerHandler(inspectAll, async (body, ctx) => {
5786
5794
  registerHandler(highlight, async (body, ctx) => {
5787
5795
  const windowId = body.window || ctx.targetWindowId;
5788
5796
  await ctx.requestFromBrowser("eval", "exec", {
5789
- code: `document.querySelector(${JSON.stringify(body.selector)})?.scrollIntoView({behavior: "smooth", block: "center"})`
5797
+ code: `${qs(body.selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
5790
5798
  }, 5000, windowId);
5791
5799
  await sleep(100);
5792
5800
  const response = await ctx.requestFromBrowser("dom", "highlight", {
@@ -5958,7 +5966,7 @@ registerHandler(wait, async (body, ctx) => {
5958
5966
  const startTime = Date.now();
5959
5967
  if (body.forElement) {
5960
5968
  const selector = body.forElement;
5961
- const checkCode = hidden ? `!document.querySelector(${JSON.stringify(selector)}) || document.querySelector(${JSON.stringify(selector)}).offsetParent === null` : `!!document.querySelector(${JSON.stringify(selector)}) && document.querySelector(${JSON.stringify(selector)}).offsetParent !== null`;
5969
+ const checkCode = hidden ? `(function(){var el=${qs(selector)};return !el || el.offsetParent === null})()` : qsVisible(selector);
5962
5970
  while (Date.now() - startTime < timeout) {
5963
5971
  const checkResponse = await ctx.requestFromBrowser("eval", "exec", { code: checkCode }, 5000, windowId);
5964
5972
  if (checkResponse.success && checkResponse.data === true) {
@@ -6055,7 +6063,7 @@ registerHandler(formData, async (body, ctx) => {
6055
6063
  const includeHidden = body.includeHidden ?? false;
6056
6064
  const selector = body.selector || "form";
6057
6065
  const formCode = `(function() {
6058
- const form = document.querySelector(${JSON.stringify(selector)});
6066
+ const form = (window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(selector)});
6059
6067
  if (!form) return { success: false, error: 'Form not found: ${selector.replace(/'/g, "\\'")}' };
6060
6068
 
6061
6069
  const fields = {};
@@ -6175,7 +6183,7 @@ registerHandler(scroll, async (body, ctx) => {
6175
6183
  if (body.selector) {
6176
6184
  const code = `
6177
6185
  (async () => {
6178
- const el = document.querySelector(${JSON.stringify(body.selector)});
6186
+ const el = (window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(body.selector)});
6179
6187
  if (!el) return { success: false, error: 'Element not found' };
6180
6188
  const rect = el.getBoundingClientRect();
6181
6189
  const blockAlign = ${JSON.stringify(block)};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Browser control for AI agents - query DOM, click, type, run JS, watch mutations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",