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.
Files changed (36) hide show
  1. package/README.md +82 -1
  2. package/dist/browser/actions.d.ts +272 -0
  3. package/dist/browser/actions.d.ts.map +1 -1
  4. package/dist/browser/actions.js +797 -0
  5. package/dist/browser/actions.js.map +1 -1
  6. package/dist/browser/manager.d.ts +88 -0
  7. package/dist/browser/manager.d.ts.map +1 -1
  8. package/dist/browser/manager.js +231 -0
  9. package/dist/browser/manager.js.map +1 -1
  10. package/dist/cli/client.d.ts +1 -0
  11. package/dist/cli/client.d.ts.map +1 -1
  12. package/dist/cli/client.js +21 -0
  13. package/dist/cli/client.js.map +1 -1
  14. package/dist/cli/commands/actions.d.ts.map +1 -1
  15. package/dist/cli/commands/actions.js +762 -10
  16. package/dist/cli/commands/actions.js.map +1 -1
  17. package/dist/cli/index.js +1 -1
  18. package/dist/daemon/routes/actions.d.ts.map +1 -1
  19. package/dist/daemon/routes/actions.js +529 -6
  20. package/dist/daemon/routes/actions.js.map +1 -1
  21. package/dist/daemon/routes/browser_control.d.ts +12 -0
  22. package/dist/daemon/routes/browser_control.d.ts.map +1 -0
  23. package/dist/daemon/routes/browser_control.js +172 -0
  24. package/dist/daemon/routes/browser_control.js.map +1 -0
  25. package/dist/daemon/routes/interaction.d.ts +11 -0
  26. package/dist/daemon/routes/interaction.d.ts.map +1 -0
  27. package/dist/daemon/routes/interaction.js +176 -0
  28. package/dist/daemon/routes/interaction.js.map +1 -0
  29. package/dist/daemon/routes/state.d.ts +11 -0
  30. package/dist/daemon/routes/state.d.ts.map +1 -0
  31. package/dist/daemon/routes/state.js +190 -0
  32. package/dist/daemon/routes/state.js.map +1 -0
  33. package/dist/daemon/server.d.ts.map +1 -1
  34. package/dist/daemon/server.js +7 -1
  35. package/dist/daemon/server.js.map +1 -1
  36. 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 { selector, timeout_ms = 5000, frame, purpose, operator, sensitive, retry } = req.body;
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
- return Actions.click(target, selector, timeout_ms, getLogger(), s.id, purpose, inferOperator(req, s, operator));
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 { selector, value, frame, purpose, operator, sensitive, retry } = req.body;
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 { selector, text, delay_ms = 0, frame, purpose, operator, sensitive, retry } = req.body;
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 { selector, key, frame, purpose, operator, sensitive, retry } = req.body;
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 { selector, frame, purpose, operator } = req.body;
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