@arbidocs/blocks 0.5.126 → 0.5.128

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 (102) 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-C7XHGK36.cjs} +9 -9
  16. package/dist/{FilesViewerDialog-WQRNSKWM.cjs.map → FilesViewerDialog-C7XHGK36.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-KXIRVKCJ.cjs → chunk-ERRXU2HL.cjs} +38 -6
  26. package/dist/chunk-ERRXU2HL.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-OHHKBURG.cjs → chunk-IWMHQTVL.cjs} +401 -18
  32. package/dist/chunk-IWMHQTVL.cjs.map +1 -0
  33. package/dist/{chunk-FGOJBOZX.cjs → chunk-IZJ5L246.cjs} +4 -4
  34. package/dist/{chunk-FGOJBOZX.cjs.map → chunk-IZJ5L246.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-FB3JXSQT.cjs → chunk-ZQNTYINE.cjs} +10 -10
  50. package/dist/{chunk-FB3JXSQT.cjs.map → chunk-ZQNTYINE.cjs.map} +1 -1
  51. package/dist/{chunk-CMPHJRVV.cjs → chunk-ZYGAXD3N.cjs} +3 -3
  52. package/dist/{chunk-CMPHJRVV.cjs.map → chunk-ZYGAXD3N.cjs.map} +1 -1
  53. package/dist/connect/docx.cjs +13 -13
  54. package/dist/connect/index.cjs +245 -299
  55. package/dist/connect/index.cjs.map +1 -1
  56. package/dist/connect/viewer.cjs +7 -7
  57. package/dist/connection/conversation/MermaidDiagram.js +11 -2
  58. package/dist/connection/conversation/MermaidDiagram.js.map +2 -2
  59. package/dist/connection/conversation/download-mermaid.d.ts +2 -0
  60. package/dist/connection/conversation/download-mermaid.js +29 -0
  61. package/dist/connection/conversation/download-mermaid.js.map +7 -0
  62. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js +6 -3
  63. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js.map +2 -2
  64. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js +12 -2
  65. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js.map +2 -2
  66. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js +1 -1
  67. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js.map +1 -1
  68. package/dist/connection/remote-control/browser-diagnostics.d.ts +7 -0
  69. package/dist/connection/remote-control/browser-diagnostics.js +109 -0
  70. package/dist/connection/remote-control/browser-diagnostics.js.map +7 -0
  71. package/dist/connection/remote-control/remoteControl.d.ts +9 -1
  72. package/dist/connection/remote-control/remoteControl.js +146 -10
  73. package/dist/connection/remote-control/remoteControl.js.map +2 -2
  74. package/dist/connection/remote-control/screen-sharing.d.ts +11 -0
  75. package/dist/connection/remote-control/screen-sharing.js +78 -0
  76. package/dist/connection/remote-control/screen-sharing.js.map +7 -0
  77. package/dist/connection/report-problem/screen-capture.d.ts +1 -0
  78. package/dist/connection/report-problem/screen-capture.js +2 -1
  79. package/dist/connection/report-problem/screen-capture.js.map +2 -2
  80. package/dist/docx/NativeDocxEditor.js +14 -12
  81. package/dist/docx/NativeDocxEditor.js.map +2 -2
  82. package/dist/docx/index.cjs +38 -38
  83. package/dist/docx/useVersionAutosave.d.ts +10 -0
  84. package/dist/docx/useVersionAutosave.js +36 -0
  85. package/dist/docx/useVersionAutosave.js.map +7 -0
  86. package/dist/embed/index.cjs +10 -10
  87. package/dist/facts/index.cjs +43 -43
  88. package/dist/index.cjs +94 -94
  89. package/dist/settings/index.cjs +1 -1
  90. package/dist/shell/RemoteControlPill.d.ts +2 -1
  91. package/dist/shell/RemoteControlPill.js +10 -3
  92. package/dist/shell/RemoteControlPill.js.map +2 -2
  93. package/dist/shell/index.cjs +21 -21
  94. package/dist/studio/index.cjs +1 -1
  95. package/dist/workflow-canvas/index.cjs +12 -12
  96. package/node_modules/@arbidocs/coedit/package.json +1 -1
  97. package/package.json +5 -5
  98. package/dist/chunk-KA3F6TIH.cjs.map +0 -1
  99. package/dist/chunk-KXIRVKCJ.cjs.map +0 -1
  100. package/dist/chunk-OALQGO5C.cjs +0 -18
  101. package/dist/chunk-OHHKBURG.cjs.map +0 -1
  102. 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 chunkZQNTYINE_cjs = require('../chunk-ZQNTYINE.cjs');
