agentmb 0.1.0 → 0.1.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.
- package/README.md +82 -1
- package/dist/browser/actions.d.ts +272 -0
- package/dist/browser/actions.d.ts.map +1 -1
- package/dist/browser/actions.js +797 -0
- package/dist/browser/actions.js.map +1 -1
- package/dist/browser/manager.d.ts +88 -0
- package/dist/browser/manager.d.ts.map +1 -1
- package/dist/browser/manager.js +231 -0
- package/dist/browser/manager.js.map +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +21 -0
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/commands/actions.d.ts.map +1 -1
- package/dist/cli/commands/actions.js +762 -10
- package/dist/cli/commands/actions.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/daemon/routes/actions.d.ts.map +1 -1
- package/dist/daemon/routes/actions.js +529 -6
- package/dist/daemon/routes/actions.js.map +1 -1
- package/dist/daemon/routes/browser_control.d.ts +12 -0
- package/dist/daemon/routes/browser_control.d.ts.map +1 -0
- package/dist/daemon/routes/browser_control.js +172 -0
- package/dist/daemon/routes/browser_control.js.map +1 -0
- package/dist/daemon/routes/interaction.d.ts +11 -0
- package/dist/daemon/routes/interaction.d.ts.map +1 -0
- package/dist/daemon/routes/interaction.js +176 -0
- package/dist/daemon/routes/interaction.js.map +1 -0
- package/dist/daemon/routes/state.d.ts +11 -0
- package/dist/daemon/routes/state.d.ts.map +1 -0
- package/dist/daemon/routes/state.js +190 -0
- package/dist/daemon/routes/state.js.map +1 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +7 -1
- package/dist/daemon/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -32,8 +32,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.registerActionRoutes = registerActionRoutes;
|
|
40
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
37
41
|
require("../types"); // T11: Fastify type augmentation
|
|
38
42
|
const Actions = __importStar(require("../../browser/actions"));
|
|
39
43
|
const actions_1 = require("../../browser/actions");
|
|
@@ -109,6 +113,10 @@ function inferOperator(req, s, explicit) {
|
|
|
109
113
|
return 'agentmb-daemon';
|
|
110
114
|
}
|
|
111
115
|
// ---------------------------------------------------------------------------
|
|
116
|
+
// R07-T01/T14: element_id / ref_id → CSS selector resolver
|
|
117
|
+
// Built as a factory inside registerActionRoutes to access BrowserManager.
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
112
120
|
// r06-c02: Policy check helper
|
|
113
121
|
// ---------------------------------------------------------------------------
|
|
114
122
|
/**
|
|
@@ -142,6 +150,51 @@ function registerActionRoutes(server, registry) {
|
|
|
142
150
|
function getLogger() {
|
|
143
151
|
return server.auditLogger;
|
|
144
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* R07-T14: Resolve an action target (selector | element_id | ref_id) to CSS selector.
|
|
155
|
+
* ref_id: validates snapshot exists + page_rev matches (→ 409 stale_ref on mismatch).
|
|
156
|
+
* element_id: injected DOM attribute selector.
|
|
157
|
+
* selector: passed through as-is.
|
|
158
|
+
*/
|
|
159
|
+
function resolveTarget(input, reply, sessionId) {
|
|
160
|
+
if (input.ref_id) {
|
|
161
|
+
const bm = server.browserManager;
|
|
162
|
+
if (!bm || !sessionId) {
|
|
163
|
+
reply.code(500).send({ error: 'ref_id resolution requires BrowserManager' });
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const colonIdx = input.ref_id.lastIndexOf(':');
|
|
167
|
+
if (colonIdx === -1) {
|
|
168
|
+
reply.code(400).send({ error: 'Invalid ref_id format; expected "snap_XXXXXX:eN"' });
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const snapshotId = input.ref_id.slice(0, colonIdx);
|
|
172
|
+
const eid = input.ref_id.slice(colonIdx + 1);
|
|
173
|
+
const snapshot = bm.getSnapshot(sessionId, snapshotId);
|
|
174
|
+
if (!snapshot) {
|
|
175
|
+
reply.code(409).send({ error: 'stale_ref', ref_id: input.ref_id, message: 'Snapshot not found or expired; call snapshot_map again' });
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const currentRev = bm.getPageRev(sessionId);
|
|
179
|
+
if (snapshot.page_rev !== currentRev) {
|
|
180
|
+
reply.code(409).send({
|
|
181
|
+
error: 'stale_ref',
|
|
182
|
+
ref_id: input.ref_id,
|
|
183
|
+
snapshot_page_rev: snapshot.page_rev,
|
|
184
|
+
current_page_rev: currentRev,
|
|
185
|
+
message: 'Page has changed since snapshot was taken; call snapshot_map again',
|
|
186
|
+
});
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return `[data-agentmb-eid="${eid}"]`;
|
|
190
|
+
}
|
|
191
|
+
if (input.element_id)
|
|
192
|
+
return `[data-agentmb-eid="${input.element_id}"]`;
|
|
193
|
+
if (input.selector)
|
|
194
|
+
return input.selector;
|
|
195
|
+
reply.code(400).send({ error: 'Either selector, element_id, or ref_id is required' });
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
145
198
|
/** Resolve a live session or send 404/410 and return null */
|
|
146
199
|
function resolve(id, reply) {
|
|
147
200
|
const result = registry.getLive(id);
|
|
@@ -172,24 +225,49 @@ function registerActionRoutes(server, registry) {
|
|
|
172
225
|
return Actions.navigate(s.page, url, wait_until, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
173
226
|
});
|
|
174
227
|
// POST /api/v1/sessions/:id/click
|
|
228
|
+
// T21: add fallback_x / fallback_y — if DOM click fails, retry via page.mouse.click()
|
|
175
229
|
server.post('/api/v1/sessions/:id/click', async (req, reply) => {
|
|
176
230
|
const s = resolve(req.params.id, reply);
|
|
177
231
|
if (!s)
|
|
178
232
|
return;
|
|
179
|
-
const {
|
|
233
|
+
const { timeout_ms = 5000, frame, purpose, operator, sensitive, retry, fallback_x, fallback_y } = req.body;
|
|
234
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
235
|
+
if (!selector)
|
|
236
|
+
return;
|
|
180
237
|
if (!await applyPolicy(server, req.params.id, (0, engine_1.extractDomain)(s.page.url()), 'click', { sensitive, retry }, reply))
|
|
181
238
|
return;
|
|
182
239
|
const target = resolveOrReply(s.page, frame, reply);
|
|
183
240
|
if (!target)
|
|
184
241
|
return;
|
|
185
|
-
|
|
242
|
+
try {
|
|
243
|
+
return await Actions.click(target, selector, timeout_ms, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
244
|
+
}
|
|
245
|
+
catch (domErr) {
|
|
246
|
+
// T21 dual-track: if fallback coordinates provided, retry via mouse.click()
|
|
247
|
+
if (fallback_x !== undefined && fallback_y !== undefined && s.page) {
|
|
248
|
+
try {
|
|
249
|
+
await s.page.mouse.click(fallback_x, fallback_y);
|
|
250
|
+
const duration_ms = 0;
|
|
251
|
+
return { status: 'ok', selector, track: 'coords', fallback_x, fallback_y, duration_ms };
|
|
252
|
+
}
|
|
253
|
+
catch (coordErr) {
|
|
254
|
+
// Both tracks failed — report original DOM error
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (domErr instanceof Actions.ActionDiagnosticsError)
|
|
258
|
+
return reply.code(422).send(domErr.diagnostics);
|
|
259
|
+
throw domErr;
|
|
260
|
+
}
|
|
186
261
|
});
|
|
187
262
|
// POST /api/v1/sessions/:id/fill
|
|
188
263
|
server.post('/api/v1/sessions/:id/fill', async (req, reply) => {
|
|
189
264
|
const s = resolve(req.params.id, reply);
|
|
190
265
|
if (!s)
|
|
191
266
|
return;
|
|
192
|
-
const {
|
|
267
|
+
const { value, frame, purpose, operator, sensitive, retry } = req.body;
|
|
268
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
269
|
+
if (!selector)
|
|
270
|
+
return;
|
|
193
271
|
if (!await applyPolicy(server, req.params.id, (0, engine_1.extractDomain)(s.page.url()), 'fill', { sensitive, retry }, reply))
|
|
194
272
|
return;
|
|
195
273
|
const target = resolveOrReply(s.page, frame, reply);
|
|
@@ -257,7 +335,11 @@ function registerActionRoutes(server, registry) {
|
|
|
257
335
|
const s = resolve(req.params.id, reply);
|
|
258
336
|
if (!s)
|
|
259
337
|
return;
|
|
260
|
-
const {
|
|
338
|
+
const { text, delay_ms = 0, frame, purpose, operator, sensitive, retry } = req.body;
|
|
339
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
340
|
+
if (!selector)
|
|
341
|
+
return;
|
|
342
|
+
// shadow 'selector' is now resolved; keep original destructure pattern below
|
|
261
343
|
if (!await applyPolicy(server, req.params.id, (0, engine_1.extractDomain)(s.page.url()), 'type', { sensitive, retry }, reply))
|
|
262
344
|
return;
|
|
263
345
|
const target = resolveOrReply(s.page, frame, reply);
|
|
@@ -277,7 +359,10 @@ function registerActionRoutes(server, registry) {
|
|
|
277
359
|
const s = resolve(req.params.id, reply);
|
|
278
360
|
if (!s)
|
|
279
361
|
return;
|
|
280
|
-
const {
|
|
362
|
+
const { key, frame, purpose, operator, sensitive, retry } = req.body;
|
|
363
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
364
|
+
if (!selector)
|
|
365
|
+
return;
|
|
281
366
|
if (!await applyPolicy(server, req.params.id, (0, engine_1.extractDomain)(s.page.url()), 'press', { sensitive, retry }, reply))
|
|
282
367
|
return;
|
|
283
368
|
const target = resolveOrReply(s.page, frame, reply);
|
|
@@ -315,7 +400,10 @@ function registerActionRoutes(server, registry) {
|
|
|
315
400
|
const s = resolve(req.params.id, reply);
|
|
316
401
|
if (!s)
|
|
317
402
|
return;
|
|
318
|
-
const {
|
|
403
|
+
const { frame, purpose, operator } = req.body;
|
|
404
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
405
|
+
if (!selector)
|
|
406
|
+
return;
|
|
319
407
|
const target = resolveOrReply(s.page, frame, reply);
|
|
320
408
|
if (!target)
|
|
321
409
|
return;
|
|
@@ -421,5 +509,440 @@ function registerActionRoutes(server, registry) {
|
|
|
421
509
|
const tail = req.query.tail ? parseInt(req.query.tail) : 50;
|
|
422
510
|
return logger.tail(req.params.id, tail);
|
|
423
511
|
});
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// R07-T01: element_map — scan page, assign stable element IDs
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
server.post('/api/v1/sessions/:id/element_map', async (req, reply) => {
|
|
516
|
+
const s = resolve(req.params.id, reply);
|
|
517
|
+
if (!s)
|
|
518
|
+
return;
|
|
519
|
+
const { scope, limit = 500, purpose, operator } = req.body ?? {};
|
|
520
|
+
try {
|
|
521
|
+
return await Actions.elementMap(s.page, { scope, limit }, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
525
|
+
return reply.code(422).send(e.diagnostics);
|
|
526
|
+
throw e;
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
// R07-T02: get — read a property from a page element
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
server.post('/api/v1/sessions/:id/get', async (req, reply) => {
|
|
533
|
+
const s = resolve(req.params.id, reply);
|
|
534
|
+
if (!s)
|
|
535
|
+
return;
|
|
536
|
+
const { property, attr_name, frame, purpose, operator } = req.body;
|
|
537
|
+
if (!property)
|
|
538
|
+
return reply.code(400).send({ error: 'property is required (text|html|value|attr|count|box)' });
|
|
539
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
540
|
+
if (!selector)
|
|
541
|
+
return;
|
|
542
|
+
const target = resolveOrReply(s.page, frame, reply);
|
|
543
|
+
if (!target)
|
|
544
|
+
return;
|
|
545
|
+
try {
|
|
546
|
+
return await Actions.getProperty(target, selector, property, attr_name, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
547
|
+
}
|
|
548
|
+
catch (e) {
|
|
549
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
550
|
+
return reply.code(422).send(e.diagnostics);
|
|
551
|
+
throw e;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
// R07-T02: assert — check element state
|
|
556
|
+
// ---------------------------------------------------------------------------
|
|
557
|
+
server.post('/api/v1/sessions/:id/assert', async (req, reply) => {
|
|
558
|
+
const s = resolve(req.params.id, reply);
|
|
559
|
+
if (!s)
|
|
560
|
+
return;
|
|
561
|
+
const { property, expected = true, frame, purpose, operator } = req.body;
|
|
562
|
+
if (!property)
|
|
563
|
+
return reply.code(400).send({ error: 'property is required (visible|enabled|checked)' });
|
|
564
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
565
|
+
if (!selector)
|
|
566
|
+
return;
|
|
567
|
+
const target = resolveOrReply(s.page, frame, reply);
|
|
568
|
+
if (!target)
|
|
569
|
+
return;
|
|
570
|
+
try {
|
|
571
|
+
return await Actions.assertState(target, selector, property, expected, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
572
|
+
}
|
|
573
|
+
catch (e) {
|
|
574
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
575
|
+
return reply.code(422).send(e.diagnostics);
|
|
576
|
+
throw e;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
// ---------------------------------------------------------------------------
|
|
580
|
+
// R07-T07: wait_page_stable — network idle + DOM quiescence + overlay check
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
server.post('/api/v1/sessions/:id/wait_page_stable', async (req, reply) => {
|
|
583
|
+
const s = resolve(req.params.id, reply);
|
|
584
|
+
if (!s)
|
|
585
|
+
return;
|
|
586
|
+
const { timeout_ms = 10000, dom_stable_ms = 300, overlay_selector, purpose, operator } = req.body ?? {};
|
|
587
|
+
try {
|
|
588
|
+
return await Actions.waitPageStable(s.page, { timeout_ms, dom_stable_ms, overlay_selector }, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
592
|
+
return reply.code(422).send(e.diagnostics);
|
|
593
|
+
throw e;
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// R07-T03: Interaction primitives
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
server.post('/api/v1/sessions/:id/dblclick', async (req, reply) => {
|
|
600
|
+
const s = resolve(req.params.id, reply);
|
|
601
|
+
if (!s)
|
|
602
|
+
return;
|
|
603
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
604
|
+
if (!selector)
|
|
605
|
+
return;
|
|
606
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
607
|
+
if (!target)
|
|
608
|
+
return;
|
|
609
|
+
try {
|
|
610
|
+
return await Actions.dblclick(target, selector, req.body.timeout_ms ?? 5000, getLogger(), s.id, req.body.purpose, inferOperator(req, s, req.body.operator));
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
614
|
+
return reply.code(422).send(e.diagnostics);
|
|
615
|
+
throw e;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
server.post('/api/v1/sessions/:id/focus', async (req, reply) => {
|
|
619
|
+
const s = resolve(req.params.id, reply);
|
|
620
|
+
if (!s)
|
|
621
|
+
return;
|
|
622
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
623
|
+
if (!selector)
|
|
624
|
+
return;
|
|
625
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
626
|
+
if (!target)
|
|
627
|
+
return;
|
|
628
|
+
try {
|
|
629
|
+
return await Actions.focus(target, selector, getLogger(), s.id, req.body.purpose, inferOperator(req, s, req.body.operator));
|
|
630
|
+
}
|
|
631
|
+
catch (e) {
|
|
632
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
633
|
+
return reply.code(422).send(e.diagnostics);
|
|
634
|
+
throw e;
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
server.post('/api/v1/sessions/:id/check', async (req, reply) => {
|
|
638
|
+
const s = resolve(req.params.id, reply);
|
|
639
|
+
if (!s)
|
|
640
|
+
return;
|
|
641
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
642
|
+
if (!selector)
|
|
643
|
+
return;
|
|
644
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
645
|
+
if (!target)
|
|
646
|
+
return;
|
|
647
|
+
try {
|
|
648
|
+
return await Actions.check(target, selector, req.body.timeout_ms ?? 5000, getLogger(), s.id, req.body.purpose, inferOperator(req, s, req.body.operator));
|
|
649
|
+
}
|
|
650
|
+
catch (e) {
|
|
651
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
652
|
+
return reply.code(422).send(e.diagnostics);
|
|
653
|
+
throw e;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
server.post('/api/v1/sessions/:id/uncheck', async (req, reply) => {
|
|
657
|
+
const s = resolve(req.params.id, reply);
|
|
658
|
+
if (!s)
|
|
659
|
+
return;
|
|
660
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
661
|
+
if (!selector)
|
|
662
|
+
return;
|
|
663
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
664
|
+
if (!target)
|
|
665
|
+
return;
|
|
666
|
+
try {
|
|
667
|
+
return await Actions.uncheck(target, selector, req.body.timeout_ms ?? 5000, getLogger(), s.id, req.body.purpose, inferOperator(req, s, req.body.operator));
|
|
668
|
+
}
|
|
669
|
+
catch (e) {
|
|
670
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
671
|
+
return reply.code(422).send(e.diagnostics);
|
|
672
|
+
throw e;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
server.post('/api/v1/sessions/:id/scroll', async (req, reply) => {
|
|
676
|
+
const s = resolve(req.params.id, reply);
|
|
677
|
+
if (!s)
|
|
678
|
+
return;
|
|
679
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
680
|
+
if (!selector)
|
|
681
|
+
return;
|
|
682
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
683
|
+
if (!target)
|
|
684
|
+
return;
|
|
685
|
+
const { delta_x = 0, delta_y = 300, purpose, operator } = req.body;
|
|
686
|
+
try {
|
|
687
|
+
return await Actions.scroll(target, selector, { delta_x, delta_y }, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
688
|
+
}
|
|
689
|
+
catch (e) {
|
|
690
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
691
|
+
return reply.code(422).send(e.diagnostics);
|
|
692
|
+
throw e;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
server.post('/api/v1/sessions/:id/scroll_into_view', async (req, reply) => {
|
|
696
|
+
const s = resolve(req.params.id, reply);
|
|
697
|
+
if (!s)
|
|
698
|
+
return;
|
|
699
|
+
const selector = resolveTarget(req.body, reply, s.id);
|
|
700
|
+
if (!selector)
|
|
701
|
+
return;
|
|
702
|
+
const target = resolveOrReply(s.page, req.body.frame, reply);
|
|
703
|
+
if (!target)
|
|
704
|
+
return;
|
|
705
|
+
try {
|
|
706
|
+
return await Actions.scrollIntoView(target, selector, getLogger(), s.id, req.body.purpose, inferOperator(req, s, req.body.operator));
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
710
|
+
return reply.code(422).send(e.diagnostics);
|
|
711
|
+
throw e;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
server.post('/api/v1/sessions/:id/drag', async (req, reply) => {
|
|
715
|
+
const s = resolve(req.params.id, reply);
|
|
716
|
+
if (!s)
|
|
717
|
+
return;
|
|
718
|
+
const { source, source_element_id, target, target_element_id, purpose, operator } = req.body;
|
|
719
|
+
const src = source_element_id ? `[data-agentmb-eid="${source_element_id}"]` : source;
|
|
720
|
+
const tgt = target_element_id ? `[data-agentmb-eid="${target_element_id}"]` : target;
|
|
721
|
+
if (!src || !tgt)
|
|
722
|
+
return reply.code(400).send({ error: 'source and target are required (selector or element_id)' });
|
|
723
|
+
try {
|
|
724
|
+
return await Actions.drag(s.page, src, tgt, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
725
|
+
}
|
|
726
|
+
catch (e) {
|
|
727
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
728
|
+
return reply.code(422).send(e.diagnostics);
|
|
729
|
+
throw e;
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
server.post('/api/v1/sessions/:id/mouse_move', async (req, reply) => {
|
|
733
|
+
const s = resolve(req.params.id, reply);
|
|
734
|
+
if (!s)
|
|
735
|
+
return;
|
|
736
|
+
const { x, y, purpose, operator } = req.body;
|
|
737
|
+
try {
|
|
738
|
+
return await Actions.mouseMove(s.page, x, y, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
739
|
+
}
|
|
740
|
+
catch (e) {
|
|
741
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
742
|
+
return reply.code(422).send(e.diagnostics);
|
|
743
|
+
throw e;
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
server.post('/api/v1/sessions/:id/mouse_down', async (req, reply) => {
|
|
747
|
+
const s = resolve(req.params.id, reply);
|
|
748
|
+
if (!s)
|
|
749
|
+
return;
|
|
750
|
+
const { x, y, button = 'left', purpose, operator } = req.body;
|
|
751
|
+
try {
|
|
752
|
+
return await Actions.mouseDown(s.page, { x, y, button }, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
753
|
+
}
|
|
754
|
+
catch (e) {
|
|
755
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
756
|
+
return reply.code(422).send(e.diagnostics);
|
|
757
|
+
throw e;
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
server.post('/api/v1/sessions/:id/mouse_up', async (req, reply) => {
|
|
761
|
+
const s = resolve(req.params.id, reply);
|
|
762
|
+
if (!s)
|
|
763
|
+
return;
|
|
764
|
+
const { button = 'left', purpose, operator } = req.body;
|
|
765
|
+
try {
|
|
766
|
+
return await Actions.mouseUp(s.page, button, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
770
|
+
return reply.code(422).send(e.diagnostics);
|
|
771
|
+
throw e;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
server.post('/api/v1/sessions/:id/key_down', async (req, reply) => {
|
|
775
|
+
const s = resolve(req.params.id, reply);
|
|
776
|
+
if (!s)
|
|
777
|
+
return;
|
|
778
|
+
const { key, purpose, operator } = req.body;
|
|
779
|
+
try {
|
|
780
|
+
return await Actions.keyDown(s.page, key, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
781
|
+
}
|
|
782
|
+
catch (e) {
|
|
783
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
784
|
+
return reply.code(422).send(e.diagnostics);
|
|
785
|
+
throw e;
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
server.post('/api/v1/sessions/:id/key_up', async (req, reply) => {
|
|
789
|
+
const s = resolve(req.params.id, reply);
|
|
790
|
+
if (!s)
|
|
791
|
+
return;
|
|
792
|
+
const { key, purpose, operator } = req.body;
|
|
793
|
+
try {
|
|
794
|
+
return await Actions.keyUp(s.page, key, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
795
|
+
}
|
|
796
|
+
catch (e) {
|
|
797
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
798
|
+
return reply.code(422).send(e.diagnostics);
|
|
799
|
+
throw e;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
// R07-T04: Wait / navigation control
|
|
804
|
+
// ---------------------------------------------------------------------------
|
|
805
|
+
server.post('/api/v1/sessions/:id/back', async (req, reply) => {
|
|
806
|
+
const s = resolve(req.params.id, reply);
|
|
807
|
+
if (!s)
|
|
808
|
+
return;
|
|
809
|
+
const { timeout_ms = 5000, wait_until = 'load', purpose, operator } = req.body ?? {};
|
|
810
|
+
try {
|
|
811
|
+
return await Actions.back(s.page, timeout_ms, wait_until, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
812
|
+
}
|
|
813
|
+
catch (e) {
|
|
814
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
815
|
+
return reply.code(422).send(e.diagnostics);
|
|
816
|
+
throw e;
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
server.post('/api/v1/sessions/:id/forward', async (req, reply) => {
|
|
820
|
+
const s = resolve(req.params.id, reply);
|
|
821
|
+
if (!s)
|
|
822
|
+
return;
|
|
823
|
+
const { timeout_ms = 5000, wait_until = 'load', purpose, operator } = req.body ?? {};
|
|
824
|
+
try {
|
|
825
|
+
return await Actions.forward(s.page, timeout_ms, wait_until, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
826
|
+
}
|
|
827
|
+
catch (e) {
|
|
828
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
829
|
+
return reply.code(422).send(e.diagnostics);
|
|
830
|
+
throw e;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
server.post('/api/v1/sessions/:id/reload', async (req, reply) => {
|
|
834
|
+
const s = resolve(req.params.id, reply);
|
|
835
|
+
if (!s)
|
|
836
|
+
return;
|
|
837
|
+
const { timeout_ms = 10000, wait_until = 'load', purpose, operator } = req.body ?? {};
|
|
838
|
+
try {
|
|
839
|
+
return await Actions.reload(s.page, timeout_ms, wait_until, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
840
|
+
}
|
|
841
|
+
catch (e) {
|
|
842
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
843
|
+
return reply.code(422).send(e.diagnostics);
|
|
844
|
+
throw e;
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
server.post('/api/v1/sessions/:id/wait_text', async (req, reply) => {
|
|
848
|
+
const s = resolve(req.params.id, reply);
|
|
849
|
+
if (!s)
|
|
850
|
+
return;
|
|
851
|
+
const { text, timeout_ms = 5000, frame, purpose, operator } = req.body;
|
|
852
|
+
const target = resolveOrReply(s.page, frame, reply);
|
|
853
|
+
if (!target)
|
|
854
|
+
return;
|
|
855
|
+
try {
|
|
856
|
+
return await Actions.waitForText(target, text, timeout_ms, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
857
|
+
}
|
|
858
|
+
catch (e) {
|
|
859
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
860
|
+
return reply.code(422).send(e.diagnostics);
|
|
861
|
+
throw e;
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
server.post('/api/v1/sessions/:id/wait_load_state', async (req, reply) => {
|
|
865
|
+
const s = resolve(req.params.id, reply);
|
|
866
|
+
if (!s)
|
|
867
|
+
return;
|
|
868
|
+
const { state = 'load', timeout_ms = 10000, purpose, operator } = req.body ?? {};
|
|
869
|
+
try {
|
|
870
|
+
return await Actions.waitForLoadState(s.page, state, timeout_ms, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
871
|
+
}
|
|
872
|
+
catch (e) {
|
|
873
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
874
|
+
return reply.code(422).send(e.diagnostics);
|
|
875
|
+
throw e;
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
server.post('/api/v1/sessions/:id/wait_function', async (req, reply) => {
|
|
879
|
+
const s = resolve(req.params.id, reply);
|
|
880
|
+
if (!s)
|
|
881
|
+
return;
|
|
882
|
+
const { expression, timeout_ms = 5000, purpose, operator } = req.body;
|
|
883
|
+
try {
|
|
884
|
+
return await Actions.waitForFunction(s.page, expression, timeout_ms, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
885
|
+
}
|
|
886
|
+
catch (e) {
|
|
887
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
888
|
+
return reply.code(422).send(e.diagnostics);
|
|
889
|
+
throw e;
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
// ---------------------------------------------------------------------------
|
|
893
|
+
// R07-T08: Generic scroll primitives
|
|
894
|
+
// ---------------------------------------------------------------------------
|
|
895
|
+
server.post('/api/v1/sessions/:id/scroll_until', async (req, reply) => {
|
|
896
|
+
const s = resolve(req.params.id, reply);
|
|
897
|
+
if (!s)
|
|
898
|
+
return;
|
|
899
|
+
const { purpose, operator, ...opts } = req.body ?? {};
|
|
900
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
901
|
+
try {
|
|
902
|
+
return await Actions.scrollUntil(s.page, opts, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
903
|
+
}
|
|
904
|
+
catch (e) {
|
|
905
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
906
|
+
return reply.code(422).send(e.diagnostics);
|
|
907
|
+
throw e;
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
server.post('/api/v1/sessions/:id/load_more_until', async (req, reply) => {
|
|
911
|
+
const s = resolve(req.params.id, reply);
|
|
912
|
+
if (!s)
|
|
913
|
+
return;
|
|
914
|
+
const { purpose, operator, ...opts } = req.body;
|
|
915
|
+
try {
|
|
916
|
+
return await Actions.loadMoreUntil(s.page, opts, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
917
|
+
}
|
|
918
|
+
catch (e) {
|
|
919
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
920
|
+
return reply.code(422).send(e.diagnostics);
|
|
921
|
+
throw e;
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
// ---------------------------------------------------------------------------
|
|
925
|
+
// R07-T13: snapshot_map — versioned element scan with page_rev tracking
|
|
926
|
+
// ---------------------------------------------------------------------------
|
|
927
|
+
server.post('/api/v1/sessions/:id/snapshot_map', async (req, reply) => {
|
|
928
|
+
const s = resolve(req.params.id, reply);
|
|
929
|
+
if (!s)
|
|
930
|
+
return;
|
|
931
|
+
const { scope, limit = 500, purpose, operator } = req.body ?? {};
|
|
932
|
+
const bm = server.browserManager;
|
|
933
|
+
try {
|
|
934
|
+
const elemResult = await Actions.elementMap(s.page, { scope, limit }, getLogger(), s.id, purpose, inferOperator(req, s, operator));
|
|
935
|
+
const snapshotId = 'snap_' + crypto_1.default.randomBytes(4).toString('hex');
|
|
936
|
+
const pageRev = bm?.getPageRev(s.id) ?? 0;
|
|
937
|
+
const elements = elemResult.elements.map((el) => ({ ...el, ref_id: `${snapshotId}:${el.element_id}` }));
|
|
938
|
+
bm?.storeSnapshot(s.id, { snapshot_id: snapshotId, page_rev: pageRev, url: s.page.url(), elements, created_at: Date.now() });
|
|
939
|
+
return { status: 'ok', snapshot_id: snapshotId, page_rev: pageRev, url: s.page.url(), elements, count: elements.length, duration_ms: elemResult.duration_ms };
|
|
940
|
+
}
|
|
941
|
+
catch (e) {
|
|
942
|
+
if (e instanceof actions_1.ActionDiagnosticsError)
|
|
943
|
+
return reply.code(422).send(e.diagnostics);
|
|
944
|
+
throw e;
|
|
945
|
+
}
|
|
946
|
+
});
|
|
424
947
|
}
|
|
425
948
|
//# sourceMappingURL=actions.js.map
|