@marimo-team/islands 0.25.2-dev99 → 0.25.3-dev10

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 (205) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BZmqc_F0.js → ConnectedDataExplorerComponent-CNMliVzw.js} +15 -15
  2. package/dist/{ErrorBoundary-CXkr_IcB.js → ErrorBoundary-BsqSMK9Y.js} +12 -12
  3. package/dist/{ImageComparisonComponent-Cpfz30OZ.js → ImageComparisonComponent-uU8FSKZE.js} +3 -3
  4. package/dist/{Plot-CTyjbQDT.js → Plot-DdYOe1UK.js} +2 -2
  5. package/dist/{abnfDiagram-VCTEODGH-C_Lmaa2j.js → abnfDiagram-VCTEODGH-D-Ez91gV.js} +3 -3
  6. package/dist/{any-language-editor-BFzqhOBR.js → any-language-editor-DZ_YTVus.js} +7 -7
  7. package/dist/{architectureDiagram-5GKGNRK7-BKMataHB.js → architectureDiagram-5GKGNRK7-CTpcNtO8.js} +5 -5
  8. package/dist/assets/__vite-browser-external-BXYkiFsk.js +1 -0
  9. package/dist/assets/worker-HPY9fbhz.js +114 -0
  10. package/dist/{blockDiagram-I7D4REHJ-BeHOjyDk.js → blockDiagram-I7D4REHJ-Cg25UrxT.js} +9 -9
  11. package/dist/{button-BtsM2UqM.js → button-CvjkwdSk.js} +5 -5
  12. package/dist/{c4Diagram-7LVT6UL2-Bao10t_O.js → c4Diagram-7LVT6UL2-BxFgrnNV.js} +7 -7
  13. package/dist/{capabilities-A5jrzjCJ.js → capabilities-CQ5IjhzL.js} +1 -1
  14. package/dist/{channel-BxCkvUgE.js → channel-CPObtJXj.js} +1 -1
  15. package/dist/{chat-ui-CwOWzoSO.js → chat-ui-0lKdOoQ3.js} +1588 -1396
  16. package/dist/{check-CNP79M3A.js → check-EDwSwP89.js} +1 -1
  17. package/dist/{chunk-4HAMMTFA-CH7Gpamd.js → chunk-4HAMMTFA-D4eFPyFh.js} +5 -5
  18. package/dist/{chunk-75Z2AOVW-AI7CvgKn.js → chunk-75Z2AOVW-D7gxIs2Q.js} +1 -1
  19. package/dist/{chunk-DU6HZSFF-BSvuTkX4.js → chunk-DU6HZSFF-BwiyaNpg.js} +1 -1
  20. package/dist/{chunk-F27PBJKO-CLshwT9T.js → chunk-F27PBJKO-ufseIn5y.js} +1 -1
  21. package/dist/{chunk-GMAD6QVW-wnHcn-lH.js → chunk-GMAD6QVW-BhaJx0X3.js} +3 -3
  22. package/dist/{chunk-GVQU2GXP-7O2p8hut.js → chunk-GVQU2GXP-Cwbt-lF0.js} +2 -2
  23. package/dist/{chunk-IMKFNOWR-Bu0Vh_Yi.js → chunk-IMKFNOWR-DGxi-zeo.js} +5 -5
  24. package/dist/{chunk-L3NEJ4N5-B9_Alrxb.js → chunk-L3NEJ4N5-n-zgIVSy.js} +5 -5
  25. package/dist/{chunk-OSK3NFVY-PeQPH2fD.js → chunk-OSK3NFVY-DXa-Cxbn.js} +6 -6
  26. package/dist/{chunk-P2QGCYS3-D9twiFuU.js → chunk-P2QGCYS3-B_Zd3-X0.js} +1 -1
  27. package/dist/{chunk-POPQ4Y6H-BUiNs18s.js → chunk-POPQ4Y6H-hYOXbYH_.js} +1 -1
  28. package/dist/{chunk-PWAF6VOD-D8S9NPCy.js → chunk-PWAF6VOD-CQw5qATZ.js} +1 -1
  29. package/dist/{chunk-SHT3W25Y-B2dyNZ31.js → chunk-SHT3W25Y-CV9J3rAB.js} +7 -7
  30. package/dist/{chunk-SVP7TREG-D7UPvB10.js → chunk-SVP7TREG-BL-9Fqgg.js} +2 -2
  31. package/dist/{chunk-TICWLB2K-BNaGoadd.js → chunk-TICWLB2K-Cu_XMjDM.js} +5 -5
  32. package/dist/{chunk-YOKDWASO-DrWbKnxA.js → chunk-YOKDWASO-DPZk8vhS.js} +6 -6
  33. package/dist/classDiagram-ZZMXUADV-BXXV13Hq.js +30 -0
  34. package/dist/classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js +30 -0
  35. package/dist/{code-visibility-pPjYsRTH.js → code-visibility-CXQvUXHT.js} +18 -18
  36. package/dist/{common-CkvBp0B4.js → common-CuD-CAC-.js} +6128 -7136
  37. package/dist/{compiler-runtime-Cj8ZWiNS.js → compiler-runtime-CldRdSsv.js} +2 -2
  38. package/dist/{copy-icon-BY7jgcaV.js → copy-icon-Ft7t9bqz.js} +6 -6
  39. package/dist/{cynefinDiagram-5FMLGOSQ-CCDn4Z9e.js → cynefinDiagram-5FMLGOSQ-CH96rwba.js} +3 -3
  40. package/dist/{dagre-GXQ25YYZ-BJiUO_SV.js → dagre-GXQ25YYZ-BD3PmpFY.js} +10 -10
  41. package/dist/{data-grid-overlay-editor-L6ELxFAh.js → data-grid-overlay-editor-DgeyYmC1.js} +7 -7
  42. package/dist/{data-grid-types-BucVmDDp.js → data-grid-types-BH_r7g7l.js} +1 -1
  43. package/dist/{diagram-S7CK7UJ4-BZfxFNRR.js → diagram-S7CK7UJ4-BsSJJldB.js} +4 -4
  44. package/dist/{diagram-UQ7AKVKN-1xcsiIMg.js → diagram-UQ7AKVKN-muQtNZc4.js} +3 -3
  45. package/dist/{diagram-VSXAHHWV-DPAWJ9Nw.js → diagram-VSXAHHWV-BJWcVVJY.js} +2 -2
  46. package/dist/{diagram-VX7I27RA-DmRsnHYY.js → diagram-VX7I27RA-CdhuZAqR.js} +5 -5
  47. package/dist/{diagram-Z3DM3KII--b49ryKx.js → diagram-Z3DM3KII-BD8mXjOj.js} +3 -3
  48. package/dist/{dialog-CmExhUDI.js → dialog-CEBFZNyc.js} +6 -6
  49. package/dist/{dist-CcyKTBTF.js → dist-DpBpA_n1.js} +2 -2
  50. package/dist/{dropdown-menu-Cz4TdOJh.js → dropdown-menu-CP_qCYHC.js} +139 -139
  51. package/dist/{ebnfDiagram-PWID7BFC-rSwVyh4y.js → ebnfDiagram-PWID7BFC-BIN1sweE.js} +3 -3
  52. package/dist/{erDiagram-RLTQ6QDP-Bfd3iAoC.js → erDiagram-RLTQ6QDP-Ctp5cfoN.js} +12 -12
  53. package/dist/{error-banner-D30nA9nE.js → error-banner-D-WCIiMt.js} +16 -11
  54. package/dist/{cells-DZi0ioak.js → error-entries-DSvFWT_M.js} +6171 -5075
  55. package/dist/{esm-DAVrGVKP.js → esm-AMFssoNp.js} +4 -4
  56. package/dist/{feedback-button-D-pV0Qji.js → feedback-button-DZMj9PF3.js} +119 -120
  57. package/dist/flowDiagram-HODETNUW-3EfLrlFb.js +16 -0
  58. package/dist/{formats-DhhbdyAH.js → formats-CeOmU1MU.js} +6 -6
  59. package/dist/{ganttDiagram-EL5Y4UJY-B4m_YsKa.js → ganttDiagram-EL5Y4UJY-BQT16JSc.js} +2 -2
  60. package/dist/{gitGraphDiagram-WWUBYQGX-QRBbvWXh.js → gitGraphDiagram-WWUBYQGX-D5nSIzdE.js} +2 -2
  61. package/dist/{glide-data-editor-D6CfYwdr.js → glide-data-editor-g_L8FLAC.js} +60 -60
  62. package/dist/{glide-utils-BVJ4fkxX.js → glide-utils-DJyqh0EM.js} +4 -4
  63. package/dist/{globals-BwK9YGrX.js → globals-CYGlmBXb.js} +3 -3
  64. package/dist/{highlighted-body-KPVGNVTW-Ad3MKZ18.js → highlighted-body-KPVGNVTW-zwjq21tf.js} +4 -4
  65. package/dist/{html-to-image-Cn1_16-x.js → html-to-image-B8s5SgGm.js} +37 -37
  66. package/dist/{infoDiagram-27XIBGKW-DVQFYvhg.js → infoDiagram-27XIBGKW-DipQoHxl.js} +2 -2
  67. package/dist/{ishikawaDiagram-5VMMS53U-viPND-nm.js → ishikawaDiagram-5VMMS53U-DgFMjSGz.js} +3 -3
  68. package/dist/{journeyDiagram-3NMN7TZE-BeeJdJo3.js → journeyDiagram-3NMN7TZE-BaLqn-dZ.js} +2 -2
  69. package/dist/{jsx-runtime-DgDPJV9S.js → jsx-runtime-3BD1kT0s.js} +1 -1
  70. package/dist/{kanban-definition-UXKFOSKX-Dcl_ECO9.js → kanban-definition-UXKFOSKX-w1ACVjgl.js} +9 -9
  71. package/dist/{katex-BNCmKUol.js → katex-BIdZGsyO.js} +141 -116
  72. package/dist/{label-DQO1jFjf.js → label-BDKF3Afc.js} +10 -10
  73. package/dist/{loader-Cm-ATtou.js → loader-BQOfjTIJ.js} +1 -1
  74. package/dist/main.js +623 -590
  75. package/dist/{mermaid-BfhcnSj-.js → mermaid-CHo49Yv-.js} +56 -56
  76. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +2 -0
  77. package/dist/{mhchem-BEc7A1or.js → mhchem-CdWPJK6g.js} +1 -1
  78. package/dist/{mindmap-definition-YA3MSWOX-BdxxhF-5.js → mindmap-definition-YA3MSWOX-CrT0KSfX.js} +11 -11
  79. package/dist/{number-overlay-editor-tMylyldi.js → number-overlay-editor-9VRv-pdj.js} +5 -5
  80. package/dist/{pegDiagram-XKGWAZYB-Du5p2s0w.js → pegDiagram-XKGWAZYB-BqvywNJA.js} +3 -3
  81. package/dist/{pieDiagram-E7YTZNPT-DJg-SOIq.js → pieDiagram-E7YTZNPT-BfxJ_XCc.js} +3 -3
  82. package/dist/{process-output-CUG57KRp.js → process-output-CKRXyszb.js} +5 -5
  83. package/dist/{quadrantDiagram-AXDQQJYC-keLfFx9f.js → quadrantDiagram-AXDQQJYC-Ccw0xMjN.js} +1 -1
  84. package/dist/{railroadDiagram-O6MQD6OU-C0ojm-6_.js → railroadDiagram-O6MQD6OU-8vFLVTPu.js} +3 -3
  85. package/dist/{react-dom-BUgJ-ab_.js → react-dom-DUmH0GXv.js} +29 -21
  86. package/dist/{react-ofJbxM4x.js → react-sHtZdjeP.js} +103 -94
  87. package/dist/{react-vega-ewj7q_U5.js → react-vega-BED0DjiB.js} +3 -3
  88. package/dist/{requirementDiagram-BXWQKSXE-CNwNBmI3.js → requirementDiagram-BXWQKSXE-a_MxGvxe.js} +11 -11
  89. package/dist/{reveal-component-LXUMcqWX.js → reveal-component-C87mv9Mn.js} +906 -878
  90. package/dist/{sankeyDiagram-P5KCCOFB-Bh4EsJox.js → sankeyDiagram-P5KCCOFB-ELvOcnZH.js} +1 -1
  91. package/dist/{select-Bs-nmvm6.js → select-BButFi-d.js} +11 -11
  92. package/dist/{sequenceDiagram-WJ2MYXX4-DQaW5qBG.js → sequenceDiagram-WJ2MYXX4-CXFMnxGh.js} +4 -4
  93. package/dist/{spec-DVsoMvYT.js → spec-BwarBpUJ.js} +4 -4
  94. package/dist/{stateDiagram-D77RDMKH-BDa515QC.js → stateDiagram-D77RDMKH-DQeA_CwI.js} +13 -13
  95. package/dist/stateDiagram-v2-MP3YSRHH-BxxQucm1.js +30 -0
  96. package/dist/style.css +1 -1
  97. package/dist/{swimlanes-42K2YHIH-M4IdGi8J.js → swimlanes-42K2YHIH-Do6L-cYg.js} +10 -10
  98. package/dist/swimlanesDiagram-VR7AAH4N-DUzPvSVU.js +30 -0
  99. package/dist/{swiper-component-CpN-hewZ.js → swiper-component-DKntyn8p.js} +5 -5
  100. package/dist/{timeline-definition-24CTP7MA-DH-kvakg.js → timeline-definition-24CTP7MA-D3c46WM5.js} +3 -3
  101. package/dist/{toDate-vEi5kTfY.js → toDate-vIXljnsr.js} +43 -42
  102. package/dist/{toast-DfEpJedt.js → toast-B9UMEi8u.js} +9 -9
  103. package/dist/{tooltip-CuNWg2ay.js → tooltip-BO1REbqs.js} +7 -7
  104. package/dist/{use-restore-focus-kmgwrL8q.js → use-restore-focus-BEM7sFWl.js} +2042 -1827
  105. package/dist/{use-vega-container-remeasure-4DwJbPWu.js → use-vega-container-remeasure-Bz4PZMtt.js} +2 -2
  106. package/dist/{useAsyncData-rkAlR_vh.js → useAsyncData-Deh5kszT.js} +3 -3
  107. package/dist/{useDateFormatter-C-tz65rD.js → useDateFormatter-D8XruNGG.js} +4 -4
  108. package/dist/{useDeepCompareMemoize-BlHFBj5_.js → useDeepCompareMemoize-DXwzRINI.js} +2 -2
  109. package/dist/{useIframeCapabilities-C1Bt04Iz.js → useIframeCapabilities-DBETvvPl.js} +2 -2
  110. package/dist/{useLifecycle-B68iY077.js → useLifecycle-j7D9uD1o.js} +6 -6
  111. package/dist/{useTheme-BwSgeAFb.js → useTheme-CRnjkku0.js} +7 -7
  112. package/dist/{vega-component-C-bPttRN.js → vega-component-DTG6zxaY.js} +14 -14
  113. package/dist/{vega-embed-container-sSR5hAsy.js → vega-embed-container-BNHE18G9.js} +5 -5
  114. package/dist/{vennDiagram-4TSXK5OY-d5g7XeiI.js → vennDiagram-4TSXK5OY-BSZD8JcZ.js} +3 -3
  115. package/dist/{wardleyDiagram-VM6X3IG4-BfX3bSzR.js → wardleyDiagram-VM6X3IG4-48cUWkfA.js} +3 -3
  116. package/dist/{xychartDiagram-S5SC5T6Z-C1QsLZg7.js → xychartDiagram-S5SC5T6Z-CswYgYeu.js} +5 -5
  117. package/package.json +25 -22
  118. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  119. package/src/__tests__/host-imports.test.ts +100 -0
  120. package/src/__tests__/mount.test.ts +8 -8
  121. package/src/components/data-table/__tests__/column_formatting.test.ts +30 -0
  122. package/src/components/data-table/__tests__/columns.test.tsx +70 -0
  123. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  124. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  125. package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
  126. package/src/components/data-table/column-formatting/feature.ts +20 -1
  127. package/src/components/data-table/columns.tsx +25 -18
  128. package/src/components/data-table/context-menu.tsx +125 -26
  129. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  130. package/src/components/data-table/export-actions.tsx +192 -116
  131. package/src/components/data-table/schemas.ts +14 -36
  132. package/src/components/data-table/utils.ts +18 -0
  133. package/src/components/editor/Output.tsx +9 -2
  134. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  135. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  136. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  137. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  138. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  139. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +372 -0
  140. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  141. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  142. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  143. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +4 -3
  144. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +548 -0
  145. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  146. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  147. package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
  148. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  149. package/src/components/editor/output/ansi-reduce.ts +6 -0
  150. package/src/components/editor/output/console/ConsoleOutput.tsx +5 -3
  151. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +18 -0
  152. package/src/components/storage/__tests__/storage-file-viewer.test.tsx +2 -1
  153. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  154. package/src/core/attachments/state.ts +21 -0
  155. package/src/core/attachments/toast.ts +15 -0
  156. package/src/core/cells/__tests__/cells.test.ts +52 -0
  157. package/src/core/cells/__tests__/document-roundtrip.test.ts +16 -0
  158. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  159. package/src/core/cells/__tests__/session.test.ts +50 -1
  160. package/src/core/cells/error-report.ts +63 -0
  161. package/src/core/cells/session.ts +28 -1
  162. package/src/core/codemirror/copilot/__tests__/completion-integration.test.ts +447 -0
  163. package/src/core/codemirror/copilot/__tests__/inline-completion.test.ts +395 -0
  164. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +511 -2
  165. package/src/core/codemirror/copilot/__tests__/rpc-cancellation.test.ts +201 -0
  166. package/src/core/codemirror/copilot/extension.ts +22 -11
  167. package/src/core/codemirror/copilot/language-server.ts +109 -60
  168. package/src/core/codemirror/language/__tests__/sql.test.ts +4 -3
  169. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +138 -26
  170. package/src/core/codemirror/language/embedded/embedded-python.ts +47 -13
  171. package/src/core/codemirror/language/embedded/latex.ts +20 -13
  172. package/src/core/config/pair.ts +5 -3
  173. package/src/core/datasets/__tests__/data-source-discovery.test.ts +18 -11
  174. package/src/core/host/AGENTS.md +6 -0
  175. package/src/core/host/__tests__/protocol.test.ts +214 -0
  176. package/src/core/host/protocol.ts +1943 -0
  177. package/src/core/islands/__tests__/bootstrap.test.ts +26 -0
  178. package/src/core/islands/bootstrap.ts +1 -0
  179. package/src/core/islands/bridge.ts +2 -2
  180. package/src/core/kernel/messages.ts +4 -7
  181. package/src/core/network/__tests__/api.test.ts +114 -1
  182. package/src/core/network/api.ts +3 -2
  183. package/src/core/wasm/bridge.ts +2 -2
  184. package/src/core/wasm/worker/types.ts +2 -2
  185. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +193 -0
  186. package/src/core/websocket/useMarimoKernelConnection.tsx +9 -0
  187. package/src/css/katex-css.test.ts +1 -1
  188. package/src/css/katex.min.css +6 -1
  189. package/src/hooks/__tests__/useEventListener.test.tsx +45 -1
  190. package/src/hooks/useEventListener.ts +6 -1
  191. package/src/mount.tsx +3 -3
  192. package/src/plugins/impl/NumberPlugin.tsx +20 -3
  193. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  194. package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +159 -1
  195. package/src/utils/__tests__/dates.test.ts +116 -0
  196. package/src/utils/dates.ts +94 -0
  197. package/dist/assets/__vite-browser-external-BqAtzd2P.js +0 -1
  198. package/dist/assets/worker-Bf8dt6S-.js +0 -109
  199. package/dist/classDiagram-ZZMXUADV-Bbo2n384.js +0 -30
  200. package/dist/classDiagram-v2-VYDZK3BY-xR8cGP0r.js +0 -30
  201. package/dist/error-entries-BovkFYNy.js +0 -56
  202. package/dist/flowDiagram-HODETNUW-D7IBKS12.js +0 -16
  203. package/dist/mermaid-HWGCJPDP-DYCzSILp.js +0 -2
  204. package/dist/stateDiagram-v2-MP3YSRHH-ChNddepZ.js +0 -30
  205. package/dist/swimlanesDiagram-VR7AAH4N-FUSwmzEp.js +0 -30