5
5
  require('../chunk-JITYC4OS.cjs');
6
6
  require('../chunk-WVZLAAIZ.cjs');
7
7
  require('../chunk-X7ALF4MP.cjs');
@@ -10,14 +10,14 @@ var chunkPL7HUTM3_cjs = require('../chunk-PL7HUTM3.cjs');
10
10
  require('../chunk-QWVUBTW3.cjs');
11
11
  require('../chunk-JVEDKKNO.cjs');
12
12
  require('../chunk-ADDQKZ5I.cjs');
13
- require('../chunk-KXIRVKCJ.cjs');
13
+ require('../chunk-ERRXU2HL.cjs');
14
14
  require('../chunk-5QERK4PK.cjs');
15
15
  require('../chunk-T73BHNQX.cjs');
16
16
  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 chunkZQNTYINE_cjs.ConnectedDocumentViewer; }
98
98
  });
99
99
  Object.defineProperty(exports, "ConnectedDocumentToolsProvider", {
100
100
  enumerable: true,
@@ -1,7 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useId } from "react";
4
+ import { Download } from "lucide-react";
5
+ import { Button } from "@arbidocs/react/ui";
4
6
  import { normalizeMermaidGraph } from "../../chat/mermaid-normalize.js";
7
+ import { downloadMermaidSvg } from "./download-mermaid.js";
5
8
  let mermaidInstance = null;
6
9
  let mermaidLoadPromise = null;
7
10
  async function getMermaid() {
@@ -89,12 +92,18 @@ function MermaidDiagram({ chart }) {
89
92
  }
90
93
  );
91
94
  }
92
- return /* @__PURE__ */ jsx(
95
+ return /* @__PURE__ */ jsxs(
93
96
  "div",
94
97
  {
95
98
  className: "mermaid-diagram my-4 overflow-x-auto rounded-md border border-border bg-background p-4",
96
99
  "data-testid": "mermaid-diagram",
97
- dangerouslySetInnerHTML: { __html: svg }
100
+ children: [
101
+ /* @__PURE__ */ jsx("div", { className: "mb-2 flex justify-end", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => downloadMermaidSvg(svg), children: [
102
+ /* @__PURE__ */ jsx(Download, { className: "mr-2 size-4", "aria-hidden": "true" }),
103
+ "Download SVG"
104
+ ] }) }),
105
+ /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: svg } })
106
+ ]
98
107
  }
99
108
  );
