teamshare-bridge 0.26.14 → 0.26.17
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.
- package/dist/bridge/daemon.js +13 -3
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.d.ts +52 -8
- package/dist/bridge/local-server.js +449 -50
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/cli/commands/blog.js +3 -0
- package/dist/cli/commands/blog.js.map +1 -1
- package/dist/cli/commands/harness.d.ts +1 -1
- package/dist/cli/commands/harness.js +2 -1
- package/dist/cli/commands/harness.js.map +1 -1
- package/dist/cli/commands/research.js +33 -3
- package/dist/cli/commands/research.js.map +1 -1
- package/dist/cli/commands/run.js +0 -21
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/lib/agent-scope.d.ts +21 -0
- package/dist/lib/agent-scope.js +35 -0
- package/dist/lib/agent-scope.js.map +1 -0
- package/dist/lib/brief.js +4 -0
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/batch.d.ts +62 -1
- package/dist/lib/browser/batch.js +109 -3
- package/dist/lib/browser/batch.js.map +1 -1
- package/dist/lib/browser/cdp.d.ts +52 -2
- package/dist/lib/browser/cdp.js +207 -14
- package/dist/lib/browser/cdp.js.map +1 -1
- package/dist/lib/browser/index.d.ts +2 -2
- package/dist/lib/browser/index.js +4 -1
- package/dist/lib/browser/index.js.map +1 -1
- package/dist/lib/browser/login.d.ts +20 -2
- package/dist/lib/browser/login.js +125 -88
- package/dist/lib/browser/login.js.map +1 -1
- package/dist/lib/browser/network-log.d.ts +33 -0
- package/dist/lib/browser/network-log.js +58 -11
- package/dist/lib/browser/network-log.js.map +1 -1
- package/dist/lib/browser/page-context.d.ts +40 -1
- package/dist/lib/browser/page-context.js +117 -1
- package/dist/lib/browser/page-context.js.map +1 -1
- package/dist/lib/browser/refs.d.ts +104 -0
- package/dist/lib/browser/refs.js +430 -0
- package/dist/lib/browser/refs.js.map +1 -0
- package/dist/lib/browser/session.d.ts +129 -4
- package/dist/lib/browser/session.js +262 -3
- package/dist/lib/browser/session.js.map +1 -1
- package/dist/lib/chat/board-reply.js +2 -0
- package/dist/lib/chat/board-reply.js.map +1 -1
- package/dist/lib/chat/chat-reply.js +2 -0
- package/dist/lib/chat/chat-reply.js.map +1 -1
- package/dist/lib/chat/doc-reply.js +2 -0
- package/dist/lib/chat/doc-reply.js.map +1 -1
- package/dist/lib/chat/workflow-author-reply.js +2 -0
- package/dist/lib/chat/workflow-author-reply.js.map +1 -1
- package/dist/lib/diagnostics.d.ts +2 -0
- package/dist/lib/diagnostics.js +9 -6
- package/dist/lib/diagnostics.js.map +1 -1
- package/dist/lib/git/git-finalize.js +17 -2
- package/dist/lib/git/git-finalize.js.map +1 -1
- package/dist/lib/harness/harness-adapter.d.ts +17 -13
- package/dist/lib/harness/harness-adapter.js +33 -7
- package/dist/lib/harness/harness-adapter.js.map +1 -1
- package/dist/lib/harness/opencode-control.d.ts +73 -0
- package/dist/lib/harness/opencode-control.js +318 -0
- package/dist/lib/harness/opencode-control.js.map +1 -1
- package/dist/lib/harness/opencode-outcome.d.ts +16 -2
- package/dist/lib/harness/opencode-outcome.js +36 -7
- package/dist/lib/harness/opencode-outcome.js.map +1 -1
- package/dist/lib/harness/relay-key.d.ts +23 -0
- package/dist/lib/harness/relay-key.js +36 -0
- package/dist/lib/harness/relay-key.js.map +1 -0
- package/dist/lib/harness/spawn.d.ts +23 -5
- package/dist/lib/harness/spawn.js +264 -69
- package/dist/lib/harness/spawn.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +375 -30
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/dist/lib/scope-guard.d.ts +54 -0
- package/dist/lib/scope-guard.js +246 -0
- package/dist/lib/scope-guard.js.map +1 -0
- package/dist/lib/tools/browser-tool.js +24 -3
- package/dist/lib/tools/browser-tool.js.map +1 -1
- package/dist/lib/workspace/repo-lock.d.ts +5 -1
- package/dist/lib/workspace/repo-lock.js +5 -3
- package/dist/lib/workspace/repo-lock.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_REFS = void 0;
|
|
4
|
+
exports.buildRefScanScript = buildRefScanScript;
|
|
5
|
+
exports.formatRefs = formatRefs;
|
|
6
|
+
exports.scanRefs = scanRefs;
|
|
7
|
+
exports.buildRefInfoScript = buildRefInfoScript;
|
|
8
|
+
exports.buildRefFocusScript = buildRefFocusScript;
|
|
9
|
+
exports.buildRefPointerScript = buildRefPointerScript;
|
|
10
|
+
exports.buildAnnotateScript = buildAnnotateScript;
|
|
11
|
+
exports.buildClearAnnotationScript = buildClearAnnotationScript;
|
|
12
|
+
exports.buildPopoverCountScript = buildPopoverCountScript;
|
|
13
|
+
exports.buildOutsidePointerScript = buildOutsidePointerScript;
|
|
14
|
+
/**
|
|
15
|
+
* Element references for the debug/MCP browser (2026-10).
|
|
16
|
+
*
|
|
17
|
+
* WHY THIS EXISTS: the QA agent used to identify controls with raw CSS
|
|
18
|
+
* selectors, `getElements` indexes or x/y coordinates. On TeamShare's own
|
|
19
|
+
* shadcn/Radix UI that failed constantly: a dropdown, popover or kebab menu
|
|
20
|
+
* renders into a PORTAL at the end of `<body>`, the selector the model guessed
|
|
21
|
+
* matched nothing, and a coordinate read from a screenshot was device-scale.
|
|
22
|
+
* Most of a QA run was spent re-guessing selectors.
|
|
23
|
+
*
|
|
24
|
+
* A ref is a short opaque handle (`e1`, `e2`, ...) minted by ONE in-page scan:
|
|
25
|
+
*
|
|
26
|
+
* - the scanner walks every interactive element in the document, INCLUDING
|
|
27
|
+
* portalled menus/popovers/dialogs (they are still in the DOM, just not
|
|
28
|
+
* where the markup suggests), filters to what is actually visible, and
|
|
29
|
+
* records role/name/coords plus a direct reference to the live node in
|
|
30
|
+
* `window.__tsRefs.nodes`;
|
|
31
|
+
* - actions resolve a ref to the live node first and its centre point second,
|
|
32
|
+
* so an action is never sent to a coordinate that a re-render invalidated;
|
|
33
|
+
* - the live node is checked with `isConnected` before every use. A dead ref
|
|
34
|
+
* is refused with `REF_STALE` AND, when a unique same-role/same-name match
|
|
35
|
+
* exists in the fresh scan, transparently re-pointed at it.
|
|
36
|
+
*
|
|
37
|
+
* A ref list is invalidated by navigating or by a re-render, exactly like a
|
|
38
|
+
* Playwright locator; the session keeps the last list so it can explain what
|
|
39
|
+
* happened instead of failing mute.
|
|
40
|
+
*
|
|
41
|
+
* The scan deliberately runs in the page (one `Runtime.evaluate`) instead of
|
|
42
|
+
* joining `Accessibility.getFullAXTree` with `DOMSnapshot.captureSnapshot`:
|
|
43
|
+
* the DOM scan gives portal content AND geometry in one round trip, and it is
|
|
44
|
+
* the same document the actions operate on. Roles/names are computed from the
|
|
45
|
+
* explicit ARIA attributes first, then the implicit HTML semantics.
|
|
46
|
+
*/
|
|
47
|
+
const cdp_1 = require("./cdp");
|
|
48
|
+
/** Hard cap on refs per scan: keeps the tool result readable and bounded. */
|
|
49
|
+
exports.MAX_REFS = 300;
|
|
50
|
+
/** Elements that can be interacted with (and are useful to name). */
|
|
51
|
+
const REF_SELECTOR = [
|
|
52
|
+
'a[href]',
|
|
53
|
+
'button',
|
|
54
|
+
'input:not([type="hidden"])',
|
|
55
|
+
'select',
|
|
56
|
+
'textarea',
|
|
57
|
+
'summary',
|
|
58
|
+
'[role="button"]',
|
|
59
|
+
'[role="link"]',
|
|
60
|
+
'[role="menuitem"]',
|
|
61
|
+
'[role="menuitemcheckbox"]',
|
|
62
|
+
'[role="menuitemradio"]',
|
|
63
|
+
'[role="option"]',
|
|
64
|
+
'[role="tab"]',
|
|
65
|
+
'[role="checkbox"]',
|
|
66
|
+
'[role="radio"]',
|
|
67
|
+
'[role="switch"]',
|
|
68
|
+
'[role="combobox"]',
|
|
69
|
+
'[role="textbox"]',
|
|
70
|
+
'[contenteditable="true"]',
|
|
71
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
72
|
+
].join(',');
|
|
73
|
+
/**
|
|
74
|
+
* The one scan. Returns `{ at, list }` and stores the live nodes on
|
|
75
|
+
* `window.__tsRefs.nodes` for later `isConnected` checks / focus calls.
|
|
76
|
+
*/
|
|
77
|
+
function buildRefScanScript() {
|
|
78
|
+
return `
|
|
79
|
+
(() => {
|
|
80
|
+
const MAX = ${exports.MAX_REFS};
|
|
81
|
+
const SEL = ${JSON.stringify(REF_SELECTOR)};
|
|
82
|
+
const PORTAL_SEL = '[data-radix-popper-content-wrapper],[data-radix-portal],[data-floating-ui-portal]';
|
|
83
|
+
const DIALOG_SEL = 'dialog[open],[role="dialog"],[aria-modal="true"]';
|
|
84
|
+
const vis = (el) => {
|
|
85
|
+
if (!el || el.nodeType !== 1) return false;
|
|
86
|
+
const r = el.getBoundingClientRect();
|
|
87
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
88
|
+
const cs = getComputedStyle(el);
|
|
89
|
+
if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') return false;
|
|
90
|
+
if (cs.pointerEvents === 'none') return false;
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
const clean = (v, cap) => String(v == null ? '' : v)
|
|
94
|
+
.replace(/\\s+/g, ' ')
|
|
95
|
+
.trim()
|
|
96
|
+
.slice(0, cap || 80);
|
|
97
|
+
const nameOf = (el) => {
|
|
98
|
+
const aria = el.getAttribute('aria-label');
|
|
99
|
+
if (aria) return clean(aria, 80);
|
|
100
|
+
const by = el.getAttribute('aria-labelledby');
|
|
101
|
+
if (by) {
|
|
102
|
+
const t = by.split(/\\s+/).map((id) => {
|
|
103
|
+
const n = document.getElementById(id);
|
|
104
|
+
return n ? (n.innerText || n.textContent || '') : '';
|
|
105
|
+
}).join(' ');
|
|
106
|
+
if (t.trim()) return clean(t, 80);
|
|
107
|
+
}
|
|
108
|
+
const tag = String(el.tagName || '').toLowerCase();
|
|
109
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
110
|
+
const id = el.id;
|
|
111
|
+
if (id) {
|
|
112
|
+
try {
|
|
113
|
+
const l = document.querySelector('label[for="' + CSS.escape(id) + '"]');
|
|
114
|
+
if (l) return clean(l.innerText || l.textContent, 80);
|
|
115
|
+
} catch (e) {}
|
|
116
|
+
}
|
|
117
|
+
const wrap = el.closest && el.closest('label');
|
|
118
|
+
if (wrap) return clean(wrap.innerText || wrap.textContent, 80);
|
|
119
|
+
const ph = el.getAttribute('placeholder');
|
|
120
|
+
if (ph) return clean(ph, 80);
|
|
121
|
+
const nm = el.getAttribute('name');
|
|
122
|
+
if (nm) return clean(nm, 80);
|
|
123
|
+
if (el.id) return clean(el.id, 80);
|
|
124
|
+
}
|
|
125
|
+
const txt = el.innerText || el.textContent || '';
|
|
126
|
+
if (txt.trim()) return clean(txt, 80);
|
|
127
|
+
return clean(
|
|
128
|
+
el.getAttribute('title') ||
|
|
129
|
+
el.getAttribute('alt') ||
|
|
130
|
+
el.getAttribute('value') ||
|
|
131
|
+
'',
|
|
132
|
+
80,
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
const roleOf = (el) => {
|
|
136
|
+
const explicit = el.getAttribute('role');
|
|
137
|
+
if (explicit) return clean(explicit, 24);
|
|
138
|
+
const tag = String(el.tagName || '').toLowerCase();
|
|
139
|
+
const type = String(el.type || '').toLowerCase();
|
|
140
|
+
if (tag === 'a') return 'link';
|
|
141
|
+
if (tag === 'button' || tag === 'summary') return 'button';
|
|
142
|
+
if (tag === 'select') return 'combobox';
|
|
143
|
+
if (tag === 'textarea') return 'textbox';
|
|
144
|
+
if (tag === 'input') {
|
|
145
|
+
if (type === 'submit' || type === 'button' || type === 'reset') return 'button';
|
|
146
|
+
if (type === 'checkbox') return 'checkbox';
|
|
147
|
+
if (type === 'radio') return 'radio';
|
|
148
|
+
if (type === 'search') return 'searchbox';
|
|
149
|
+
return 'textbox';
|
|
150
|
+
}
|
|
151
|
+
if (el.isContentEditable === true) return 'textbox';
|
|
152
|
+
return tag || 'element';
|
|
153
|
+
};
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
const nodes = {};
|
|
156
|
+
const list = [];
|
|
157
|
+
const all = [...document.querySelectorAll(SEL)];
|
|
158
|
+
for (const el of all) {
|
|
159
|
+
if (list.length >= MAX) break;
|
|
160
|
+
if (seen.has(el)) continue;
|
|
161
|
+
seen.add(el);
|
|
162
|
+
// Skip an element wholly covered by another candidate we already took
|
|
163
|
+
// only when it IS that element - nested buttons are real (icon spans
|
|
164
|
+
// are not in the selector, so this is only a dedupe guard).
|
|
165
|
+
if (!vis(el)) continue;
|
|
166
|
+
const r = el.getBoundingClientRect();
|
|
167
|
+
const ref = 'e' + (list.length + 1);
|
|
168
|
+
const row = {
|
|
169
|
+
ref,
|
|
170
|
+
tag: clean(String(el.tagName || '').toLowerCase(), 20) || 'element',
|
|
171
|
+
role: roleOf(el),
|
|
172
|
+
name: nameOf(el),
|
|
173
|
+
disabled:
|
|
174
|
+
el.disabled === true || el.getAttribute('aria-disabled') === 'true',
|
|
175
|
+
inDialog: !!(el.closest && el.closest(DIALOG_SEL)),
|
|
176
|
+
inPortal: !!(el.closest && el.closest(PORTAL_SEL)),
|
|
177
|
+
x: Math.round(r.x + r.width / 2),
|
|
178
|
+
y: Math.round(r.y + r.height / 2),
|
|
179
|
+
width: Math.round(r.width),
|
|
180
|
+
height: Math.round(r.height),
|
|
181
|
+
};
|
|
182
|
+
if (
|
|
183
|
+
(row.tag === 'input' || row.tag === 'textarea') &&
|
|
184
|
+
typeof el.value === 'string'
|
|
185
|
+
) {
|
|
186
|
+
row.valueLength = el.value.length;
|
|
187
|
+
}
|
|
188
|
+
nodes[ref] = el;
|
|
189
|
+
list.push(row);
|
|
190
|
+
}
|
|
191
|
+
window.__tsRefs = { at: Date.now(), list, nodes };
|
|
192
|
+
return { at: window.__tsRefs.at, list };
|
|
193
|
+
})()
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Human/LLM-readable ref lines. One line per element:
|
|
198
|
+
* `e7 button "Publish draft" (640,412) [in menu]`
|
|
199
|
+
*/
|
|
200
|
+
function formatRefs(list, maxLines = 300) {
|
|
201
|
+
return list.slice(0, maxLines).map((r) => {
|
|
202
|
+
const parts = [r.ref, r.role];
|
|
203
|
+
if (r.name)
|
|
204
|
+
parts.push(JSON.stringify(r.name.slice(0, 80)));
|
|
205
|
+
if (r.valueLength !== undefined)
|
|
206
|
+
parts.push(`${r.valueLength} chars`);
|
|
207
|
+
parts.push(`(${r.x},${r.y})`);
|
|
208
|
+
if (r.disabled)
|
|
209
|
+
parts.push('[disabled]');
|
|
210
|
+
if (r.inDialog)
|
|
211
|
+
parts.push('[in dialog]');
|
|
212
|
+
if (r.inPortal)
|
|
213
|
+
parts.push('[in menu/popover]');
|
|
214
|
+
return parts.join(' ');
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/** Run a fresh scan and return it (the caller keeps the list). */
|
|
218
|
+
async function scanRefs(page) {
|
|
219
|
+
const raw = await (0, cdp_1.evaluateJs)(page, buildRefScanScript());
|
|
220
|
+
if (!raw || typeof raw !== 'object')
|
|
221
|
+
return { at: 0, list: [] };
|
|
222
|
+
const rec = raw;
|
|
223
|
+
return {
|
|
224
|
+
at: typeof rec.at === 'number' ? rec.at : 0,
|
|
225
|
+
list: Array.isArray(rec.list) ? rec.list : [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** Liveness + geometry of one ref, from the node the scan stored. */
|
|
229
|
+
function buildRefInfoScript(ref) {
|
|
230
|
+
const id = JSON.stringify(ref);
|
|
231
|
+
return `(() => {
|
|
232
|
+
const s = window.__tsRefs;
|
|
233
|
+
const el = s && s.nodes ? s.nodes[${id}] : null;
|
|
234
|
+
if (!el || el.isConnected !== true) return { ref: ${id}, alive: false };
|
|
235
|
+
const r = el.getBoundingClientRect();
|
|
236
|
+
return {
|
|
237
|
+
ref: ${id},
|
|
238
|
+
alive: true,
|
|
239
|
+
x: Math.round(r.x + r.width / 2),
|
|
240
|
+
y: Math.round(r.y + r.height / 2),
|
|
241
|
+
width: Math.round(r.width),
|
|
242
|
+
height: Math.round(r.height),
|
|
243
|
+
inViewport:
|
|
244
|
+
r.top >= 0 && r.left >= 0 &&
|
|
245
|
+
r.bottom <= (window.innerHeight || 0) && r.right <= (window.innerWidth || 0),
|
|
246
|
+
};
|
|
247
|
+
})()`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Focus a ref'd element, scrolling it to the middle first. Returns whether the
|
|
251
|
+
* element actually took focus plus its tag/role, so `focus` is never a silent
|
|
252
|
+
* no-op: a div without `tabindex` cannot be focused and says so.
|
|
253
|
+
*/
|
|
254
|
+
function buildRefFocusScript(ref) {
|
|
255
|
+
const id = JSON.stringify(ref);
|
|
256
|
+
return `(() => {
|
|
257
|
+
const s = window.__tsRefs;
|
|
258
|
+
const el = s && s.nodes ? s.nodes[${id}] : null;
|
|
259
|
+
if (!el || el.isConnected !== true) return { ref: ${id}, ok: false, alive: false };
|
|
260
|
+
try { el.scrollIntoView({ block: 'center', inline: 'center' }); } catch (e) {}
|
|
261
|
+
try { el.focus({ preventScroll: true }); } catch (e) {}
|
|
262
|
+
const a = document.activeElement;
|
|
263
|
+
return {
|
|
264
|
+
ref: ${id},
|
|
265
|
+
ok: a === el,
|
|
266
|
+
alive: true,
|
|
267
|
+
focused: a === el,
|
|
268
|
+
tag: String((el.tagName || '')).toLowerCase(),
|
|
269
|
+
contentEditable: el.isContentEditable === true,
|
|
270
|
+
valueLength: typeof el.value === 'string' ? el.value.length : undefined,
|
|
271
|
+
};
|
|
272
|
+
})()`;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Dispatch the FULL pointer sequence on a ref'd element, in-page.
|
|
276
|
+
*
|
|
277
|
+
* This is the fallback for controls that ignore a coordinate click: the
|
|
278
|
+
* frontend tours (`frontend/src/modules/tour/utils/acts.ts`) taught us that a
|
|
279
|
+
* bare `.click()` is a SILENT no-op on Radix tabs/menus, which activate on
|
|
280
|
+
* `pointerdown`/`mousedown`. Same recipe, one copy per side of the wire.
|
|
281
|
+
*/
|
|
282
|
+
function buildRefPointerScript(ref) {
|
|
283
|
+
const id = JSON.stringify(ref);
|
|
284
|
+
return `(() => {
|
|
285
|
+
const s = window.__tsRefs;
|
|
286
|
+
const el = s && s.nodes ? s.nodes[${id}] : null;
|
|
287
|
+
if (!el || el.isConnected !== true) return { ref: ${id}, ok: false, alive: false };
|
|
288
|
+
try { el.scrollIntoView({ block: 'center', inline: 'center' }); } catch (e) {}
|
|
289
|
+
const rect = el.getBoundingClientRect();
|
|
290
|
+
const x = Math.round(rect.x + rect.width / 2);
|
|
291
|
+
const y = Math.round(rect.y + rect.height / 2);
|
|
292
|
+
const base = {
|
|
293
|
+
bubbles: true,
|
|
294
|
+
cancelable: true,
|
|
295
|
+
composed: true,
|
|
296
|
+
clientX: x,
|
|
297
|
+
clientY: y,
|
|
298
|
+
view: window,
|
|
299
|
+
button: 0,
|
|
300
|
+
buttons: 1,
|
|
301
|
+
};
|
|
302
|
+
const pointer = (type) => {
|
|
303
|
+
try {
|
|
304
|
+
return new PointerEvent(type, Object.assign({}, base, {
|
|
305
|
+
pointerId: 1,
|
|
306
|
+
pointerType: 'mouse',
|
|
307
|
+
isPrimary: true,
|
|
308
|
+
}));
|
|
309
|
+
} catch (e) {
|
|
310
|
+
return new MouseEvent(type, base);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const mouse = (type) => new MouseEvent(type, base);
|
|
314
|
+
el.dispatchEvent(pointer('pointerover'));
|
|
315
|
+
el.dispatchEvent(pointer('pointerenter'));
|
|
316
|
+
el.dispatchEvent(pointer('pointermove'));
|
|
317
|
+
el.dispatchEvent(pointer('pointerdown'));
|
|
318
|
+
el.dispatchEvent(mouse('mousedown'));
|
|
319
|
+
try { el.focus({ preventScroll: true }); } catch (e) {}
|
|
320
|
+
el.dispatchEvent(pointer('pointerup'));
|
|
321
|
+
el.dispatchEvent(mouse('mouseup'));
|
|
322
|
+
el.dispatchEvent(mouse('click'));
|
|
323
|
+
return { ref: ${id}, ok: true, alive: true, clicked: true, x, y };
|
|
324
|
+
})()`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Draw ref labels over the live page (2026-10).
|
|
328
|
+
*
|
|
329
|
+
* A plain screenshot shows WHAT is on screen; the model still has to guess
|
|
330
|
+
* which button is `e7`. The overlay draws a numbered box on every ref'd
|
|
331
|
+
* element so ONE image both shows the page and names the targets. Removed
|
|
332
|
+
* immediately after the capture - the page is never left modified.
|
|
333
|
+
*/
|
|
334
|
+
function buildAnnotateScript() {
|
|
335
|
+
return `(() => {
|
|
336
|
+
const s = window.__tsRefs;
|
|
337
|
+
if (!s || !s.list) return 0;
|
|
338
|
+
const ID = 'ts-ref-overlay';
|
|
339
|
+
const old = document.getElementById(ID);
|
|
340
|
+
if (old) old.remove();
|
|
341
|
+
const root = document.createElement('div');
|
|
342
|
+
root.id = ID;
|
|
343
|
+
root.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;z-index:2147483647;pointer-events:none;';
|
|
344
|
+
let n = 0;
|
|
345
|
+
for (const item of s.list) {
|
|
346
|
+
const el = s.nodes ? s.nodes[item.ref] : null;
|
|
347
|
+
if (!el || el.isConnected !== true) continue;
|
|
348
|
+
const r = el.getBoundingClientRect();
|
|
349
|
+
if (r.width === 0 || r.height === 0) continue;
|
|
350
|
+
const box = document.createElement('div');
|
|
351
|
+
box.style.cssText =
|
|
352
|
+
'position:fixed;border:2px solid #e11d48;border-radius:3px;box-sizing:border-box;pointer-events:none;left:' +
|
|
353
|
+
Math.round(r.left) + 'px;top:' + Math.round(r.top) + 'px;width:' + Math.round(r.width) +
|
|
354
|
+
'px;height:' + Math.round(r.height) + 'px;';
|
|
355
|
+
const tag = document.createElement('div');
|
|
356
|
+
tag.textContent = item.ref;
|
|
357
|
+
tag.style.cssText =
|
|
358
|
+
'position:fixed;background:#e11d48;color:#ffffff;font:600 11px/1.2 sans-serif;padding:1px 3px;border-radius:2px;pointer-events:none;left:' +
|
|
359
|
+
Math.round(r.left) + 'px;top:' + Math.max(0, Math.round(r.top) - 14) + 'px;';
|
|
360
|
+
root.appendChild(box);
|
|
361
|
+
root.appendChild(tag);
|
|
362
|
+
if (++n >= 120) break;
|
|
363
|
+
}
|
|
364
|
+
document.documentElement.appendChild(root);
|
|
365
|
+
return n;
|
|
366
|
+
})()`;
|
|
367
|
+
}
|
|
368
|
+
/** Remove the ref overlay (always called, even when the capture failed). */
|
|
369
|
+
function buildClearAnnotationScript() {
|
|
370
|
+
return `(() => {
|
|
371
|
+
const el = document.getElementById('ts-ref-overlay');
|
|
372
|
+
if (el) el.remove();
|
|
373
|
+
return true;
|
|
374
|
+
})()`;
|
|
375
|
+
}
|
|
376
|
+
/** How many popovers/menus are currently open (used by closePopup). */
|
|
377
|
+
function buildPopoverCountScript() {
|
|
378
|
+
return `(() => {
|
|
379
|
+
const vis = (el) => {
|
|
380
|
+
if (!el || el.nodeType !== 1) return false;
|
|
381
|
+
const r = el.getBoundingClientRect();
|
|
382
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
383
|
+
const cs = getComputedStyle(el);
|
|
384
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
385
|
+
};
|
|
386
|
+
const n = [...document.querySelectorAll('[data-radix-popper-content-wrapper],[role="menu"],[role="listbox"]')]
|
|
387
|
+
.filter(vis).length;
|
|
388
|
+
return n;
|
|
389
|
+
})()`;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Click OUTSIDE every open layer (the Radix DismissableLayer listens for a
|
|
393
|
+
* document-level `pointerdown`). Coordinate clicks can land on something
|
|
394
|
+
* dangerous in a corner, so this dispatches the pointer sequence on `<body>`
|
|
395
|
+
* at the layer's own outside point instead.
|
|
396
|
+
*/
|
|
397
|
+
function buildOutsidePointerScript() {
|
|
398
|
+
return `(() => {
|
|
399
|
+
const target = document.body;
|
|
400
|
+
if (!target) return false;
|
|
401
|
+
const base = {
|
|
402
|
+
bubbles: true,
|
|
403
|
+
cancelable: true,
|
|
404
|
+
composed: true,
|
|
405
|
+
clientX: 4,
|
|
406
|
+
clientY: 4,
|
|
407
|
+
view: window,
|
|
408
|
+
button: 0,
|
|
409
|
+
buttons: 1,
|
|
410
|
+
};
|
|
411
|
+
const pointer = (type) => {
|
|
412
|
+
try {
|
|
413
|
+
return new PointerEvent(type, Object.assign({}, base, {
|
|
414
|
+
pointerId: 1,
|
|
415
|
+
pointerType: 'mouse',
|
|
416
|
+
isPrimary: true,
|
|
417
|
+
}));
|
|
418
|
+
} catch (e) {
|
|
419
|
+
return new MouseEvent(type, base);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
target.dispatchEvent(pointer('pointerdown'));
|
|
423
|
+
target.dispatchEvent(new MouseEvent('mousedown', base));
|
|
424
|
+
target.dispatchEvent(pointer('pointerup'));
|
|
425
|
+
target.dispatchEvent(new MouseEvent('mouseup', base));
|
|
426
|
+
target.dispatchEvent(new MouseEvent('click', base));
|
|
427
|
+
return true;
|
|
428
|
+
})()`;
|
|
429
|
+
}
|
|
430
|
+
//# sourceMappingURL=refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.js","sourceRoot":"","sources":["../../../src/lib/browser/refs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CdpPage, ElementInfo, PageInfo, PageText, PageLink } from './cdp';
|
|
2
2
|
import { type NetworkEntry, type ConsoleEntry, type WebSocketFrame, type NetworkLogStatus } from './network-log';
|
|
3
3
|
import { type ReadStagedResult, type FillExcelResult } from './xlsx-template';
|
|
4
|
-
import { type LoginResult, type PerformLoginOptions } from './login';
|
|
4
|
+
import { type LoginResult, type PerformLoginOptions, type TypeResult } from './login';
|
|
5
5
|
import { type NativeDialogState, type PagePaginationState, type PageSnapshot, type WaitForKind } from './page-context';
|
|
6
6
|
import { type PageEvent } from './page-events';
|
|
7
|
+
import { type RefElement } from './refs';
|
|
7
8
|
/**
|
|
8
9
|
* Persistent Chrome profile for the AUTO-ANSWER browser (research reads,
|
|
9
10
|
* `browse --auto-answer`, the web-auto-answer tool).
|
|
@@ -55,6 +56,16 @@ export interface BrowserSessionOptions {
|
|
|
55
56
|
* `type()` keeps throwing `LOGIN_BLOCKED` for a credential field.
|
|
56
57
|
*/
|
|
57
58
|
allowCredentials?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* OPT-IN (default false): QA VISION mode (2026-10).
|
|
61
|
+
*
|
|
62
|
+
* When on, every mutating action result carries an inline screenshot (the
|
|
63
|
+
* bridge handler captures it) and `snapshot({refs:true})` can hand back an
|
|
64
|
+
* accessibility-style ref list. Only a QA run turns it on
|
|
65
|
+
* (`browser_open({ qaVision: true })`, instructed by the QA brief), so the
|
|
66
|
+
* interactive browser and the auto-answer path are unchanged.
|
|
67
|
+
*/
|
|
68
|
+
qaVision?: boolean;
|
|
58
69
|
}
|
|
59
70
|
export declare class BrowserSession {
|
|
60
71
|
private readonly id;
|
|
@@ -68,6 +79,14 @@ export declare class BrowserSession {
|
|
|
68
79
|
private _connected;
|
|
69
80
|
/** Opt-in credential entry (see BrowserSessionOptions.allowCredentials). */
|
|
70
81
|
private _allowCredentials;
|
|
82
|
+
/** Opt-in QA vision mode (see BrowserSessionOptions.qaVision). */
|
|
83
|
+
private _qaVision;
|
|
84
|
+
/**
|
|
85
|
+
* The last ref scan (2026-10). Kept so a stale ref can be explained and,
|
|
86
|
+
* when a unique same-role/same-name element exists in the fresh scan,
|
|
87
|
+
* transparently re-pointed - instead of failing with a mute miss.
|
|
88
|
+
*/
|
|
89
|
+
private _lastRefs;
|
|
71
90
|
/**
|
|
72
91
|
* True when THIS bridge process launched the Chrome this session is attached
|
|
73
92
|
* to. Only such a browser may be terminated by the bridge; one the human
|
|
@@ -108,6 +127,12 @@ export declare class BrowserSession {
|
|
|
108
127
|
*/
|
|
109
128
|
setAllowCredentials(allow: boolean): void;
|
|
110
129
|
get allowCredentials(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Toggle QA vision mode (2026-10). Persists across actions, exactly like
|
|
132
|
+
* allowCredentials: `browser_open({ qaVision: true })` sets it once.
|
|
133
|
+
*/
|
|
134
|
+
setQaVision(on: boolean): void;
|
|
135
|
+
get qaVision(): boolean;
|
|
111
136
|
/** The native JS dialog currently blocking the page, if any. */
|
|
112
137
|
get nativeDialog(): NativeDialogState | null;
|
|
113
138
|
/** Last time a command actually used this session (drives idle close). */
|
|
@@ -237,6 +262,12 @@ export declare class BrowserSession {
|
|
|
237
262
|
* Capture a screenshot. Returns base64-encoded data.
|
|
238
263
|
*/
|
|
239
264
|
screenshot(format?: 'jpeg' | 'png', quality?: number): Promise<string>;
|
|
265
|
+
/**
|
|
266
|
+
* Capture a screenshot with numbered ref labels drawn over the interactive
|
|
267
|
+
* elements, then remove the overlay (2026-10). One image shows the page AND
|
|
268
|
+
* names every target; the page is never left modified.
|
|
269
|
+
*/
|
|
270
|
+
annotatedScreenshot(format?: 'jpeg' | 'png', quality?: number): Promise<string>;
|
|
240
271
|
/**
|
|
241
272
|
* Read the network + console evidence collected for this session (QA).
|
|
242
273
|
* `onlyProblems` narrows to failures/redirects; `clear` empties the buffer
|
|
@@ -254,9 +285,95 @@ export declare class BrowserSession {
|
|
|
254
285
|
status: NetworkLogStatus;
|
|
255
286
|
}>;
|
|
256
287
|
/**
|
|
257
|
-
* Click at (x, y)
|
|
288
|
+
* Click at (x, y) CSS pixels. Coordinates OUTSIDE the current viewport are
|
|
289
|
+
* refused loudly — a screenshot pixel is not a CSS pixel (the capture is
|
|
290
|
+
* device-scale), so clicking one lands on the wrong element.
|
|
258
291
|
*/
|
|
259
292
|
click(x: number, y: number): Promise<void>;
|
|
293
|
+
/**
|
|
294
|
+
* Click by reference instead of raw coordinates: an element REF (`e7`, from
|
|
295
|
+
* getRefs / snapshot refs:true), explicit x/y, a fresh `getElements` index,
|
|
296
|
+
* or a CSS selector resolved in-page. Coordinates from a screenshot image are
|
|
297
|
+
* NEVER accepted here — they are device-scale.
|
|
298
|
+
*/
|
|
299
|
+
clickRef(ref: {
|
|
300
|
+
ref?: string;
|
|
301
|
+
x?: number;
|
|
302
|
+
y?: number;
|
|
303
|
+
elementIndex?: number;
|
|
304
|
+
selector?: string;
|
|
305
|
+
}): Promise<{
|
|
306
|
+
x: number;
|
|
307
|
+
y: number;
|
|
308
|
+
refreshedFrom?: string;
|
|
309
|
+
}>;
|
|
310
|
+
/**
|
|
311
|
+
* Fresh element-ref scan (2026-10).
|
|
312
|
+
*
|
|
313
|
+
* Returns the interactive elements of the CURRENT page as `e1 … eN` with
|
|
314
|
+
* role, accessible name, state and CSS-pixel centre. Includes elements
|
|
315
|
+
* inside portalled menus/popovers/dialogs, which is the whole point: a kebab
|
|
316
|
+
* menu that just opened is findable without guessing a selector.
|
|
317
|
+
*/
|
|
318
|
+
getRefs(): Promise<{
|
|
319
|
+
at: number;
|
|
320
|
+
count: number;
|
|
321
|
+
refs: RefElement[];
|
|
322
|
+
lines: string[];
|
|
323
|
+
}>;
|
|
324
|
+
/**
|
|
325
|
+
* Resolve a ref to a clickable point. A ref whose node left the DOM is
|
|
326
|
+
* either re-pointed at a UNIQUE same-role/same-name element from a fresh
|
|
327
|
+
* scan (the common case: a re-render replaced the node), or refused with
|
|
328
|
+
* `REF_STALE` - never silently clicked at stale coordinates.
|
|
329
|
+
*/
|
|
330
|
+
private resolveRefPoint;
|
|
331
|
+
/** Move the pointer to (x, y) without clicking (hover menus/tooltips). */
|
|
332
|
+
hover(x: number, y: number): Promise<void>;
|
|
333
|
+
/** Hover an element by ref (preferred) or CSS selector. */
|
|
334
|
+
hoverRef(ref: {
|
|
335
|
+
ref?: string;
|
|
336
|
+
selector?: string;
|
|
337
|
+
}): Promise<{
|
|
338
|
+
x: number;
|
|
339
|
+
y: number;
|
|
340
|
+
refreshedFrom?: string;
|
|
341
|
+
}>;
|
|
342
|
+
/**
|
|
343
|
+
* Click a ref'd element with the IN-PAGE pointer sequence instead of a CDP
|
|
344
|
+
* coordinate click (2026-10). This is the rescue for a control that ignores
|
|
345
|
+
* a normal click (the Radix lesson from the web tours: some activators only
|
|
346
|
+
* react to a full pointerdown/mousedown gesture). Explicit, never automatic:
|
|
347
|
+
* a blind second click after "nothing happened" could double-submit a form.
|
|
348
|
+
*/
|
|
349
|
+
clickPointerRef(ref: string): Promise<{
|
|
350
|
+
x: number;
|
|
351
|
+
y: number;
|
|
352
|
+
}>;
|
|
353
|
+
/**
|
|
354
|
+
* Focus an element by ref (2026-10). Reports whether focus actually moved:
|
|
355
|
+
* a plain div without tabindex cannot take focus, and claiming `ok:true`
|
|
356
|
+
* while the active element is unchanged is how a QA step "passes" having
|
|
357
|
+
* done nothing.
|
|
358
|
+
*/
|
|
359
|
+
focusRef(ref: string): Promise<{
|
|
360
|
+
ok: boolean;
|
|
361
|
+
alive: boolean;
|
|
362
|
+
tag?: string;
|
|
363
|
+
contentEditable?: boolean;
|
|
364
|
+
refreshedFrom?: string;
|
|
365
|
+
}>;
|
|
366
|
+
/**
|
|
367
|
+
* Close whatever menu/popover layer is open (2026-10): Escape first (Radix
|
|
368
|
+
* DismissableLayer), then a document-level outside pointer sequence for
|
|
369
|
+
* layers that ignore Escape. Reports what is left, so a "still open" menu is
|
|
370
|
+
* visible instead of silently swallowing the next click.
|
|
371
|
+
*/
|
|
372
|
+
closePopup(): Promise<{
|
|
373
|
+
closed: boolean;
|
|
374
|
+
popoversLeft: number;
|
|
375
|
+
}>;
|
|
376
|
+
private countPopovers;
|
|
260
377
|
/**
|
|
261
378
|
* Type text at the current cursor position, VERIFYING it landed.
|
|
262
379
|
*
|
|
@@ -264,10 +381,13 @@ export declare class BrowserSession {
|
|
|
264
381
|
* caller opted in (`browser_open({ allowCredentials: true })`) AND passed a
|
|
265
382
|
* credential for the run - the debug browser is the QA surface, the
|
|
266
383
|
* auto-answer loop is a separate path that still never touches credentials.
|
|
384
|
+
*
|
|
385
|
+
* An insert the page does not reflect is refused with `TYPE_NOT_VERIFIED`
|
|
386
|
+
* (carrying the observed field state) — never reported as success.
|
|
267
387
|
*/
|
|
268
388
|
type(text: string, opts?: {
|
|
269
389
|
clearFirst?: boolean;
|
|
270
|
-
}): Promise<
|
|
390
|
+
}): Promise<TypeResult>;
|
|
271
391
|
/**
|
|
272
392
|
* Deterministically fill a login form (username + password) and submit.
|
|
273
393
|
* Handles the common two-step email -> Continue -> password flow. Requires
|
|
@@ -424,7 +544,12 @@ export declare class BrowserSession {
|
|
|
424
544
|
includeText?: boolean;
|
|
425
545
|
maxTextChars?: number;
|
|
426
546
|
maxFields?: number;
|
|
427
|
-
|
|
547
|
+
/** Also return the interactive element refs (QA vision, 2026-10). */
|
|
548
|
+
refs?: boolean;
|
|
549
|
+
}): Promise<PageSnapshot & {
|
|
550
|
+
refs?: RefElement[];
|
|
551
|
+
refsText?: string[];
|
|
552
|
+
}>;
|
|
428
553
|
/**
|
|
429
554
|
* Bounded state attached to every mutating action result. Never include the
|
|
430
555
|
* page text (that is what `snapshot` is for); a failure returns null so an
|