@@ -0,0 +1,63 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describeError } from "@/core/errors/error-entries";
4
+ import type { MarimoError } from "@/core/kernel/messages";
5
+ import { sanitizeHtml } from "@/plugins/core/sanitize-html";
6
+ import { parseHtmlContent } from "@/utils/dom";
7
+ import type { CellId } from "./ids";
8
+
9
+ export interface CellOutputMessage<Output> {
10
+ type: "cell-output";
11
+ cell_id: CellId;
12
+ code?: string;
13
+ output: Output;
14
+ }
15
+
16
+ export interface ErrorMessageOutput {
17
+ type: "error";
18
+ instructions: string;
19
+ message: string;
20
+ traceback?: string;
21
+ }
22
+
23
+ export const ERROR_MESSAGE_INSTRUCTIONS = "Fix this notebook error.";
24
+
25
+ export function buildErrorReport(
26
+ cellId: CellId,
27
+ evidence: { code?: string } & (
28
+ | { errors: MarimoError[] }
29
+ | { traceback: string; tracebackFormat: "html" | "text" }
30
+ ),
31
+ ): CellOutputMessage<ErrorMessageOutput> {
32
+ let message: string;
33
+ let traceback: string;
34
+ if ("errors" in evidence) {
35
+ message = evidence.errors.map(describeError).join("\n");
36
+ traceback = evidence.errors
37
+ .flatMap((error) =>
38
+ "traceback" in error && error.traceback
39
+ ? [parseHtmlContent(sanitizeHtml(error.traceback))]
40
+ : [],
41
+ )
42
+ .join("\n\n");
43
+ } else {
44
+ traceback =
45
+ evidence.tracebackFormat === "html"
46
+ ? parseHtmlContent(sanitizeHtml(evidence.traceback))
47
+ : evidence.traceback;
48
+ message = traceback.split("\n").findLast((line) => line.length > 0) ?? "";
49
+ }
50
+
51
+ // Put repair intent before evidence so it survives prefix truncation.
52
+ return {
53
+ type: "cell-output",
54
+ cell_id: cellId,
55
+ output: {
56
+ type: "error",
57
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
58
+ message,
59
+ ...(traceback ? { traceback } : {}),
60
+ },
61
+ ...(evidence.code === undefined ? {} : { code: evidence.code }),
62
+ };
63
+ }
@@ -206,8 +206,20 @@ function createCellRuntimeFromSession(
206
206
  channel: "marimo-error",
207
207
  data: [
208
208
  {
209
- type: "unknown",
209
+ type: "exception",
210
+ exception_type: primaryOutput.ename,
210
211
  msg: primaryOutput.evalue,
212
+ raising_cell: null,
213
+ traceback:
214
+ primaryOutput.traceback.length === 0
215
+ ? null
216
+ : primaryOutput.traceback
217
+ .map((line, index, lines) =>
218
+ index < lines.length - 1 && !line.endsWith("\n")
219
+ ? `${line}\n`
220
+ : line,
221
+ )
222
+ .join(""),
211
223
  },
212
224
  ],
213
225
  mimetype: "application/vnd.marimo+error",
@@ -223,6 +235,9 @@ function createCellRuntimeFromSession(
223
235
  timestamp: DEFAULT_TIMESTAMP,
224
236
  };
225
237
  }