100
109
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/conversation/MermaidDiagram.tsx"],
4
- "sourcesContent": ["/**\n * MermaidDiagram Component\n *\n * Renders a Mermaid diagram from source code.\n * Used inside MarkdownRenderer to handle ```mermaid code blocks.\n * Lazy-loads the mermaid library to avoid bloating the initial bundle.\n */\n\nimport { useState, useEffect, useId } from 'react'\nimport { normalizeMermaidGraph } from '../../chat/mermaid-normalize'\n\ninterface MermaidDiagramProps {\n /** Mermaid diagram source code */\n chart: string\n}\n\n// Lazy-load and initialize mermaid once\nlet mermaidInstance: typeof import('mermaid').default | null = null\nlet mermaidLoadPromise: Promise<typeof import('mermaid').default> | null = null\n\nasync function getMermaid() {\n if (mermaidInstance) return mermaidInstance\n if (!mermaidLoadPromise) {\n mermaidLoadPromise = import('mermaid').then((mod) => {\n const m = mod.default\n m.initialize({\n startOnLoad: false,\n theme: 'default',\n securityLevel: 'strict',\n flowchart: { useMaxWidth: true, curve: 'basis' },\n })\n mermaidInstance = m\n return m\n })\n }\n return mermaidLoadPromise\n}\n\nexport function MermaidDiagram({ chart }: MermaidDiagramProps) {\n const [svg, setSvg] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const uniqueId = useId().replace(/:/g, '-')\n\n useEffect(() => {\n let cancelled = false\n\n async function render() {\n const mermaid = await getMermaid()\n const diagramId = `mermaid${uniqueId}`\n\n // Validate with parse() before render(). In Mermaid v11 a render() with\n // bad source can return a \"Syntax error in text\" bomb SVG instead of\n // throwing \u2014 and it appends DOM side-effects of its own \u2014 so a try/catch\n // around render() doesn't reliably catch failures and the bomb leaks\n // into the page (especially under React StrictMode where effects run\n // twice). parse() throws cleanly with no DOM side-effects.\n //\n // Defensive rescue: some LLMs emit single-line `graph`/`flowchart`\n // source. If the original parse fails, try a normalized variant. The\n // normalizer is a no-op on already-multi-line source and on non-graph\n // diagram types, so this only helps the bad-source case and never\n // corrupts valid input.\n let validSource: string | null = null\n let firstErr: unknown\n try {\n await mermaid.parse(chart)\n validSource = chart\n } catch (err) {\n firstErr = err\n const normalized = normalizeMermaidGraph(chart)\n if (normalized !== chart) {\n try {\n await mermaid.parse(normalized)\n validSource = normalized\n } catch (retryErr) {\n console.warn('Mermaid normalized parse also failed', retryErr)\n }\n }\n }\n\n if (cancelled) return\n\n if (!validSource) {\n console.warn('Mermaid parse failed, showing source', firstErr)\n setError('Failed to render diagram')\n setSvg(null)\n return\n }\n\n try {\n const { svg: renderedSvg } = await mermaid.render(diagramId, validSource)\n if (!cancelled) {\n setSvg(renderedSvg)\n setError(null)\n }\n } catch (renderErr) {\n if (!cancelled) {\n console.warn('Mermaid render failed despite parse success', renderErr)\n setError('Failed to render diagram')\n setSvg(null)\n }\n }\n }\n\n render()\n return () => {\n cancelled = true\n }\n }, [chart, uniqueId])\n\n if (error) {\n // Fall back to showing the raw source in a code block\n return (\n <div className=\"code-block\" data-testid=\"mermaid-error\">\n <div className=\"text-sm text-muted-foreground mb-1\">{error}</div>\n <code className=\"language-mermaid\">{chart}</code>\n </div>\n )\n }\n\n if (!svg) {\n return (\n <div\n className=\"flex items-center justify-center py-8 text-muted-foreground text-sm\"\n data-testid=\"mermaid-loading\"\n >\n Rendering diagram...\n </div>\n )\n }\n\n return (\n <div\n className=\"mermaid-diagram my-4 overflow-x-auto rounded-md border border-border bg-background p-4\"\n data-testid=\"mermaid-diagram\"\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n )\n}\n"],
5
- "mappings": ";AAiHM,SACE,KADF;AAzGN,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,6BAA6B;AAQtC,IAAI,kBAA2D;AAC/D,IAAI,qBAAuE;AAE3E,eAAe,aAAa;AAC1B,MAAI,gBAAiB,QAAO;AAC5B,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,OAAO,SAAS,EAAE,KAAK,CAAC,QAAQ;AACnD,YAAM,IAAI,IAAI;AACd,QAAE,WAAW;AAAA,QACX,aAAa;AAAA,QACb,OAAO;AAAA,QACP,eAAe;AAAA,QACf,WAAW,EAAE,aAAa,MAAM,OAAO,QAAQ;AAAA,MACjD,CAAC;AACD,wBAAkB;AAClB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,SAAS,eAAe,EAAE,MAAM,GAAwB;AAC7D,QAAM,CAAC,KAAK,MAAM,IAAI,SAAwB,IAAI;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,WAAW,MAAM,EAAE,QAAQ,MAAM,GAAG;AAE1C,YAAU,MAAM;AACd,QAAI,YAAY;AAEhB,mBAAe,SAAS;AACtB,YAAM,UAAU,MAAM,WAAW;AACjC,YAAM,YAAY,UAAU,QAAQ;AAcpC,UAAI,cAA6B;AACjC,UAAI;AACJ,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK;AACzB,sBAAc;AAAA,MAChB,SAAS,KAAK;AACZ,mBAAW;AACX,cAAM,aAAa,sBAAsB,KAAK;AAC9C,YAAI,eAAe,OAAO;AACxB,cAAI;AACF,kBAAM,QAAQ,MAAM,UAAU;AAC9B,0BAAc;AAAA,UAChB,SAAS,UAAU;AACjB,oBAAQ,KAAK,wCAAwC,QAAQ;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AAEA,UAAI,UAAW;AAEf,UAAI,CAAC,aAAa;AAChB,gBAAQ,KAAK,wCAAwC,QAAQ;AAC7D,iBAAS,0BAA0B;AACnC,eAAO,IAAI;AACX;AAAA,MACF;AAEA,UAAI;AACF,cAAM,EAAE,KAAK,YAAY,IAAI,MAAM,QAAQ,OAAO,WAAW,WAAW;AACxE,YAAI,CAAC,WAAW;AACd,iBAAO,WAAW;AAClB,mBAAS,IAAI;AAAA,QACf;AAAA,MACF,SAAS,WAAW;AAClB,YAAI,CAAC,WAAW;AACd,kBAAQ,KAAK,+CAA+C,SAAS;AACrE,mBAAS,0BAA0B;AACnC,iBAAO,IAAI;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AACP,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,MAAI,OAAO;AAET,WACE,qBAAC,SAAI,WAAU,cAAa,eAAY,iBACtC;AAAA,0BAAC,SAAI,WAAU,sCAAsC,iBAAM;AAAA,MAC3D,oBAAC,UAAK,WAAU,oBAAoB,iBAAM;AAAA,OAC5C;AAAA,EAEJ;AAEA,MAAI,CAAC,KAAK;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,eAAY;AAAA,QACb;AAAA;AAAA,IAED;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,eAAY;AAAA,MACZ,yBAAyB,EAAE,QAAQ,IAAI;AAAA;AAAA,EACzC;AAEJ;",
4
+ "sourcesContent": ["/**\n * MermaidDiagram Component\n *\n * Renders a Mermaid diagram from source code.\n * Used inside MarkdownRenderer to handle ```mermaid code blocks.\n * Lazy-loads the mermaid library to avoid bloating the initial bundle.\n */\n\nimport { useState, useEffect, useId } from 'react'\nimport { Download } from 'lucide-react'\nimport { Button } from '@arbidocs/react/ui'\nimport { normalizeMermaidGraph } from '../../chat/mermaid-normalize'\nimport { downloadMermaidSvg } from './download-mermaid'\n\ninterface MermaidDiagramProps {\n /** Mermaid diagram source code */\n chart: string\n}\n\n// Lazy-load and initialize mermaid once\nlet mermaidInstance: typeof import('mermaid').default | null = null\nlet mermaidLoadPromise: Promise<typeof import('mermaid').default> | null = null\n\nasync function getMermaid() {\n if (mermaidInstance) return mermaidInstance\n if (!mermaidLoadPromise) {\n mermaidLoadPromise = import('mermaid').then((mod) => {\n const m = mod.default\n m.initialize({\n startOnLoad: false,\n theme: 'default',\n securityLevel: 'strict',\n flowchart: { useMaxWidth: true, curve: 'basis' },\n })\n mermaidInstance = m\n return m\n })\n }\n return mermaidLoadPromise\n}\n\nexport function MermaidDiagram({ chart }: MermaidDiagramProps) {\n const [svg, setSvg] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const uniqueId = useId().replace(/:/g, '-')\n\n useEffect(() => {\n let cancelled = false\n\n async function render() {\n const mermaid = await getMermaid()\n const diagramId = `mermaid${uniqueId}`\n\n // Validate with parse() before render(). In Mermaid v11 a render() with\n // bad source can return a \"Syntax error in text\" bomb SVG instead of\n // throwing \u2014 and it appends DOM side-effects of its own \u2014 so a try/catch\n // around render() doesn't reliably catch failures and the bomb leaks\n // into the page (especially under React StrictMode where effects run\n // twice). parse() throws cleanly with no DOM side-effects.\n //\n // Defensive rescue: some LLMs emit single-line `graph`/`flowchart`\n // source. If the original parse fails, try a normalized variant. The\n // normalizer is a no-op on already-multi-line source and on non-graph\n // diagram types, so this only helps the bad-source case and never\n // corrupts valid input.\n let validSource: string | null = null\n let firstErr: unknown\n try {\n await mermaid.parse(chart)\n validSource = chart\n } catch (err) {\n firstErr = err\n const normalized = normalizeMermaidGraph(chart)\n if (normalized !== chart) {\n try {\n await mermaid.parse(normalized)\n validSource = normalized\n } catch (retryErr) {\n console.warn('Mermaid normalized parse also failed', retryErr)\n }\n }\n }\n\n if (cancelled) return\n\n if (!validSource) {\n console.warn('Mermaid parse failed, showing source', firstErr)\n setError('Failed to render diagram')\n setSvg(null)\n return\n }\n\n try {\n const { svg: renderedSvg } = await mermaid.render(diagramId, validSource)\n if (!cancelled) {\n setSvg(renderedSvg)\n setError(null)\n }\n } catch (renderErr) {\n if (!cancelled) {\n console.warn('Mermaid render failed despite parse success', renderErr)\n setError('Failed to render diagram')\n setSvg(null)\n }\n }\n }\n\n render()\n return () => {\n cancelled = true\n }\n }, [chart, uniqueId])\n\n if (error) {\n // Fall back to showing the raw source in a code block\n return (\n <div className=\"code-block\" data-testid=\"mermaid-error\">\n <div className=\"text-sm text-muted-foreground mb-1\">{error}</div>\n <code className=\"language-mermaid\">{chart}</code>\n </div>\n )\n }\n\n if (!svg) {\n return (\n <div\n className=\"flex items-center justify-center py-8 text-muted-foreground text-sm\"\n data-testid=\"mermaid-loading\"\n >\n Rendering diagram...\n </div>\n )\n }\n\n return (\n <div\n className=\"mermaid-diagram my-4 overflow-x-auto rounded-md border border-border bg-background p-4\"\n data-testid=\"mermaid-diagram\"\n >\n <div className=\"mb-2 flex justify-end\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => downloadMermaidSvg(svg)}>\n <Download className=\"mr-2 size-4\" aria-hidden=\"true\" />\n Download SVG\n </Button>\n </div>\n <div dangerouslySetInnerHTML={{ __html: svg }} />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,2 @@
1
+ /** Save the rendered graphic, including Mermaid's styles, as a standalone image. */
2
+ export declare function downloadMermaidSvg(rendered: string): void;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ function downloadMermaidSvg(rendered) {
3
+ const doc = new DOMParser().parseFromString(rendered, "image/svg+xml");
4
+ const svg = doc.documentElement;
5
+ if (svg.localName !== "svg" || doc.querySelector("parsererror"))
6
+ throw new Error("The diagram did not produce a valid SVG image");
7
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
8
+ const box = svg.getAttribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
9
+ if (box?.length === 4 && box.every(Number.isFinite) && box[2] > 0 && box[3] > 0) {
10
+ svg.setAttribute("width", String(Math.ceil(box[2])));
11
+ svg.setAttribute("height", String(Math.ceil(box[3])));
12
+ }
13
+ const blob = new Blob([new XMLSerializer().serializeToString(svg)], { type: "image/svg+xml" });
14
+ const url = URL.createObjectURL(blob);
15
+ const link = document.createElement("a");
16
+ link.href = url;
17
+ link.download = "diagram.svg";
18
+ document.body.append(link);
19
+ try {
20
+ link.click();
21
+ } finally {
22
+ link.remove();
23
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
24
+ }
25
+ }
26
+ export {
27
+ downloadMermaidSvg
28
+ };
29
+ //# sourceMappingURL=download-mermaid.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/connection/conversation/download-mermaid.ts"],
4
+ "sourcesContent": ["/** Save the rendered graphic, including Mermaid's styles, as a standalone image. */\nexport function downloadMermaidSvg(rendered: string): void {\n const doc = new DOMParser().parseFromString(rendered, 'image/svg+xml')\n const svg = doc.documentElement\n if (svg.localName !== 'svg' || doc.querySelector('parsererror'))\n throw new Error('The diagram did not produce a valid SVG image')\n svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')\n const box = svg\n .getAttribute('viewBox')\n ?.trim()\n .split(/[\\s,]+/)\n .map(Number)\n if (box?.length === 4 && box.every(Number.isFinite) && box[2] > 0 && box[3] > 0) {\n svg.setAttribute('width', String(Math.ceil(box[2])))\n svg.setAttribute('height', String(Math.ceil(box[3])))\n }\n const blob = new Blob([new XMLSerializer().serializeToString(svg)], { type: 'image/svg+xml' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = 'diagram.svg'\n document.body.append(link)\n try {\n link.click()\n } finally {\n link.remove()\n // Let the browser consume the download before releasing the image URL.\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n }\n}\n"],
5
+ "mappings": ";AACO,SAAS,mBAAmB,UAAwB;AACzD,QAAM,MAAM,IAAI,UAAU,EAAE,gBAAgB,UAAU,eAAe;AACrE,QAAM,MAAM,IAAI;AAChB,MAAI,IAAI,cAAc,SAAS,IAAI,cAAc,aAAa;AAC5D,UAAM,IAAI,MAAM,+CAA+C;AACjE,MAAI,aAAa,SAAS,4BAA4B;AACtD,QAAM,MAAM,IACT,aAAa,SAAS,GACrB,KAAK,EACN,MAAM,QAAQ,EACd,IAAI,MAAM;AACb,MAAI,KAAK,WAAW,KAAK,IAAI,MAAM,OAAO,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG;AAC/E,QAAI,aAAa,SAAS,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;AACnD,QAAI,aAAa,UAAU,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,EACtD;AACA,QAAM,OAAO,IAAI,KAAK,CAAC,IAAI,cAAc,EAAE,kBAAkB,GAAG,CAAC,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC7F,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,OAAO,SAAS,cAAc,GAAG;AACvC,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,WAAS,KAAK,OAAO,IAAI;AACzB,MAAI;AACF,SAAK,MAAM;AAAA,EACb,UAAE;AACA,SAAK,OAAO;AAEZ,eAAW,MAAM,IAAI,gBAAgB,GAAG,GAAG,GAAI;AAAA,EACjD;AACF;",
6
+ "names": []
7
+ }
@@ -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": ";AAAA,SAAS,yBAAyB;AAG3B,SAAS,4BAA4B;AAC1C,QAAM,QAAkB,CAAC;AACzB,QAAM,MAAM,CAAC,YAAoB;AAC/B,UAAM,KAAK,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,mBAAmB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,EAAE;AACtF,QAAI,MAAM,SAAS,GAAI,OAAM,MAAM;AAAA,EACrC;AACA,QAAM,YAAY,CAAC,UAA2B;AAC5C,QAAI,iBAAiB;AACnB,aAAO,kBAAkB,EAAE,OAAO,OAAO,KAAK,QAAQ,SAAS,IAAI,EAAE,CAAC;AACxE,QAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU;AAC7E,aAAO,OAAO,KAAK;AACrB,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,eAAe,QAAQ;AAC7B,QAAM,QAAQ,IAAI,SAAoB;AACpC,QAAI,UAAU,KAAK,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE;AAC7C,kBAAc,MAAM,SAAS,IAAI;AAAA,EACnC;AACA,QAAM,OAAO,IAAI,SAAoB;AACnC,QAAI,YAAY,KAAK,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE;AAC/C,iBAAa,MAAM,SAAS,IAAI;AAAA,EAClC;AACA,UAAQ,QAAQ;AAChB,UAAQ,OAAO;AACf,QAAM,UAAU,CAAC,UAAsB,IAAI,aAAa,UAAU,MAAM,SAAS,MAAM,OAAO,CAAC,EAAE;AACjG,QAAM,cAAc,CAAC,UACnB,IAAI,wBAAwB,UAAU,MAAM,MAAM,CAAC,EAAE;AACvD,SAAO,iBAAiB,SAAS,OAAO;AACxC,SAAO,iBAAiB,sBAAsB,WAAW;AACzD,QAAM,gBAAgB,OAAO;AAC7B,QAAM,eAA6B,UAAU,SAAS;AACpD,UAAM,QAAQ,KAAK,CAAC;AACpB,UAAM,MAAM;AAAA,MACV,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAO,MAAM;AAAA,IAChF;AACA,QAAI;AACF,YAAM,WAAW,MAAM,cAAc,MAAM,QAAQ,IAAI;AACvD,UAAI,CAAC,SAAS,GAAI,KAAI,QAAQ,SAAS,MAAM,KAAK,GAAG,EAAE;AACvD,aAAO;AAAA,IACT,SAAS,SAAS;AAChB,UAAI,mBAAmB,GAAG,EAAE;AAC5B,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO,QAAQ;AAEf,MAAI;AACJ,MAAI,OAAO,wBAAwB,aAAa;AAC9C,QAAI;AACF,kBAAY,IAAI,oBAAoB,CAAC,SAAS;AAC5C,mBAAW,SAAS,KAAK,WAAW,GAAG;AACrC,gBAAM,WAAW;AACjB,cAAI,oBAAoB,YAAY,OAAO,SAAS,cAAc,KAAK;AACrE,gBAAI,QAAQ,SAAS,cAAc,KAAK,QAAQ,SAAS,IAAI,CAAC,EAAE;AAAA,QACpE;AAAA,MACF,CAAC;AACD,gBAAU,QAAQ,EAAE,MAAM,WAAW,CAAC;AAAA,IACxC,QAAQ;AACN,kBAAY;AAAA,IACd;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU,MACR;AAAA,MACE,aAAa,QAAQ,SAAS,IAAI,CAAC;AAAA,MACnC,aAAa,UAAU,MAAM,WAAW,SAAS,gBAAgB;AAAA,MACjE;AAAA,MACA,GAAG,MAAM;AAAA,QACP,SAAS;AAAA,UACP;AAAA,QACF;AAAA,MACF,EACG;AAAA,QACC,CAAC,OACC,GAAG,sBAAsB,EAAE,QAAQ,KAAK,CAAC,GAAG,QAAQ,+BAA+B;AAAA,MACvF,EACC,MAAM,GAAG,EAAE,EACX;AAAA,QACC,CAAC,OACC,gBAAgB,KAAK,UAAU,GAAG,QAAQ,MAAM,CAAC,KAAK,mBAAmB,GAAG,aAAa,YAAY,KAAK,GAAG,aAAa,OAAO,KAAK,GAAG,eAAe,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,MAC5K;AAAA,MACF,GAAG;AAAA,IACL,EACG,KAAK,IAAI,EACT,MAAM,KAAO;AAAA,IAClB,MAAM,MAAM;AACV,UAAI,QAAQ,UAAU,MAAO,SAAQ,QAAQ;AAC7C,UAAI,QAAQ,SAAS,KAAM,SAAQ,OAAO;AAC1C,UAAI,OAAO,UAAU,aAAc,QAAO,QAAQ;AAClD,aAAO,oBAAoB,SAAS,OAAO;AAC3C,aAAO,oBAAoB,sBAAsB,WAAW;AAC5D,iBAAW,WAAW;AACtB,YAAM,SAAS;AAAA,IACjB;AAAA,EACF;AACF;AAEO,SAAS,QAAQ,OAAuB;AAC7C,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,WAAO,GAAG,IAAI,MAAM,GAAG,IAAI,QAAQ;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AACO,SAAS,mBAAmB,OAAuB;AACxD,SAAO,MACJ,QAAQ,0BAA0B,CAAC,QAAQ,QAAQ,GAAG,CAAC,EACvD,QAAQ,yBAAyB,mBAAmB,EACpD;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,QAAQ,0DAA0D,kBAAkB;AACzF;",
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
  /**