@arbidocs/blocks 0.5.126 → 0.5.127

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 (94) hide show
  1. package/README.md +19 -0
  2. package/dist/{ConnectedCallDock-733O5M36.cjs → ConnectedCallDock-XJOYVVAH.cjs} +5 -5
  3. package/dist/{ConnectedCallDock-733O5M36.cjs.map → ConnectedCallDock-XJOYVVAH.cjs.map} +1 -1
  4. package/dist/{ConnectedCallRoom-TAUGSRGV.cjs → ConnectedCallRoom-CUDYAPQK.cjs} +5 -5
  5. package/dist/{ConnectedCallRoom-TAUGSRGV.cjs.map → ConnectedCallRoom-CUDYAPQK.cjs.map} +1 -1
  6. package/dist/{ConnectedNativeDocxComparison-HDE522YC.cjs → ConnectedNativeDocxComparison-JUF2TI2P.cjs} +9 -9
  7. package/dist/{ConnectedNativeDocxComparison-HDE522YC.cjs.map → ConnectedNativeDocxComparison-JUF2TI2P.cjs.map} +1 -1
  8. package/dist/ConnectedPresentation-SI3L77J4.cjs +1 -1
  9. package/dist/{ConnectedSlidesTab-JSPU6EY5.cjs → ConnectedSlidesTab-E76OVY34.cjs} +3 -3
  10. package/dist/{ConnectedSlidesTab-JSPU6EY5.cjs.map → ConnectedSlidesTab-E76OVY34.cjs.map} +1 -1
  11. package/dist/{ConnectedStreamedWordArtifact-4FHPOAFZ.cjs → ConnectedStreamedWordArtifact-Q64DRRQD.cjs} +9 -9
  12. package/dist/{ConnectedStreamedWordArtifact-4FHPOAFZ.cjs.map → ConnectedStreamedWordArtifact-Q64DRRQD.cjs.map} +1 -1
  13. package/dist/{DocumentReviewCard-BDNXP3D4.cjs → DocumentReviewCard-M53XFLI2.cjs} +4 -4
  14. package/dist/{DocumentReviewCard-BDNXP3D4.cjs.map → DocumentReviewCard-M53XFLI2.cjs.map} +1 -1
  15. package/dist/{FilesViewerDialog-WQRNSKWM.cjs → FilesViewerDialog-V6FSRQTP.cjs} +8 -8
  16. package/dist/{FilesViewerDialog-WQRNSKWM.cjs.map → FilesViewerDialog-V6FSRQTP.cjs.map} +1 -1
  17. package/dist/{NativeDocxEditor-SUDULUN3.cjs → NativeDocxEditor-2PTEK6ND.cjs} +6 -6
  18. package/dist/{NativeDocxEditor-SUDULUN3.cjs.map → NativeDocxEditor-2PTEK6ND.cjs.map} +1 -1
  19. package/dist/WorkspaceTableView-3XYAV4BV.cjs +1 -1
  20. package/dist/agents/index.cjs +1 -1
  21. package/dist/ai-IEIR7SC2.cjs +1 -1
  22. package/dist/calls/index.cjs +2 -2
  23. package/dist/{chunk-Q6N5D3R5.cjs → chunk-23I5EUZA.cjs} +45 -15
  24. package/dist/chunk-23I5EUZA.cjs.map +1 -0
  25. package/dist/{chunk-OHHKBURG.cjs → chunk-2SU37RZA.cjs} +378 -11
  26. package/dist/chunk-2SU37RZA.cjs.map +1 -0
  27. package/dist/{chunk-6B4CCDDB.cjs → chunk-ESVRVZ53.cjs} +5 -5
  28. package/dist/{chunk-6B4CCDDB.cjs.map → chunk-ESVRVZ53.cjs.map} +1 -1
  29. package/dist/{chunk-KA3F6TIH.cjs → chunk-IWHP7FAB.cjs} +12 -5
  30. package/dist/chunk-IWHP7FAB.cjs.map +1 -0
  31. package/dist/{chunk-FB3JXSQT.cjs → chunk-JVLOIIA7.cjs} +8 -8
  32. package/dist/{chunk-FB3JXSQT.cjs.map → chunk-JVLOIIA7.cjs.map} +1 -1
  33. package/dist/{chunk-FGOJBOZX.cjs → chunk-M6WVIGYP.cjs} +4 -4
  34. package/dist/{chunk-FGOJBOZX.cjs.map → chunk-M6WVIGYP.cjs.map} +1 -1
  35. package/dist/{chunk-I4X72WUT.cjs → chunk-MMSHIIKG.cjs} +6 -6
  36. package/dist/{chunk-I4X72WUT.cjs.map → chunk-MMSHIIKG.cjs.map} +1 -1
  37. package/dist/{chunk-LXM5QXII.cjs → chunk-MXBXZXXQ.cjs} +3 -3
  38. package/dist/{chunk-LXM5QXII.cjs.map → chunk-MXBXZXXQ.cjs.map} +1 -1
  39. package/dist/{chunk-463QM764.cjs → chunk-NG2EE7HR.cjs} +3 -3
  40. package/dist/{chunk-463QM764.cjs.map → chunk-NG2EE7HR.cjs.map} +1 -1
  41. package/dist/chunk-NTDQQLDD.cjs +18 -0
  42. package/dist/{chunk-OALQGO5C.cjs.map → chunk-NTDQQLDD.cjs.map} +1 -1
  43. package/dist/{chunk-XNB27POJ.cjs → chunk-Q6IKXWPG.cjs} +4 -4
  44. package/dist/{chunk-XNB27POJ.cjs.map → chunk-Q6IKXWPG.cjs.map} +1 -1
  45. package/dist/{chunk-LOB2ATS5.cjs → chunk-VLQCLQAS.cjs} +5 -5
  46. package/dist/{chunk-LOB2ATS5.cjs.map → chunk-VLQCLQAS.cjs.map} +1 -1
  47. package/dist/{chunk-4KEQ62SL.cjs → chunk-YP5EBGLU.cjs} +6 -6
  48. package/dist/{chunk-4KEQ62SL.cjs.map → chunk-YP5EBGLU.cjs.map} +1 -1
  49. package/dist/{chunk-CMPHJRVV.cjs → chunk-ZYGAXD3N.cjs} +3 -3
  50. package/dist/{chunk-CMPHJRVV.cjs.map → chunk-ZYGAXD3N.cjs.map} +1 -1
  51. package/dist/connect/docx.cjs +13 -13
  52. package/dist/connect/index.cjs +233 -287
  53. package/dist/connect/index.cjs.map +1 -1
  54. package/dist/connect/viewer.cjs +6 -6
  55. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js +6 -3
  56. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js.map +2 -2
  57. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js +12 -2
  58. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js.map +2 -2
  59. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js +1 -1
  60. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js.map +1 -1
  61. package/dist/connection/remote-control/browser-diagnostics.d.ts +7 -0
  62. package/dist/connection/remote-control/browser-diagnostics.js +109 -0
  63. package/dist/connection/remote-control/browser-diagnostics.js.map +7 -0
  64. package/dist/connection/remote-control/remoteControl.d.ts +9 -1
  65. package/dist/connection/remote-control/remoteControl.js +130 -10
  66. package/dist/connection/remote-control/remoteControl.js.map +2 -2
  67. package/dist/connection/remote-control/screen-sharing.d.ts +11 -0
  68. package/dist/connection/remote-control/screen-sharing.js +78 -0
  69. package/dist/connection/remote-control/screen-sharing.js.map +7 -0
  70. package/dist/connection/report-problem/screen-capture.d.ts +1 -0
  71. package/dist/connection/report-problem/screen-capture.js +2 -1
  72. package/dist/connection/report-problem/screen-capture.js.map +2 -2
  73. package/dist/docx/NativeDocxEditor.js +14 -12
  74. package/dist/docx/NativeDocxEditor.js.map +2 -2
  75. package/dist/docx/index.cjs +38 -38
  76. package/dist/docx/useVersionAutosave.d.ts +10 -0
  77. package/dist/docx/useVersionAutosave.js +36 -0
  78. package/dist/docx/useVersionAutosave.js.map +7 -0
  79. package/dist/embed/index.cjs +9 -9
  80. package/dist/facts/index.cjs +43 -43
  81. package/dist/index.cjs +97 -97
  82. package/dist/settings/index.cjs +1 -1
  83. package/dist/shell/RemoteControlPill.d.ts +2 -1
  84. package/dist/shell/RemoteControlPill.js +10 -3
  85. package/dist/shell/RemoteControlPill.js.map +2 -2
  86. package/dist/shell/index.cjs +21 -21
  87. package/dist/studio/index.cjs +1 -1
  88. package/dist/workflow-canvas/index.cjs +12 -12
  89. package/node_modules/@arbidocs/coedit/package.json +1 -1
  90. package/package.json +5 -5
  91. package/dist/chunk-KA3F6TIH.cjs.map +0 -1
  92. package/dist/chunk-OALQGO5C.cjs +0 -18
  93. package/dist/chunk-OHHKBURG.cjs.map +0 -1
  94. package/dist/chunk-Q6N5D3R5.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var chunkFB3JXSQT_cjs = require('../chunk-FB3JXSQT.cjs');