238
+ if (runtimeState.output && primaryOutput.code !== undefined) {
239
+ runtimeState.output.code = primaryOutput.code;
240
+ }
226
241
  }
227
242
 
228
243
  const consoleOutputs = sessionCell.console || [];
@@ -237,7 +252,13 @@ function createCellRuntimeFromSession(
237
252
  channel: "media",
238
253
  data: consoleOutput.data,
239
254
  mimetype: consoleOutput.mimetype,
255
+ ...(consoleOutput.traceback_format === undefined
256
+ ? {}
257
+ : { traceback_format: consoleOutput.traceback_format }),
240
258
  timestamp: DEFAULT_TIMESTAMP,
259
+ ...(consoleOutput.code === undefined
260
+ ? {}
261
+ : { code: consoleOutput.code }),
241
262
  };
242
263
  }
243
264
  // Handle StreamOutput (type: "stream")
@@ -245,7 +266,13 @@ function createCellRuntimeFromSession(
245
266
  channel: consoleOutput.name === "stderr" ? "stderr" : "stdout",
246
267
  data: consoleOutput.text,
247
268
  mimetype: consoleOutput.mimetype ?? "text/plain",
269
+ ...(consoleOutput.traceback_format === undefined
270
+ ? {}
271
+ : { traceback_format: consoleOutput.traceback_format }),
248
272
  timestamp: DEFAULT_TIMESTAMP,
273
+ ...(consoleOutput.code === undefined
274
+ ? {}
275
+ : { code: consoleOutput.code }),
249
276
  };