4
+ var chunkJVLOIIA7_cjs = require('../chunk-JVLOIIA7.cjs');
5
5
  require('../chunk-JITYC4OS.cjs');
6
6
  require('../chunk-WVZLAAIZ.cjs');
7
7
  require('../chunk-X7ALF4MP.cjs');
@@ -17,7 +17,7 @@ require('../chunk-HYUKPHCT.cjs');
17
17
  require('../chunk-ALK3PUUB.cjs');
18
18
  require('../chunk-223D4727.cjs');
19
19
  require('../chunk-SNUBQMIS.cjs');
20
- require('../chunk-LOB2ATS5.cjs');
20
+ require('../chunk-VLQCLQAS.cjs');
21
21
  require('../chunk-4ONPEVTP.cjs');
22
22
  require('../chunk-V3KLUWQQ.cjs');
23
23
  require('../chunk-P5AQYC4D.cjs');
@@ -30,7 +30,7 @@ require('../chunk-DTMBAFIL.cjs');
30
30
  require('../chunk-ZESMRGDV.cjs');
31
31
  require('../chunk-VCEX7COE.cjs');
32
32
  require('../chunk-34HQO4ZQ.cjs');
33
- require('../chunk-KA3F6TIH.cjs');
33
+ require('../chunk-IWHP7FAB.cjs');
34
34
  require('../chunk-K4763GD5.cjs');
35
35
  require('../chunk-2RLA6QO2.cjs');
36
36
  require('../chunk-MZZ3SBQL.cjs');
@@ -43,7 +43,7 @@ require('../chunk-SK6HQ46Q.cjs');
43
43
  require('../chunk-MNIQDXM6.cjs');
44
44
  require('../chunk-E3Y7RI3J.cjs');
45
45
  require('../chunk-TG2GM7WJ.cjs');
46
- require('../chunk-CMPHJRVV.cjs');
46
+ require('../chunk-ZYGAXD3N.cjs');
47
47
  require('../chunk-OEJQFPWO.cjs');
48
48
  require('../chunk-OYXWQTHF.cjs');
49
49
  require('../chunk-S237ORM3.cjs');
@@ -63,12 +63,12 @@ require('../chunk-VG24E4JJ.cjs');
63
63
  require('../chunk-U2FFG2B3.cjs');
64
64
  require('../chunk-KKYNTWDU.cjs');
65
65
  require('../chunk-M74FWYNL.cjs');
66
+ require('../chunk-VZOOGA47.cjs');
66
67
  require('../chunk-HQDR43JC.cjs');
67
68
  require('../chunk-JR2YSBD5.cjs');
68
69
  require('../chunk-3YZKDCWY.cjs');
69
70
  require('../chunk-SJSGSHIT.cjs');
70
71
  require('../chunk-ZW6FPIFC.cjs');
71
- require('../chunk-VZOOGA47.cjs');
72
72
  require('../chunk-CT3SLXB7.cjs');
73
73
  require('../chunk-DEXUL3T6.cjs');
74
74
  require('../chunk-AFQTQC7A.cjs');
@@ -94,7 +94,7 @@ require('../chunk-PLIF5CUV.cjs');
94
94
 
95
95
  Object.defineProperty(exports, "ConnectedDocumentViewer", {
96
96
  enumerable: true,
97
- get: function () { return chunkFB3JXSQT_cjs.ConnectedDocumentViewer; }
97
+ get: function () { return chunkJVLOIIA7_cjs.ConnectedDocumentViewer; }
98
98
  });
99
99
  Object.defineProperty(exports, "ConnectedDocumentToolsProvider", {
100
100
  enumerable: true,
@@ -18,6 +18,8 @@ import { useGuidedRuns } from "../../guided/useGuidedRuns.js";
18
18
  import { useRemoteControlActions } from "./remoteControl.js";
19
19
  function RunPlan({ request }) {
20
20
  const { sequences } = useGuidedRuns();
21
+ if (request.screenRequestId)
22
+ return /* @__PURE__ */ jsx("p", { "data-testid": "remote-control-consent-plan", children: request.screenQuestion });
21
23
  const steps = request.steps ?? [];
22
24
  if (steps.length === 0) {
23
25
  const label = request.sequenceId ? sequences[request.sequenceId]?.label : void 0;
@@ -75,7 +77,7 @@ function ConsentPrompt() {
75
77
  /* @__PURE__ */ jsxs("span", { children: [
76
78
  /* @__PURE__ */ jsx("span", { className: "font-semibold", "data-testid": "remote-control-consent-agent-name", children: pending.agentName }),
77
79
  " ",
78
- "wants to control your screen"
80
+ pending.screenRequestId ? "wants to view and control your screen" : "wants to control your screen"
79
81
  ] })
80
82
  ] }),
81
83
  /* @__PURE__ */ jsxs(DialogDescription, { className: "sr-only", children: [
@@ -86,6 +88,7 @@ function ConsentPrompt() {
86
88
  /* @__PURE__ */ jsxs(ModalSection, { className: "space-y-3 text-sm text-muted-foreground", children: [
87
89
  /* @__PURE__ */ jsx("p", { children: "Remote control lets this assistant click and type in the app on your behalf \u2014 you'll see the cursor move and click as it works." }),
88
90
  /* @__PURE__ */ jsx("p", { children: "You can turn it off at any time, and it is off by default at the start of every session." }),
91
+ pending.screenRequestId && /* @__PURE__ */ jsx("p", { children: "Choose a tab or window to share. While sharing, the assistant can inspect fresh images, recent ARBI console errors and failed requests, and click or type inside ARBI. Stop sharing ends viewing and control together. No audio or recording is captured." }),
89
92
  /* @__PURE__ */ jsx(RunPlan, { request: pending }),
90
93
  pending.agentExtId && /* @__PURE__ */ jsxs("p", { className: "text-xs", children: [
91
94
  "Requesting agent:",
@@ -107,7 +110,7 @@ function ConsentPrompt() {
107
110
  className: "w-full",
108
111
  onClick: () => remote.grantAndRun({ forSession: true }),
109
112
  "data-testid": "remote-control-consent-allow-session",
110
- children: "Allow for this session"
113
+ children: pending.screenRequestId ? "Start sharing" : "Allow for this session"
111
114
  }
112
115
  ),
113
116
  /* @__PURE__ */ jsx(
@@ -118,7 +121,7 @@ function ConsentPrompt() {
118
121
  className: "w-full",
119
122
  onClick: () => remote.grantAndRun({ forSession: false }),
120
123
  "data-testid": "remote-control-consent-allow-once",
121
- children: "Just this once"
124
+ children: pending.screenRequestId ? "Share one image" : "Just this once"
122
125
  }
123
126
  ),
124
127
  /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlConsent.tsx"],
4
- "sourcesContent": ["/**\n * ConnectedRemoteControlConsent \u2014 the consent gate for backend-driven remote control.\n *\n * Opens whenever an `agt-\u2026` agent has requested to drive the UI over the websocket and remote\n * control is not already enabled this session (the request sits in the session's remote-control\n * `pending`). It names the requesting agent explicitly (GUARANTEE #3) so the user knows which of\n * their assistants is asking, and offers three choices:\n * - Allow for this session (primary) \u2014 enables it until the session ends.\n * - Just this once \u2014 runs the one requested action, stays off afterwards.\n * - Deny (cancel) \u2014 runs nothing.\n * It says what will run (the run's name, or the inline steps' notes and targets), and opens on\n * \"Just this once\", so Enter never grants the whole session. Nothing shows while the site has not\n * turned remote control on (`guided.remoteControl`).\n *\n * Mounted once by `ConnectedAppRuntime`. Its state lives in the session, so no props.\n */\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Button,\n} from '@arbidocs/react/ui'\nimport { useRef } from 'react'\nimport { useRemoteControl, type RemoteControlRequest } from '@arbidocs/react'\nimport { MousePointerClick } from 'lucide-react'\nimport { ModalSection } from '../../settings/ModalScaffold'\nimport { REMOTE_CONTROL_SURFACE, useMountedSurface } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\n/** What the request will run: the registered run's name, or its inline steps. */\nfunction RunPlan({ request }: { request: RemoteControlRequest }) {\n const { sequences } = useGuidedRuns()\n const steps = request.steps ?? []\n if (steps.length === 0) {\n const label = request.sequenceId ? sequences[request.sequenceId]?.label : undefined\n return (\n <p data-testid=\"remote-control-consent-plan\">\n It will run:{' '}\n <span className=\"font-medium text-foreground\">{label ?? request.sequenceId}</span>\n </p>\n )\n }\n // Every step, however many (the list scrolls): nothing runs that the user was not shown.\n return (\n <div data-testid=\"remote-control-consent-plan\">\n <p>\n It will ({steps.length} {steps.length === 1 ? 'step' : 'steps'}):\n </p>\n <ol className=\"mt-1 max-h-40 list-decimal space-y-0.5 overflow-y-auto pl-5 text-xs\">\n {steps.map((step, i) => (\n <li key={i}>\n {step.do === 'type' ? 'Type into' : 'Click'}{' '}\n <span className=\"break-all font-mono text-foreground\">{step.target}</span>\n {step.note ? <> \u2014 {step.note}</> : null}\n </li>\n ))}\n </ol>\n </div>\n )\n}\n\nexport function ConnectedRemoteControlConsent(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <ConsentPrompt /> : null\n}\n\nfunction ConsentPrompt(): React.ReactElement | null {\n const pending = useRemoteControl((s) => s.pending)\n const remote = useRemoteControlActions()\n const onceRef = useRef<HTMLButtonElement>(null)\n // A remote request is refused while no prompt is on screen to ask.\n useMountedSurface('consent')\n\n // Closing the dialog by any means (Escape, overlay, X) is a denial.\n const handleOpenChange = (next: boolean): void => {\n if (!next) remote.deny()\n }\n\n if (!pending) return null\n\n return (\n <Dialog open={pending !== null} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n data-testid=\"remote-control-consent-modal\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n // The safest choice has the focus: Enter runs it once, never grants the session.\n onOpenAutoFocus={(event) => {\n event.preventDefault()\n onceRef.current?.focus()\n }}\n >\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <MousePointerClick className=\"h-5 w-5 text-primary\" />\n <span>\n <span className=\"font-semibold\" data-testid=\"remote-control-consent-agent-name\">\n {pending.agentName}\n </span>{' '}\n wants to control your screen\n </span>\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {pending.agentName} is requesting permission to click and type in the app on your\n behalf. Choose whether to allow it for this session, just this once, or deny.\n </DialogDescription>\n </DialogHeader>\n\n <ModalSection className=\"space-y-3 text-sm text-muted-foreground\">\n <p>\n Remote control lets this assistant click and type in the app on your behalf \u2014\n you&apos;ll see the cursor move and click as it works.\n </p>\n <p>\n You can turn it off at any time, and it is off by default at the start of every session.\n </p>\n <RunPlan request={pending} />\n {pending.agentExtId && (\n <p className=\"text-xs\">\n Requesting agent:{' '}\n <span\n className=\"font-mono text-foreground\"\n data-testid=\"remote-control-consent-agent-id\"\n >\n {pending.agentExtId}\n </span>\n </p>\n )}\n </ModalSection>\n\n <DialogFooter className=\"flex-col gap-2 sm:flex-col sm:space-x-0\">\n <Button\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: true })}\n data-testid=\"remote-control-consent-allow-session\"\n >\n Allow for this session\n </Button>\n <Button\n ref={onceRef}\n variant=\"outline\"\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: false })}\n data-testid=\"remote-control-consent-allow-once\"\n >\n Just this once\n </Button>\n <Button\n variant=\"ghost\"\n className=\"w-full\"\n onClick={() => remote.deny()}\n data-testid=\"remote-control-consent-deny\"\n >\n Deny\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * ConnectedRemoteControlConsent \u2014 the consent gate for backend-driven remote control.\n *\n * Opens whenever an `agt-\u2026` agent has requested to drive the UI over the websocket and remote\n * control is not already enabled this session (the request sits in the session's remote-control\n * `pending`). It names the requesting agent explicitly (GUARANTEE #3) so the user knows which of\n * their assistants is asking, and offers three choices:\n * - Allow for this session (primary) \u2014 enables it until the session ends.\n * - Just this once \u2014 runs the one requested action, stays off afterwards.\n * - Deny (cancel) \u2014 runs nothing.\n * It says what will run (the run's name, or the inline steps' notes and targets), and opens on\n * \"Just this once\", so Enter never grants the whole session. Nothing shows while the site has not\n * turned remote control on (`guided.remoteControl`).\n *\n * Mounted once by `ConnectedAppRuntime`. Its state lives in the session, so no props.\n */\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Button,\n} from '@arbidocs/react/ui'\nimport { useRef } from 'react'\nimport { useRemoteControl, type RemoteControlRequest } from '@arbidocs/react'\nimport { MousePointerClick } from 'lucide-react'\nimport { ModalSection } from '../../settings/ModalScaffold'\nimport { REMOTE_CONTROL_SURFACE, useMountedSurface } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\n/** What the request will run: the registered run's name, or its inline steps. */\nfunction RunPlan({ request }: { request: RemoteControlRequest }) {\n const { sequences } = useGuidedRuns()\n if (request.screenRequestId)\n return <p data-testid=\"remote-control-consent-plan\">{request.screenQuestion}</p>\n const steps = request.steps ?? []\n if (steps.length === 0) {\n const label = request.sequenceId ? sequences[request.sequenceId]?.label : undefined\n return (\n <p data-testid=\"remote-control-consent-plan\">\n It will run:{' '}\n <span className=\"font-medium text-foreground\">{label ?? request.sequenceId}</span>\n </p>\n )\n }\n // Every step, however many (the list scrolls): nothing runs that the user was not shown.\n return (\n <div data-testid=\"remote-control-consent-plan\">\n <p>\n It will ({steps.length} {steps.length === 1 ? 'step' : 'steps'}):\n </p>\n <ol className=\"mt-1 max-h-40 list-decimal space-y-0.5 overflow-y-auto pl-5 text-xs\">\n {steps.map((step, i) => (\n <li key={i}>\n {step.do === 'type' ? 'Type into' : 'Click'}{' '}\n <span className=\"break-all font-mono text-foreground\">{step.target}</span>\n {step.note ? <> \u2014 {step.note}</> : null}\n </li>\n ))}\n </ol>\n </div>\n )\n}\n\nexport function ConnectedRemoteControlConsent(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <ConsentPrompt /> : null\n}\n\nfunction ConsentPrompt(): React.ReactElement | null {\n const pending = useRemoteControl((s) => s.pending)\n const remote = useRemoteControlActions()\n const onceRef = useRef<HTMLButtonElement>(null)\n // A remote request is refused while no prompt is on screen to ask.\n useMountedSurface('consent')\n\n // Closing the dialog by any means (Escape, overlay, X) is a denial.\n const handleOpenChange = (next: boolean): void => {\n if (!next) remote.deny()\n }\n\n if (!pending) return null\n\n return (\n <Dialog open={pending !== null} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n data-testid=\"remote-control-consent-modal\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n // The safest choice has the focus: Enter runs it once, never grants the session.\n onOpenAutoFocus={(event) => {\n event.preventDefault()\n onceRef.current?.focus()\n }}\n >\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <MousePointerClick className=\"h-5 w-5 text-primary\" />\n <span>\n <span className=\"font-semibold\" data-testid=\"remote-control-consent-agent-name\">\n {pending.agentName}\n </span>{' '}\n {pending.screenRequestId\n ? 'wants to view and control your screen'\n : 'wants to control your screen'}\n </span>\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {pending.agentName} is requesting permission to click and type in the app on your\n behalf. Choose whether to allow it for this session, just this once, or deny.\n </DialogDescription>\n </DialogHeader>\n\n <ModalSection className=\"space-y-3 text-sm text-muted-foreground\">\n <p>\n Remote control lets this assistant click and type in the app on your behalf \u2014\n you&apos;ll see the cursor move and click as it works.\n </p>\n <p>\n You can turn it off at any time, and it is off by default at the start of every session.\n </p>\n {pending.screenRequestId && (\n <p>\n Choose a tab or window to share. While sharing, the assistant can inspect fresh\n images, recent ARBI console errors and failed requests, and click or type inside ARBI.\n Stop sharing ends viewing and control together. No audio or recording is captured.\n </p>\n )}\n <RunPlan request={pending} />\n {pending.agentExtId && (\n <p className=\"text-xs\">\n Requesting agent:{' '}\n <span\n className=\"font-mono text-foreground\"\n data-testid=\"remote-control-consent-agent-id\"\n >\n {pending.agentExtId}\n </span>\n </p>\n )}\n </ModalSection>\n\n <DialogFooter className=\"flex-col gap-2 sm:flex-col sm:space-x-0\">\n <Button\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: true })}\n data-testid=\"remote-control-consent-allow-session\"\n >\n {pending.screenRequestId ? 'Start sharing' : 'Allow for this session'}\n </Button>\n <Button\n ref={onceRef}\n variant=\"outline\"\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: false })}\n data-testid=\"remote-control-consent-allow-once\"\n >\n {pending.screenRequestId ? 'Share one image' : 'Just this once'}\n </Button>\n <Button\n variant=\"ghost\"\n className=\"w-full\"\n onClick={() => remote.deny()}\n data-testid=\"remote-control-consent-deny\"\n >\n Deny\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,12 +9,22 @@ function ConnectedRemoteControlIndicator() {
9
9
  }
10
10
  function Indicator() {
11
11
  const enabled = useRemoteControl((s) => s.enabled);
12
+ const sharing = useRemoteControl((s) => !!s.sharing);
12
13
  const controllingAgent = useRemoteControl((s) => s.controllingAgent);
13
14
  const remote = useRemoteControlActions();
14
15
  if (!enabled && !controllingAgent) return null;
15
16
  const isActive = controllingAgent !== null;
16
- const label = isActive ? `${controllingAgent.name} is controlling your screen` : "Remote control on";
17
- return /* @__PURE__ */ jsx(RemoteControlPill, { isActive, label, onDisable: () => remote.disable() });
17
+ const label = sharing ? controllingAgent ? `${controllingAgent.name} \xB7 screen sharing and control on` : "Sharing screen \xB7 remote control on" : isActive ? `${controllingAgent.name} is controlling your screen` : "Remote control on";
18
+ return /* @__PURE__ */ jsx(
19
+ RemoteControlPill,
20
+ {
21
+ isActive: isActive || sharing,
22
+ label,
23
+ disableLabel: sharing ? "Stop sharing" : void 0,
24
+ showDisableLabel: sharing,
25
+ onDisable: () => remote.disable()
26
+ }
27
+ );
18
28
  }
19
29
  export {
20
30
  ConnectedRemoteControlIndicator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlIndicator.tsx"],
4
- "sourcesContent": ["/**\n * ConnectedRemoteControlIndicator: the persistent remote-control pill (`RemoteControlPill`) on the\n * session's state. Shown while remote control is enabled this session (or a run is executing);\n * labels WHO is driving while a websocket-driven run executes, and always offers an immediate\n * disable (GUARANTEE #5). Hidden entirely when remote control is off and no run is active, and\n * while the site has not turned remote control on (`guided.remoteControl`).\n */\n\nimport { useRemoteControl } from '@arbidocs/react'\nimport { RemoteControlPill } from '../../shell/RemoteControlPill'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlIndicator(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <Indicator /> : null\n}\n\nfunction Indicator(): React.ReactElement | null {\n const enabled = useRemoteControl((s) => s.enabled)\n const controllingAgent = useRemoteControl((s) => s.controllingAgent)\n const remote = useRemoteControlActions()\n\n if (!enabled && !controllingAgent) return null\n\n const isActive = controllingAgent !== null\n const label = isActive\n ? `${controllingAgent.name} is controlling your screen`\n : 'Remote control on'\n\n return <RemoteControlPill isActive={isActive} label={label} onDisable={() => remote.disable()} />\n}\n"],
5
- "mappings": ";AAcyC;AANzC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,kCAA6D;AAC3E,SAAO,cAAc,EAAE,gBAAgB,oBAAC,aAAU,IAAK;AACzD;AAEA,SAAS,YAAuC;AAC9C,QAAM,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,mBAAmB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;AACnE,QAAM,SAAS,wBAAwB;AAEvC,MAAI,CAAC,WAAW,CAAC,iBAAkB,QAAO;AAE1C,QAAM,WAAW,qBAAqB;AACtC,QAAM,QAAQ,WACV,GAAG,iBAAiB,IAAI,gCACxB;AAEJ,SAAO,oBAAC,qBAAkB,UAAoB,OAAc,WAAW,MAAM,OAAO,QAAQ,GAAG;AACjG;",
4
+ "sourcesContent": ["/**\n * ConnectedRemoteControlIndicator: the persistent remote-control pill (`RemoteControlPill`) on the\n * session's state. Shown while remote control is enabled this session (or a run is executing);\n * labels WHO is driving while a websocket-driven run executes, and always offers an immediate\n * disable (GUARANTEE #5). Hidden entirely when remote control is off and no run is active, and\n * while the site has not turned remote control on (`guided.remoteControl`).\n */\n\nimport { useRemoteControl } from '@arbidocs/react'\nimport { RemoteControlPill } from '../../shell/RemoteControlPill'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlIndicator(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <Indicator /> : null\n}\n\nfunction Indicator(): React.ReactElement | null {\n const enabled = useRemoteControl((s) => s.enabled)\n const sharing = useRemoteControl((s) => !!s.sharing)\n const controllingAgent = useRemoteControl((s) => s.controllingAgent)\n const remote = useRemoteControlActions()\n\n if (!enabled && !controllingAgent) return null\n\n const isActive = controllingAgent !== null\n const label = sharing\n ? controllingAgent\n ? `${controllingAgent.name} \u00B7 screen sharing and control on`\n : 'Sharing screen \u00B7 remote control on'\n : isActive\n ? `${controllingAgent.name} is controlling your screen`\n : 'Remote control on'\n\n return (\n <RemoteControlPill\n isActive={isActive || sharing}\n label={label}\n disableLabel={sharing ? 'Stop sharing' : undefined}\n showDisableLabel={sharing}\n onDisable={() => remote.disable()}\n />\n )\n}\n"],
5
+ "mappings": ";AAcyC;AANzC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,kCAA6D;AAC3E,SAAO,cAAc,EAAE,gBAAgB,oBAAC,aAAU,IAAK;AACzD;AAEA,SAAS,YAAuC;AAC9C,QAAM,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,UAAU,iBAAiB,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO;AACnD,QAAM,mBAAmB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;AACnE,QAAM,SAAS,wBAAwB;AAEvC,MAAI,CAAC,WAAW,CAAC,iBAAkB,QAAO;AAE1C,QAAM,WAAW,qBAAqB;AACtC,QAAM,QAAQ,UACV,mBACE,GAAG,iBAAiB,IAAI,wCACxB,0CACF,WACE,GAAG,iBAAiB,IAAI,gCACxB;AAEN,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB;AAAA,MACA,cAAc,UAAU,iBAAiB;AAAA,MACzC,kBAAkB;AAAA,MAClB,WAAW,MAAM,OAAO,QAAQ;AAAA;AAAA,EAClC;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,7 @@ function RemoteControlSwitch() {
18
18
  label: "Remote control",
19
19
  labelFor: "remoteControlEnabled",
20
20
  ...{ [REMOTE_CONTROL_SURFACE]: "" },
21
- description: "Let an assistant click and type in the app on your behalf. Off by default every session \u2014 you'll always see the cursor move as it works.",
21
+ description: "Let an assistant help in the app. Screen sharing asks you to choose a tab or window; turning this off stops both sharing and control. Off by default every session.",
22
22
  children: /* @__PURE__ */ jsx(
23
23
  Switch,
24
24
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlSwitch.tsx"],
4
- "sourcesContent": ["/**\n * The remote-control switch of Settings \u2192 Privacy. Letting an assistant click and type in the app\n * is a privacy-adjacent permission, so it is switched there rather than from the account menu.\n * Off by default every session. Not shown while the site has not turned remote control on\n * (`guided.remoteControl`).\n */\nimport { useRemoteControl } from '@arbidocs/react'\nimport { Switch } from '@arbidocs/react/ui'\nimport { SettingsRow } from '../../settings/SettingsPanel'\nimport { REMOTE_CONTROL_SURFACE } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlSwitch() {\n return useGuidedRuns().remoteControl ? <RemoteControlSwitch /> : null\n}\n\nfunction RemoteControlSwitch() {\n const enabled = useRemoteControl((state) => state.enabled)\n const remote = useRemoteControlActions()\n return (\n <SettingsRow\n label=\"Remote control\"\n labelFor=\"remoteControlEnabled\"\n // The whole row, its label too, is remote control's own: a remote run never acts on it.\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n description=\"Let an assistant click and type in the app on your behalf. Off by default every session \u2014 you'll always see the cursor move as it works.\"\n >\n <Switch\n id=\"remoteControlEnabled\"\n checked={enabled}\n onCheckedChange={(value) => (value ? remote.enable() : remote.disable())}\n data-testid=\"toggle-remote-control\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n />\n </SettingsRow>\n )\n}\n"],
4
+ "sourcesContent": ["/**\n * The remote-control switch of Settings \u2192 Privacy. Letting an assistant click and type in the app\n * is a privacy-adjacent permission, so it is switched there rather than from the account menu.\n * Off by default every session. Not shown while the site has not turned remote control on\n * (`guided.remoteControl`).\n */\nimport { useRemoteControl } from '@arbidocs/react'\nimport { Switch } from '@arbidocs/react/ui'\nimport { SettingsRow } from '../../settings/SettingsPanel'\nimport { REMOTE_CONTROL_SURFACE } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlSwitch() {\n return useGuidedRuns().remoteControl ? <RemoteControlSwitch /> : null\n}\n\nfunction RemoteControlSwitch() {\n const enabled = useRemoteControl((state) => state.enabled)\n const remote = useRemoteControlActions()\n return (\n <SettingsRow\n label=\"Remote control\"\n labelFor=\"remoteControlEnabled\"\n // The whole row, its label too, is remote control's own: a remote run never acts on it.\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n description=\"Let an assistant help in the app. Screen sharing asks you to choose a tab or window; turning this off stops both sharing and control. Off by default every session.\"\n >\n <Switch\n id=\"remoteControlEnabled\"\n checked={enabled}\n onCheckedChange={(value) => (value ? remote.enable() : remote.disable())}\n data-testid=\"toggle-remote-control\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n />\n </SettingsRow>\n )\n}\n"],
5
5
  "mappings": ";AAcyC;AARzC,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,+BAA+B;AAC7C,SAAO,cAAc,EAAE,gBAAgB,oBAAC,uBAAoB,IAAK;AACnE;AAEA,SAAS,sBAAsB;AAC7B,QAAM,UAAU,iBAAiB,CAAC,UAAU,MAAM,OAAO;AACzD,QAAM,SAAS,wBAAwB;AACvC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,UAAS;AAAA,MAER,GAAG,EAAE,CAAC,sBAAsB,GAAG,GAAG;AAAA,MACnC,aAAY;AAAA,MAEZ;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,SAAS;AAAA,UACT,iBAAiB,CAAC,UAAW,QAAQ,OAAO,OAAO,IAAI,OAAO,QAAQ;AAAA,UACtE,eAAY;AAAA,UACX,GAAG,EAAE,CAAC,sBAAsB,GAAG,GAAG;AAAA;AAAA,MACrC;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,7 @@
1
+ /** Session-only browser evidence, collected only while the user is sharing. No bodies, headers or storage. */
2
+ export declare function collectBrowserDiagnostics(): {
3
+ snapshot: () => string;
4
+ stop: () => void;
5
+ };
6
+ export declare function safeUrl(value: string): string;
7
+ export declare function sanitizeDiagnostic(value: string): string;
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import { formatErrorReport } from "../../settings/error-report.js";
3
+ function collectBrowserDiagnostics() {
4
+ const lines = [];
5
+ const add = (message) => {
6
+ lines.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${sanitizeDiagnostic(message).slice(0, 1800)}`);
7
+ if (lines.length > 30) lines.shift();
8
+ };
9
+ const stringify = (value) => {
10
+ if (value instanceof Error)
11
+ return formatErrorReport({ error: value, url: safeUrl(location.href) });
12
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
13
+ return String(value);
14
+ return "[object omitted]";
15
+ };
16
+ const originalError = console.error;
17
+ const originalWarn = console.warn;
18
+ const error = (...args) => {
19
+ add(`error: ${args.map(stringify).join(" ")}`);
20
+ originalError.apply(console, args);
21
+ };
22
+ const warn = (...args) => {
23
+ add(`warning: ${args.map(stringify).join(" ")}`);
24
+ originalWarn.apply(console, args);
25
+ };
26
+ console.error = error;
27
+ console.warn = warn;
28
+ const onError = (event) => add(`uncaught: ${stringify(event.error ?? event.message)}`);
29
+ const onRejection = (event) => add(`unhandled rejection: ${stringify(event.reason)}`);
30
+ window.addEventListener("error", onError);
31
+ window.addEventListener("unhandledrejection", onRejection);
32
+ const originalFetch = window.fetch;
33
+ const wrappedFetch = async (...args) => {
34
+ const input = args[0];
35
+ const url = safeUrl(
36
+ typeof input === "string" ? input : input instanceof URL ? input.href : input.url
37
+ );
38
+ try {
39
+ const response = await originalFetch.apply(window, args);
40
+ if (!response.ok) add(`HTTP ${response.status}: ${url}`);
41
+ return response;
42
+ } catch (failure) {
43
+ add(`request failed: ${url}`);
44
+ throw failure;
45
+ }
46
+ };
47
+ window.fetch = wrappedFetch;
48
+ let resources;
49
+ if (typeof PerformanceObserver !== "undefined") {
50
+ try {
51
+ resources = new PerformanceObserver((list) => {
52
+ for (const entry of list.getEntries()) {
53
+ const resource = entry;
54
+ if ("responseStatus" in resource && Number(resource.responseStatus) >= 400)
55
+ add(`HTTP ${resource.responseStatus}: ${safeUrl(resource.name)}`);
56
+ }
57
+ });
58
+ resources.observe({ type: "resource" });
59
+ } catch {
60
+ resources = void 0;
61
+ }
62
+ }
63
+ return {
64
+ snapshot: () => [
65
+ `ARBI tab: ${safeUrl(location.href)}`,
66
+ `Viewport: ${innerWidth} x ${innerHeight}; DPR ${devicePixelRatio}`,
67
+ "Visible ARBI controls (selectors for control_ui):",
68
+ ...Array.from(
69
+ document.querySelectorAll(
70
+ 'button[data-testid],a[data-testid],input[data-testid]:not([type="password"]),select[data-testid],[role="button"][data-testid]'
71
+ )
72
+ ).filter(
73
+ (el) => el.getBoundingClientRect().width > 0 && !el.closest("[data-remote-control-surface]")
74
+ ).slice(0, 60).map(
75
+ (el) => `[data-testid=${JSON.stringify(el.dataset.testid)}] ${sanitizeDiagnostic(el.getAttribute("aria-label") ?? el.getAttribute("title") ?? el.textContent ?? "").slice(0, 80)}`
76
+ ),
77
+ ...lines
78
+ ].join("\n").slice(-18e3),
79
+ stop: () => {
80
+ if (console.error === error) console.error = originalError;
81
+ if (console.warn === warn) console.warn = originalWarn;
82
+ if (window.fetch === wrappedFetch) window.fetch = originalFetch;
83
+ window.removeEventListener("error", onError);
84
+ window.removeEventListener("unhandledrejection", onRejection);
85
+ resources?.disconnect();
86
+ lines.length = 0;
87
+ }
88
+ };
89
+ }
90
+ function safeUrl(value) {
91
+ try {
92
+ const url = new URL(value, location.href);
93
+ return `${url.origin}${url.pathname}`;
94
+ } catch {
95
+ return "[invalid URL]";
96
+ }
97
+ }
98
+ function sanitizeDiagnostic(value) {
99
+ return value.replace(/https?:\/\/[^\s<>"']+/g, (url) => safeUrl(url)).replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(
100
+ /\b(token|password|secret|authorization|api[_-]?key)\s*[:=]\s*[^\s,;]+/gi,
101
+ "$1=[redacted]"
102
+ ).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted token]");
103
+ }
104
+ export {
105
+ collectBrowserDiagnostics,
106
+ safeUrl,
107
+ sanitizeDiagnostic
108
+ };
109
+ //# sourceMappingURL=browser-diagnostics.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/connection/remote-control/browser-diagnostics.ts"],
4
+ "sourcesContent": ["import { formatErrorReport } from '../../settings/error-report'\n\n/** Session-only browser evidence, collected only while the user is sharing. No bodies, headers or storage. */\nexport function collectBrowserDiagnostics() {\n const lines: string[] = []\n const add = (message: string) => {\n lines.push(`${new Date().toISOString()} ${sanitizeDiagnostic(message).slice(0, 1800)}`)\n if (lines.length > 30) lines.shift()\n }\n const stringify = (value: unknown): string => {\n if (value instanceof Error)\n return formatErrorReport({ error: value, url: safeUrl(location.href) })\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')\n return String(value)\n return '[object omitted]'\n }\n const originalError = console.error\n const originalWarn = console.warn\n const error = (...args: unknown[]) => {\n add(`error: ${args.map(stringify).join(' ')}`)\n originalError.apply(console, args)\n }\n const warn = (...args: unknown[]) => {\n add(`warning: ${args.map(stringify).join(' ')}`)\n originalWarn.apply(console, args)\n }\n console.error = error\n console.warn = warn\n const onError = (event: ErrorEvent) => add(`uncaught: ${stringify(event.error ?? event.message)}`)\n const onRejection = (event: PromiseRejectionEvent) =>\n add(`unhandled rejection: ${stringify(event.reason)}`)\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n const originalFetch = window.fetch\n const wrappedFetch: typeof fetch = async (...args) => {\n const input = args[0]\n const url = safeUrl(\n typeof input === 'string' ? input : input instanceof URL ? input.href : input.url\n )\n try {\n const response = await originalFetch.apply(window, args)\n if (!response.ok) add(`HTTP ${response.status}: ${url}`)\n return response\n } catch (failure) {\n add(`request failed: ${url}`)\n throw failure\n }\n }\n window.fetch = wrappedFetch\n // Resource timing also catches failures made through fetch references captured before sharing.\n let resources: PerformanceObserver | undefined\n if (typeof PerformanceObserver !== 'undefined') {\n try {\n resources = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const resource = entry as PerformanceResourceTiming\n if ('responseStatus' in resource && Number(resource.responseStatus) >= 400)\n add(`HTTP ${resource.responseStatus}: ${safeUrl(resource.name)}`)\n }\n })\n resources.observe({ type: 'resource' })\n } catch {\n resources = undefined\n }\n }\n return {\n snapshot: () =>\n [\n `ARBI tab: ${safeUrl(location.href)}`,\n `Viewport: ${innerWidth} x ${innerHeight}; DPR ${devicePixelRatio}`,\n 'Visible ARBI controls (selectors for control_ui):',\n ...Array.from(\n document.querySelectorAll<HTMLElement>(\n 'button[data-testid],a[data-testid],input[data-testid]:not([type=\"password\"]),select[data-testid],[role=\"button\"][data-testid]'\n )\n )\n .filter(\n (el) =>\n el.getBoundingClientRect().width > 0 && !el.closest('[data-remote-control-surface]')\n )\n .slice(0, 60)\n .map(\n (el) =>\n `[data-testid=${JSON.stringify(el.dataset.testid)}] ${sanitizeDiagnostic(el.getAttribute('aria-label') ?? el.getAttribute('title') ?? el.textContent ?? '').slice(0, 80)}`\n ),\n ...lines,\n ]\n .join('\\n')\n .slice(-18_000),\n stop: () => {\n if (console.error === error) console.error = originalError\n if (console.warn === warn) console.warn = originalWarn\n if (window.fetch === wrappedFetch) window.fetch = originalFetch\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n resources?.disconnect()\n lines.length = 0\n },\n }\n}\n\nexport function safeUrl(value: string): string {\n try {\n const url = new URL(value, location.href)\n return `${url.origin}${url.pathname}`\n } catch {\n return '[invalid URL]'\n }\n}\nexport function sanitizeDiagnostic(value: string): string {\n return value\n .replace(/https?:\\/\\/[^\\s<>\"']+/g, (url) => safeUrl(url))\n .replace(/\\bBearer\\s+[^\\s,;]+/gi, 'Bearer [redacted]')\n .replace(\n /\\b(token|password|secret|authorization|api[_-]?key)\\s*[:=]\\s*[^\\s,;]+/gi,\n '$1=[redacted]'\n )\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g, '[redacted token]')\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -21,8 +21,16 @@ export interface RemoteControl {
21
21
  /** Turn remote control ON directly — the user's own explicit choice. Runs nothing by itself. */
22
22
  enable(): void;
23
23
  }
24
+ export interface ScreenTransport {
25
+ reply(requestId: string, result: {
26
+ status: 'captured' | 'denied' | 'stopped' | 'unavailable' | 'action_finished';
27
+ screenshot_png?: string;
28
+ diagnostics?: string;
29
+ }): Promise<void>;
30
+ diagnose?(): Promise<string>;
31
+ }
24
32
  /** Remote control on a session's state, running the site's guided runs. */
25
- export declare function createRemoteControl(session: RemoteControlSession, guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>): RemoteControl;
33
+ export declare function createRemoteControl(session: RemoteControlSession, guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>, screenTransport?: ScreenTransport): RemoteControl;
26
34
  /** Remote control on the host's session and guided runs. */
27
35
  export declare function useRemoteControlActions(): RemoteControl;
28
36
  /**
@@ -7,25 +7,115 @@ import {
7
7
  toRemoteControlRequest,
8
8
  useArbiHost,
9
9
  useArbiSession,
10
- useArbiSessionStore
10
+ useArbiSessionStore,
11
+ useArbiSessionState
11
12
  } from "@arbidocs/react";
12
13
  import { cancelGuidedRun } from "../../guided/runner.js";
13
14
  import { useGuidedRuns } from "../../guided/useGuidedRuns.js";
14
15
  import { isSurfaceMounted } from "../../guided/surfaces.js";
15
- function createRemoteControl(session, guided) {
16
+ import { createScreenSharing } from "./screen-sharing.js";
17
+ const screenRuntimes = /* @__PURE__ */ new WeakMap();
18
+ function screenRuntime(session) {
19
+ let runtime = screenRuntimes.get(session);
20
+ if (!runtime) {
21
+ runtime = { sharing: createScreenSharing(), generation: 0, setup: "" };
22
+ screenRuntimes.set(session, runtime);
23
+ }
24
+ return runtime;
25
+ }
26
+ function stopScreen(session) {
27
+ const runtime = screenRuntimes.get(session);
28
+ if (!runtime) return;
29
+ runtime.generation++;
30
+ runtime.sharing.stop();
31
+ runtime.setup = "";
32
+ const actions = remoteControlActions(session);
33
+ if (remoteControlStateOf(session.getState()).sharing) actions.setSharing(false);
34
+ }
35
+ function createRemoteControl(session, guided, screenTransport) {
16
36
  const actions = remoteControlActions(session);
17
37
  const state = () => remoteControlStateOf(session.getState());
38
+ const runtime = screenRuntime(session);
39
+ const reply = async (req, status) => {
40
+ if ((req.screenRequestId || req.actionRequestId) && screenTransport) {
41
+ await screenTransport.reply(req.screenRequestId || req.actionRequestId, { status }).catch(() => {
42
+ });
43
+ }
44
+ };
45
+ const disable = () => {
46
+ const pending = state().pending;
47
+ if (pending) void reply(pending, "stopped");
48
+ stopScreen(session);
49
+ actions.setEnabled(false);
50
+ cancelGuidedRun();
51
+ };
52
+ const inspect = async (req, keepSharing) => {
53
+ if (!req.screenRequestId || !screenTransport) return;
54
+ const workspace = session.getState().workspaceId;
55
+ if (workspace !== req.workspaceExtId) {
56
+ await reply(req, "unavailable");
57
+ return;
58
+ }
59
+ const token = runtime.generation;
60
+ const current = () => token === runtime.generation && session.getState().workspaceId === workspace;
61
+ actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId });
62
+ try {
63
+ if (!runtime.sharing.active) {
64
+ await runtime.sharing.start(disable);
65
+ if (!current()) {
66
+ runtime.sharing.stop();
67
+ await reply(req, "stopped");
68
+ return;
69
+ }
70
+ actions.setSharing(true);
71
+ if (screenTransport.diagnose)
72
+ runtime.setup = await screenTransport.diagnose().catch(() => "Setup checks unavailable.");
73
+ }
74
+ if (!current()) {
75
+ await reply(req, "stopped");
76
+ return;
77
+ }
78
+ const frame = await runtime.sharing.frame();
79
+ if (!current()) {
80
+ await reply(req, "stopped");
81
+ return;
82
+ }
83
+ await screenTransport.reply(req.screenRequestId, {
84
+ status: "captured",
85
+ ...frame,
86
+ diagnostics: `${frame.diagnostics}
87
+ ${runtime.setup}`.slice(0, 24e3)
88
+ });
89
+ } catch {
90
+ await reply(req, current() ? "unavailable" : "stopped");
91
+ if (current()) disable();
92
+ } finally {
93
+ if (current()) {
94
+ actions.setControllingAgent(null);
95
+ if (!keepSharing) disable();
96
+ }
97
+ }
98
+ };
18
99
  const resolveRun = (req) => {
19
100
  if (req.steps && req.steps.length > 0) return req.steps;
20
101
  if (req.sequenceId && guided.isSequenceId(req.sequenceId)) return req.sequenceId;
21
102
  return null;
22
103
  };
23
- const runRemoteRequest = async (req) => {
104
+ const runRemoteRequest = async (req, keepSharing = state().enabled) => {
105
+ if (req.screenRequestId) {
106
+ await inspect(req, keepSharing);
107
+ return;
108
+ }
24
109
  const run = resolveRun(req);
25
110
  if (!run) return;
111
+ const token = runtime.generation;
26
112
  actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId });
27
113
  try {
28
114
  await guided.run(run, { remote: true });
115
+ if (req.actionRequestId)
116
+ await reply(req, token === runtime.generation ? "action_finished" : "stopped");
117
+ } catch {
118
+ await reply(req, "unavailable");
29
119
  } finally {
30
120
  const current = state().controllingAgent;
31
121
  if (current && current.name === req.agentName && current.extId === req.agentExtId) {
@@ -45,31 +135,49 @@ function createRemoteControl(session, guided) {
45
135
  }
46
136
  const req = toRemoteControlRequest(msg, guided.isSequenceId);
47
137
  if (!req) return;
48
- if (state().enabled) {
138
+ if ((req.screenRequestId || req.actionRequestId) && (!screenTransport || session.getState().workspaceId !== req.workspaceExtId)) {
139
+ void reply(req, "unavailable");
140
+ return;
141
+ }
142
+ if (state().enabled && (!req.screenRequestId || runtime.sharing.active)) {
49
143
  logger.info("[RemoteControl] Enabled \u2014 running requested action from", req.agentName);
50
144
  void runRemoteRequest(req);
51
145
  return;
52
146
  }
53
147
  logger.info("[RemoteControl] Consent required \u2014 prompting for", req.agentName);
148
+ if (state().pending) void reply(state().pending, "denied");
54
149
  actions.request(req);
55
150
  },
56
151
  grantAndRun(opts) {
57
152
  const pending = state().pending;
153
+ const keepSharing = opts.forSession || state().enabled;
58
154
  actions.grant(opts);
59
- if (pending) void runRemoteRequest(pending);
155
+ if (pending) void runRemoteRequest(pending, keepSharing);
60
156
  },
61
- deny: () => actions.deny(),
62
- disable() {
63
- actions.setEnabled(false);
64
- cancelGuidedRun();
157
+ deny: () => {
158
+ const pending = state().pending;
159
+ actions.deny();
160
+ if (pending) void reply(pending, "denied");
65
161
  },
162
+ disable,
66
163
  enable: () => actions.setEnabled(true)
67
164
  };
68
165
  }
69
166
  function useRemoteControlActions() {
70
167
  const session = useArbiSessionStore();
71
168
  const guided = useGuidedRuns();
72
- return useMemo(() => createRemoteControl(session, guided), [session, guided]);
169
+ const { arbi } = useArbiHost();
170
+ return useMemo(
171
+ () => createRemoteControl(
172
+ session,
173
+ guided,
174
+ arbi ? {
175
+ reply: (id, result) => arbi.remoteControl.replyScreen(id, result),
176
+ diagnose: async () => JSON.stringify(await arbi.diagnose())
177
+ } : void 0
178
+ ),
179
+ [session, guided, arbi]
180
+ );
73
181
  }
74
182
  function useRemoteControlSessionReset() {
75
183
  const session = useArbiSessionStore();
@@ -77,6 +185,7 @@ function useRemoteControlSessionReset() {
77
185
  const userId = useArbiSession().user?.external_id ?? null;
78
186
  useEffect(() => {
79
187
  const reset = () => {
188
+ stopScreen(session);
80
189
  remoteControlActions(session).reset();
81
190
  cancelGuidedRun();
82
191
  };
@@ -92,6 +201,7 @@ function useRemoteControlSessionReset() {
92
201
  useEffect(() => {
93
202
  if (lastUser.current === userId) return;
94
203
  lastUser.current = userId;
204
+ stopScreen(session);
95
205
  remoteControlActions(session).reset();
96
206
  cancelGuidedRun();
97
207
  }, [userId, session]);
@@ -100,6 +210,16 @@ function useRemoteControlHandlers() {
100
210
  useRemoteControlSessionReset();
101
211
  const remote = useRemoteControlActions();
102
212
  const { remoteControl } = useGuidedRuns();
213
+ const session = useArbiSessionStore();
214
+ const workspaceId = useArbiSessionState((s) => s.workspaceId);
215
+ useEffect(
216
+ () => () => {
217
+ stopScreen(session);
218
+ remoteControlActions(session).setEnabled(false);
219
+ cancelGuidedRun();
220
+ },
221
+ [session, workspaceId]
222
+ );
103
223
  return useMemo(
104
224
  () => remoteControl ? {
105
225
  remote_control: (message) => remote.handleMessage(message)