250
277
  }),
251
278
  };
@@ -0,0 +1,447 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { EditorView } from "@codemirror/view";
4
+ import {
5
+ languageServerWithClient,
6
+ type JSONRPCMessage,
7
+ type JSONRPCRequest,
8
+ type Transport,
9
+ } from "@marimo-team/codemirror-languageserver";
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
+ import { API } from "@/core/network/api";
12
+ import { languageAdapterState } from "../../language/extension";
13
+ import { copilotBundle } from "../extension";
14
+ import { getCodes } from "../getCodes";
15
+ import { CopilotLanguageServerClient } from "../language-server";
16
+
17
+ const fixture: { client?: CopilotLanguageServerClient } = vi.hoisted(
18
+ () => ({}),
19
+ );
20
+
21
+ vi.mock("../client", () => ({
22
+ COPILOT_FILENAME: "/__marimo_copilot__.py",
23
+ getCopilotClient: () => fixture.client,
24
+ copilotServer: () =>
25
+ languageServerWithClient({
26
+ client: fixture.client!,
27
+ documentUri: "file:///__marimo_copilot__.py",
28
+ languageId: "python",
29
+ sendIncrementalChanges: false,
30
+ hoverEnabled: false,
31
+ completionEnabled: false,
32
+ diagnosticsEnabled: false,
33
+ signatureHelpEnabled: false,
34
+ }),
35
+ }));
36
+ vi.mock("../state", async (importOriginal) => ({
37
+ ...(await importOriginal<typeof import("../state")>()),
38
+ isCopilotEnabled: () => true,
39
+ }));
40
+ vi.mock("../getCodes", () => ({
41
+ getCodes: vi.fn((code: string) => `import math\n\n${code}`),
42
+ }));
43
+
44
+ describe("Copilot editor integration", () => {
45
+ let view: EditorView;
46
+ let client: CopilotLanguageServerClient;
47
+ let emit: (message: JSONRPCMessage) => void;
48
+ let sent: JSONRPCMessage[];
49
+
50
+ function requests() {
51
+ return sent.filter(
52
+ (message): message is JSONRPCRequest =>
53
+ "id" in message &&
54
+ "method" in message &&
55
+ message.method === "textDocument/inlineCompletion",
56
+ );
57
+ }
58
+ function type(text: string) {
59
+ const from = view.state.selection.main.head;
60
+ view.dispatch({
61
+ changes: { from, insert: text },
62
+ selection: { anchor: from + text.length },
63
+ });
64
+ }
65
+ function respond(text: string) {
66
+ emit({
67
+ jsonrpc: "2.0",
68
+ id: requests().at(-1)!.id,
69
+ result: [{ insertText: text }],
70
+ });
71
+ }
72
+
73
+ function useCustomEditor() {
74
+ view.destroy();
75
+ view = new EditorView({
76
+ parent: document.body,
77
+ doc: "pri",
78
+ selection: { anchor: 3 },
79
+ extensions: [
80
+ languageAdapterState,
81
+ copilotBundle({
82
+ copilot: "custom",
83
+ activate_on_typing: true,
84
+ signature_hint_on_typing: false,
85
+ }),
86
+ ],
87
+ });
88
+ vi.spyOn(view, "hasFocus", "get").mockReturnValue(true);
89
+ }
90
+
91
+ beforeEach(async () => {
92
+ vi.useFakeTimers();
93
+ // copilotBundle intentionally disables itself in ordinary unit tests.
94
+ vi.stubEnv("NODE_ENV", "development");
95
+ sent = [];
96
+ const transport: Transport = {
97
+ connect: vi.fn().mockResolvedValue(undefined),
98
+ close: vi.fn(),
99
+ onMessage: (handler) => {
100
+ emit = handler;
101
+ return vi.fn();
102
+ },
103
+ send: (message) => {
104
+ sent.push(message);
105
+ if (
106
+ "id" in message &&
107
+ "method" in message &&
108
+ ["initialize", "shutdown"].includes(message.method)
109
+ ) {
110
+ emit({
111
+ jsonrpc: "2.0",
112
+ id: message.id,
113
+ result:
114
+ message.method === "initialize"
115
+ ? { capabilities: { textDocumentSync: 1 } }
116
+ : null,
117
+ });
118
+ }
119
+ },
120
+ };
121
+ client = new CopilotLanguageServerClient({
122
+ rootUri: "file:///",
123
+ workspaceFolders: null,
124
+ transport,
125
+ });
126
+ fixture.client = client;
127
+ await client.initializePromise;
128
+ view = new EditorView({
129
+ parent: document.body,
130
+ doc: "pri",
131
+ selection: { anchor: 3 },
132
+ extensions: copilotBundle({
133
+ copilot: "github",
134
+ activate_on_typing: true,
135
+ signature_hint_on_typing: false,
136
+ }),
137
+ });
138
+ vi.spyOn(view, "hasFocus", "get").mockReturnValue(true);
139
+ await vi.advanceTimersByTimeAsync(0);
140
+ sent.length = 0;
141
+ vi.mocked(getCodes).mockClear();
142
+ });
143
+
144
+ afterEach(() => {
145
+ view.destroy();
146
+ client.close();
147
+ fixture.client = undefined;
148
+ vi.useRealTimers();
149
+ vi.unstubAllEnvs();
150
+ vi.restoreAllMocks();
151
+ });
152
+
153
+ it("uploads once and requests the matching version/position at 150ms without an extra sleep", async () => {
154
+ type("n");
155
+ type("t");
156
+ await vi.advanceTimersByTimeAsync(149);
157
+ expect(sent).toEqual([]);
158
+ expect(getCodes).not.toHaveBeenCalled();
159
+ await vi.advanceTimersByTimeAsync(1);
160
+ expect(getCodes).toHaveBeenCalledExactlyOnceWith("print");
161
+ expect(sent).toEqual([
162
+ {
163
+ jsonrpc: "2.0",
164
+ method: "textDocument/didChange",
165
+ params: {
166
+ textDocument: { uri: "file:///__marimo_copilot__.py", version: 2 },
167
+ contentChanges: [{ text: "import math\n\nprint" }],
168
+ },
169
+ },
170
+ {
171
+ jsonrpc: "2.0",
172
+ id: expect.any(Number),
173
+ method: "textDocument/inlineCompletion",
174
+ params: {
175
+ textDocument: { uri: "file:///__marimo_copilot__.py", version: 2 },
176
+ position: { line: 2, character: 5 },
177
+ context: { triggerKind: 2 },
178
+ formattingOptions: { tabSize: 4, insertSpaces: true },
179
+ },
180
+ },
181
+ ]);
182
+ respond("(value)");
183
+ await vi.advanceTimersByTimeAsync(0);
184
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
185
+ "(value)",
186
+ );
187
+ type("(");
188
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
189
+ "value)",
190
+ );
191
+ await vi.advanceTimersByTimeAsync(500);
192
+ expect(requests()).toHaveLength(1);
193
+ expect(getCodes).toHaveBeenCalledTimes(1);
194
+ type("x");
195
+ await vi.advanceTimersByTimeAsync(150);
196
+ expect(getCodes).toHaveBeenLastCalledWith("print(x");
197
+ expect(requests()).toHaveLength(2);
198
+ respond(")");
199
+ await vi.advanceTimersByTimeAsync(0);
200
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
201
+ ")",
202
+ );
203
+ });
204
+
205
+ it("never uploads a superseded snapshot when typing restarts the debounce", async () => {
206
+ const flush = vi.spyOn(client, "flushDocumentChanges");
207
+ type("n");
208
+ await vi.advanceTimersByTimeAsync(149);
209
+ type("t");
210
+ await vi.advanceTimersByTimeAsync(149);
211
+ expect(flush).not.toHaveBeenCalled();
212
+ expect(getCodes).not.toHaveBeenCalled();
213
+ expect(sent).toEqual([]);
214
+ await vi.advanceTimersByTimeAsync(1);
215
+ expect(flush.mock.calls.filter(([code]) => code !== undefined)).toEqual([
216
+ ["print"],
217
+ ]);
218
+ expect(getCodes).toHaveBeenCalledExactlyOnceWith("print");
219
+ expect(
220
+ sent.filter(
221
+ (message) =>
222
+ "method" in message && message.method === "textDocument/didChange",
223
+ ),
224
+ ).toEqual([
225
+ {
226
+ jsonrpc: "2.0",
227
+ method: "textDocument/didChange",
228
+ params: {
229
+ textDocument: { uri: "file:///__marimo_copilot__.py", version: 2 },
230
+ contentChanges: [{ text: "import math\n\nprint" }],
231
+ },
232
+ },
233
+ ]);
234
+ expect(requests()).toHaveLength(1);
235
+ respond("(latest)");
236
+ await vi.advanceTimersByTimeAsync(0);
237
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
238
+ "(latest)",
239
+ );
240
+ });
241
+
242
+ it("does not flush a cancelled callback after moving away and back before its deadline", async () => {
243
+ const flush = vi.spyOn(client, "flushDocumentChanges");
244
+ type("n");
245
+ await vi.advanceTimersByTimeAsync(149);
246
+ view.dispatch({ selection: { anchor: 0 } });
247
+ view.dispatch({ selection: { anchor: 4 } });
248
+ await vi.advanceTimersByTimeAsync(1000);
249
+ expect(flush).not.toHaveBeenCalled();
250
+ expect(getCodes).not.toHaveBeenCalled();
251
+ expect(sent).toEqual([]);
252
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
253
+ });
254
+
255
+ it("cancels on cursor movement and never shows an old reply", async () => {
256
+ type("n");
257
+ await vi.advanceTimersByTimeAsync(150);
258
+ view.dispatch({ selection: { anchor: 0 } });
259
+ await vi.advanceTimersByTimeAsync(0);
260
+ expect(sent.at(-1)).toEqual({
261
+ jsonrpc: "2.0",
262
+ method: "$/cancelRequest",
263
+ params: { id: requests()[0].id },
264
+ });
265
+ respond("stale");
266
+ await vi.advanceTimersByTimeAsync(0);
267
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
268
+ });
269
+
270
+ it("cancels an in-flight suggestion on typing before requesting the next version", async () => {
271
+ type("n");
272
+ await vi.advanceTimersByTimeAsync(150);
273
+ type("t");
274
+ await vi.advanceTimersByTimeAsync(0);
275
+ expect(sent.at(-1)).toEqual({
276
+ jsonrpc: "2.0",
277
+ method: "$/cancelRequest",
278
+ params: { id: requests()[0].id },
279
+ });
280
+ await vi.advanceTimersByTimeAsync(150);
281
+ respond("(new)");
282
+ await vi.advanceTimersByTimeAsync(0);
283
+ emit({
284
+ jsonrpc: "2.0",
285
+ id: requests()[0].id,
286
+ result: [{ insertText: "stale" }],
287
+ });
288
+ await vi.advanceTimersByTimeAsync(0);
289
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
290
+ "(new)",
291
+ );
292
+ });
293
+ it("does not display a response after focus is lost", async () => {
294
+ type("n");
295
+ await vi.advanceTimersByTimeAsync(150);
296
+ vi.spyOn(view, "hasFocus", "get").mockReturnValue(false);
297
+ respond("stale");
298
+ await vi.advanceTimersByTimeAsync(0);
299
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
300
+ });
301
+
302
+ it("passes cancellation through to custom provider HTTP requests", async () => {
303
+ const result = Promise.withResolvers<string>();
304
+ const post = vi.spyOn(API, "post").mockReturnValue(result.promise);
305
+ useCustomEditor();
306
+ type("n");
307
+ await vi.advanceTimersByTimeAsync(150);
308
+ expect(post).toHaveBeenCalledExactlyOnceWith(
309
+ "/ai/inline_completion",
310
+ { prefix: "prin", suffix: "", language: "python" },
311
+ { signal: expect.objectContaining({ aborted: false }) },
312
+ );
313
+ const signal = post.mock.calls[0][2]?.signal;
314
+ type("t");
315
+ expect(signal?.aborted).toBe(true);
316
+ result.resolve("stale");
317
+ await vi.advanceTimersByTimeAsync(0);
318
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
319
+ });
320
+ it.each(["HTTP failure", "AbortError"])(
321
+ "recovers on the next edit after custom provider %s",
322
+ async (failure) => {
323
+ useCustomEditor();
324
+ const error = vi
325
+ .spyOn(console, "error")
326
+ .mockImplementation(() => undefined);
327
+ const post = vi
328
+ .spyOn(API, "post")
329
+ .mockRejectedValueOnce(
330
+ failure === "AbortError"
331
+ ? new DOMException("aborted", "AbortError")
332
+ : new Error("HTTP 503"),
333
+ )
334
+ .mockResolvedValueOnce("(recovered)");
335
+ type("n");
336
+ await vi.advanceTimersByTimeAsync(150);
337
+ expect(post).toHaveBeenCalledTimes(1);
338
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
339
+ expect(error).toHaveBeenCalledTimes(failure === "AbortError" ? 0 : 1);
340
+ type("t");
341
+ await vi.advanceTimersByTimeAsync(150);
342
+ expect(post).toHaveBeenCalledTimes(2);
343
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
344
+ "(recovered)",
345
+ );
346
+ },
347
+ );
348
+
349
+ it.each(["empty document", "empty prefix"])(
350
+ "does not call the custom provider for an %s",
351
+ async (input) => {
352
+ useCustomEditor();
353
+ const post = vi.spyOn(API, "post").mockResolvedValue("unexpected");
354
+ view.dispatch({
355
+ changes: {
356
+ from: 0,
357
+ to: view.state.doc.length,
358
+ insert: input === "empty document" ? "" : "suffix",
359
+ },
360
+ selection: { anchor: 0 },
361
+ });
362
+ await vi.advanceTimersByTimeAsync(150);
363
+ expect(post).not.toHaveBeenCalled();
364
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
365
+ },
366
+ );
367
+
368
+ it("aborts custom provider HTTP work when its editor is destroyed", async () => {
369
+ useCustomEditor();
370
+ const result = Promise.withResolvers<string>();
371
+ const post = vi.spyOn(API, "post").mockReturnValue(result.promise);
372
+ const error = vi
373
+ .spyOn(console, "error")
374
+ .mockImplementation(() => undefined);
375
+ type("n");
376
+ await vi.advanceTimersByTimeAsync(150);
377
+ expect(post).toHaveBeenCalledTimes(1);
378
+ const signal = post.mock.calls[0][2]?.signal;
379
+ expect(signal?.aborted).toBe(false);
380
+ view.destroy();
381
+ expect(signal?.aborted).toBe(true);
382
+ result.reject(new Error("connection closed after destruction"));
383
+ await vi.advanceTimersByTimeAsync(150);
384
+ expect(post).toHaveBeenCalledTimes(1);
385
+ expect(error).not.toHaveBeenCalled();
386
+ });
387
+
388
+ it.each(["server error", "timeout"])(
389
+ "recovers on the next edit after a Copilot %s",
390
+ async (failure) => {
391
+ type("n");
392
+ await vi.advanceTimersByTimeAsync(150);
393
+ expect(requests()).toHaveLength(1);
394
+ const id = requests()[0].id;
395
+ if (failure === "timeout") {
396
+ await vi.advanceTimersByTimeAsync(10_000);
397
+ } else {
398
+ emit({
399
+ jsonrpc: "2.0",
400
+ id,
401
+ error: { code: -32603, message: "unavailable" },
402
+ });
403
+ await vi.advanceTimersByTimeAsync(0);
404
+ }
405
+ expect(
406
+ sent.filter(
407
+ (message) =>
408
+ "method" in message && message.method === "$/cancelRequest",
409
+ ),
410
+ ).toEqual(
411
+ failure === "timeout"
412
+ ? [{ jsonrpc: "2.0", method: "$/cancelRequest", params: { id } }]
413
+ : [],
414
+ );
415
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toBeNull();
416
+ type("t");
417
+ await vi.advanceTimersByTimeAsync(150);
418
+ expect(requests()).toHaveLength(2);
419
+ respond("(recovered)");
420
+ await vi.advanceTimersByTimeAsync(0);
421
+ emit({ jsonrpc: "2.0", id, result: [{ insertText: "stale" }] });
422
+ await vi.advanceTimersByTimeAsync(0);
423
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
424
+ "(recovered)",
425
+ );
426
+ },
427
+ );
428
+
429
+ it("keeps the focused cell last when another cell changes during the debounce", async () => {
430
+ type("n");
431
+ await client.textDocumentDidChange({
432
+ textDocument: { uri: "file:///__marimo_copilot__.py", version: 1 },
433
+ contentChanges: [{ text: "other_cell = 42" }],
434
+ });
435
+ await vi.advanceTimersByTimeAsync(150);
436
+ expect(getCodes).toHaveBeenCalledExactlyOnceWith("prin");
437
+ expect(requests()[0].params).toMatchObject({
438
+ textDocument: { version: 3 },
439
+ position: { line: 2, character: 4 },
440
+ });
441
+ respond("t()");
442
+ await vi.advanceTimersByTimeAsync(0);
443
+ expect(view.dom.querySelector(".cm-inline-suggestion")).toHaveTextContent(
444
+ "t()",
445
+ );
446
+ });
447
+ });