@marimo-team/frontend 0.25.3-dev3 → 0.25.3-dev30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{CellStatus-BP2mavAq.js → CellStatus-wFCGHpDX.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-DMdJT25t.js} +1 -1
- package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-D1ozl9e0.js} +2 -2
- package/dist/assets/JsonOutput-Dqg7qwkl.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-B4fCknq1.js} +2 -2
- package/dist/assets/MarimoErrorOutput-BYSTEwo1.js +10 -0
- package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-B18UiW8r.js} +1 -1
- package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-CWvRnb98.js} +1 -1
- package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-C4pN_ho7.js} +13 -13
- package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-BErVOfUV.js} +2 -2
- package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-QQDTP8Vb.js} +5 -5
- package/dist/assets/ai-model-dropdown-D5Fe5GS-.js +13 -0
- package/dist/assets/{any-language-editor-BLCAD3yx.js → any-language-editor-BdTWdMCI.js} +1 -1
- package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-DBHN-U2l.js} +1 -1
- package/dist/assets/{blob-D5nfNHOo.js → blob-DY_W1wFZ.js} +1 -1
- package/dist/assets/{cell-editor-ADVfycpN.js → cell-editor-CCFDuQIT.js} +9 -9
- package/dist/assets/{cell-link-LSth_8hT.js → cell-link-BOqoitvE.js} +1 -1
- package/dist/assets/{cells-BRkCaSsh.js → cells-CN1I_0eH.js} +31 -31
- package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-D1_UV6v7.js} +1 -1
- package/dist/assets/{chat-panel-DQ6HkWEF.js → chat-panel-C4OJDIt6.js} +2 -2
- package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-wuDYPZ-w.js} +1 -1
- package/dist/assets/code-visibility-CnU9xZw7.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/{column-preview-Cm2D6vyX.js → column-preview-z4bqk4HW.js} +1 -1
- package/dist/assets/command-palette-TblDi15Q.js +1 -0
- package/dist/assets/{common-Cu6CDbuw.js → common-Cbk0KIh7.js} +1 -1
- package/dist/assets/{components-CGw16R_N.js → components-C88UxBXz.js} +1 -1
- package/dist/assets/{components-CP_UoFtT.js → components-CskjK6xZ.js} +1 -1
- package/dist/assets/{components-CXfO-F47.js → components-YBmtV0p6.js} +1 -1
- package/dist/assets/{config-VPSq73n8.js → config-CA1V-wjh.js} +1 -1
- package/dist/assets/config-DEzFV5-l.js +1 -0
- package/dist/assets/config-MaHeMC9o.js +104 -0
- package/dist/assets/{datasource-Cq6x_5ML.js → datasource-DZJ6qqVI.js} +1 -1
- package/dist/assets/{dates-BbFQ2oE4.js → dates-CfmdTL6B.js} +1 -1
- package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-DeZ66gIo.js} +1 -1
- package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-Tl5G1cPt.js} +1 -1
- package/dist/assets/{download-DCJXy66H.js → download-s8iwxGDb.js} +1 -1
- package/dist/assets/edit-page-CjoAJvyN.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-COZ7SfYV.js} +1 -1
- package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-D11Bn1Hk.js} +1 -1
- package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-kfxFDscu.js} +1 -1
- package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
- package/dist/assets/feedback-button-3bZHpuSh.js +1 -0
- package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DVABjD9Y.js} +2 -2
- package/dist/assets/{file-explorer-panel-BEEK5lcl.js → file-explorer-panel-CvmK-rEU.js} +1 -1
- package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-DAj9a3RQ.js} +1 -1
- package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-DDavqWJx.js} +1 -1
- package/dist/assets/{formats-yZ06h1Pl.js → formats-BkelUASr.js} +1 -1
- package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-xP6VqDyd.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
- package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-32vBJJf8.js} +1 -1
- package/dist/assets/{home-page-DMHnsRxm.js → home-page-CvGzjVr4.js} +1 -1
- package/dist/assets/hooks-C1_gQ8kr.js +1 -0
- package/dist/assets/index-6b4mf8Ai.css +2 -0
- package/dist/assets/index-Dtv0HH-7.js +30 -0
- package/dist/assets/{layout-DmcNEu3P.js → layout-BccvBeZy.js} +5 -5
- package/dist/assets/linear-Bma8J3iM.js +1 -0
- package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-BhXo1sVJ.js} +1 -1
- package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-D2dafr8r.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-D2FUGQeW.js} +2 -2
- package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-SHvgfcBR.js} +1 -1
- package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-CpY3FH-u.js} +1 -1
- package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-oZ0qZBec.js} +1 -1
- package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-ChFIY2WQ.js} +1 -1
- package/dist/assets/panels-91ywfwgc.js +4 -0
- package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
- package/dist/assets/{process-output-D0KGq04R.js → process-output-Dvq4b2kJ.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
- package/dist/assets/react-vega-BTNeboR2.js +189 -0
- package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-C1eH5BDP.js} +1 -1
- package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-BaElyj-4.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-C1JwYcqW.js} +1 -1
- package/dist/assets/{reveal-component-ltaepAqw.js → reveal-component-FCuqfC5H.js} +3 -3
- package/dist/assets/{run-page-DF3c5cDk.js → run-page-CbyMlRt1.js} +1 -1
- package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-Bpn67vTN.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/{scratchpad-panel-Dwd6W5PE.js → scratchpad-panel-OF9_e0l_.js} +1 -1
- package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-D7KN4tVs.js} +1 -1
- package/dist/assets/session-panel-CExxjkjV.js +1 -0
- package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-DfRtTFJ-.js} +1 -1
- package/dist/assets/state-BUqF6L9e.js +3 -0
- package/dist/assets/{state-XsPO8hNi.js → state-CHojzKpZ.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal--4gDbK7q.js} +3 -3
- package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
- package/dist/assets/tooltip-BsSdc1NQ.js +61 -0
- package/dist/assets/{tracing-Di6pUmX8.js → tracing-BnfAB-BH.js} +1 -1
- package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-DNabsbtA.js} +2 -2
- package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-BXvnBv45.js} +1 -1
- package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-6ECZ0xPG.js} +1 -1
- package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-BvAx9Qrr.js} +1 -1
- package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-Djyiwayj.js} +1 -1
- package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-KX6SEwki.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-B8ke9iTY.js} +1 -1
- package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-C9-icL78.js} +1 -1
- package/dist/assets/useNotebookActions-CluLEhAd.js +1 -0
- package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-B847kPLO.js} +1 -1
- package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-CnvTmIw7.js} +1 -1
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-n8Dj1zH7.js} +1 -1
- package/dist/assets/{vega-component-CnadTgoE.js → vega-component-DAkdRNSF.js} +1 -1
- package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-CxuIHhsS.js} +1 -1
- package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
- package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
- package/dist/index.html +44 -45
- package/package.json +2 -1
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/mount.test.ts +28 -8
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
- package/src/components/data-table/context-menu.tsx +125 -26
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/export-actions.tsx +192 -116
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/data-table/utils.ts +18 -0
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +18 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/cells.test.ts +52 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/session.ts +28 -1
- package/src/core/config/pair.ts +5 -3
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +5 -0
- package/src/core/host/__tests__/attachments.test.ts +57 -0
- package/src/core/host/__tests__/document.test.ts +124 -0
- package/src/core/host/__tests__/events.test.ts +46 -0
- package/src/core/host/__tests__/fixtures.ts +39 -0
- package/src/core/host/__tests__/notebook-client.test.ts +433 -0
- package/src/core/host/__tests__/protocol.test.ts +15 -0
- package/src/core/host/__tests__/stream.test.ts +209 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +61 -0
- package/src/core/host/notebook-client.ts +290 -0
- package/src/core/host/protocol.ts +11 -6
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/resources/resource.ts +47 -0
- package/src/core/host/stream.ts +211 -0
- package/src/core/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +55 -11
- package/src/core/runtime/runtime.ts +15 -1
- package/src/core/runtime/types.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +170 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +181 -65
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +88 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +59 -225
- package/src/mount.tsx +7 -3
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/dates.ts +94 -0
- package/src/utils/once.ts +19 -19
- package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
- package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
- package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
- package/dist/assets/api-BeC_VSGv.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/command-palette-DTSQ7gOC.js +0 -1
- package/dist/assets/config-BOKzA3tF.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/edit-page-ChcSgpke.js +0 -9
- package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
- package/dist/assets/hooks-93IwHd2u.js +0 -1
- package/dist/assets/index-Bcdzex-3.css +0 -2
- package/dist/assets/index-s-M1OXjs.js +0 -29
- package/dist/assets/linear-0eij_aAG.js +0 -1
- package/dist/assets/pair-CaQyK6En.js +0 -1
- package/dist/assets/panels-CxVG8-v2.js +0 -4
- package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
- package/dist/assets/react-vega-C3rYGtP6.js +0 -189
- package/dist/assets/session-panel-VdDhEBa-.js +0 -1
- package/dist/assets/state-_Qai_QyX.js +0 -3
- package/dist/assets/tooltip-mT3CGest.js +0 -61
- package/dist/assets/useNotebookActions-NHN6A-cN.js +0 -1
- package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -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: "
|
|
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
|
};
|
package/src/core/config/pair.ts
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
import { atom } from "jotai";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const pairConfigSchema = z.object({
|
|
7
7
|
command: z.string(),
|
|
8
|
+
hostEnabled: z.boolean().default(false),
|
|
8
9
|
templates: z.object({
|
|
9
10
|
prompt: z.string(),
|
|
11
|
+
notebook: z.string(),
|
|
10
12
|
session: z.string(),
|
|
11
13
|
token_file: z.string(),
|
|
12
14
|
token: z.string(),
|
|
13
15
|
}),
|
|
14
16
|
});
|
|
15
17
|
|
|
16
|
-
export type
|
|
18
|
+
export type PairConfig = z.infer<typeof pairConfigSchema>;
|
|
17
19
|
|
|
18
|
-
export const
|
|
20
|
+
export const pairConfigAtom = atom<PairConfig | undefined>(undefined);
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -41,7 +41,7 @@ import { viewStateAtom } from "./mode";
|
|
|
41
41
|
import { useRequestClient } from "./network/requests";
|
|
42
42
|
import { useFilename } from "./saving/filename";
|
|
43
43
|
import { lastSavedNotebookAtom } from "./saving/state";
|
|
44
|
-
import {
|
|
44
|
+
import { useNotebookSession } from "./websocket/useNotebookSession";
|
|
45
45
|
|
|
46
46
|
interface AppProps {
|
|
47
47
|
/**
|
|
@@ -83,7 +83,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
83
83
|
};
|
|
84
84
|
}, []);
|
|
85
85
|
|
|
86
|
-
const { connection, reconnect } =
|
|
86
|
+
const { connection, reconnect } = useNotebookSession({
|
|
87
87
|
autoInstantiate: userConfig.runtime.auto_instantiate,
|
|
88
88
|
setCells: (cells, layout) => {
|
|
89
89
|
setCells(cells);
|
package/src/core/host/AGENTS.md
CHANGED
|
@@ -4,3 +4,8 @@ Production code may import host modules, `jotai/vanilla`, `zod`, and
|
|
|
4
4
|
`@/utils/Logger`. Enforced by `marimo/host-imports`. Tests are exempt.
|
|
5
5
|
|
|
6
6
|
Define interfaces here and inject application behavior through adapters.
|
|
7
|
+
|
|
8
|
+
`stream.ts` reduces stream events into one state. Each kind of object is a
|
|
9
|
+
`Resource` in `resources/` with pure handlers keyed by event name. The stream
|
|
10
|
+
handles phase, notebook identity, and control events itself. The client holds
|
|
11
|
+
one state atom and exposes derived atoms.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { attachments } from "../resources/attachments";
|
|
5
|
+
import { createStream } from "../stream";
|
|
6
|
+
import { fixtureEvents } from "./fixtures";
|
|
7
|
+
|
|
8
|
+
const stream = createStream({ attachments });
|
|
9
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
10
|
+
const records = snapshot.flatMap((event) =>
|
|
11
|
+
event.event === "attachment" ? [event.data.attachment] : [],
|
|
12
|
+
);
|
|
13
|
+
const confirmed = () => snapshot.reduce(stream.reduce, stream.initial);
|
|
14
|
+
|
|
15
|
+
describe("attachment resource", () => {
|
|
16
|
+
it("collects snapshot records, replaces by id, and removes attachments", () => {
|
|
17
|
+
const state = confirmed();
|
|
18
|
+
expect(state.resources.attachments).toEqual(records);
|
|
19
|
+
const [browser, agent] = records;
|
|
20
|
+
const renamed = { ...agent, name: "Renamed agent" };
|
|
21
|
+
const updated = stream.reduce(state, {
|
|
22
|
+
event: "attachment",
|
|
23
|
+
id: "98",
|
|
24
|
+
data: { attachment: renamed },
|
|
25
|
+
});
|
|
26
|
+
expect(updated.resources.attachments).toEqual([browser, renamed]);
|
|
27
|
+
expect(state.resources.attachments).toEqual(records);
|
|
28
|
+
|
|
29
|
+
const removed = stream.reduce(updated, {
|
|
30
|
+
event: "attachment.removed",
|
|
31
|
+
id: "99",
|
|
32
|
+
data: { id: browser.id, time: "2026-10-01T09:30:00Z" },
|
|
33
|
+
});
|
|
34
|
+
expect(removed.resources.attachments).toEqual([renamed]);
|
|
35
|
+
const repeated = stream.reduce(removed, {
|
|
36
|
+
event: "attachment.removed",
|
|
37
|
+
id: "100",
|
|
38
|
+
data: { id: browser.id, time: "2026-10-01T09:30:00Z" },
|
|
39
|
+
});
|
|
40
|
+
expect(repeated.resources.attachments).toBe(removed.resources.attachments);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("retains records for replay and rebuilds them after reset", () => {
|
|
44
|
+
const state = confirmed();
|
|
45
|
+
expect(stream.resume(state)).toBe(state);
|
|
46
|
+
const reset = stream.reduce(state, { event: "reset", data: {} });
|
|
47
|
+
expect(reset.resources.attachments).toEqual([]);
|
|
48
|
+
const replacement = snapshot
|
|
49
|
+
.filter(
|
|
50
|
+
(event) =>
|
|
51
|
+
event.event !== "attachment" ||
|
|
52
|
+
event.data.attachment.id === records[0].id,
|
|
53
|
+
)
|
|
54
|
+
.reduce(stream.reduce, reset);
|
|
55
|
+
expect(replacement.resources.attachments).toEqual([records[0]]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { NotebookEvent } from "../events";
|
|
5
|
+
import { document } from "../resources/document";
|
|
6
|
+
import { createStream } from "../stream";
|
|
7
|
+
import { fixtureEvents } from "./fixtures";
|
|
8
|
+
|
|
9
|
+
const stream = createStream({ document });
|
|
10
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
11
|
+
const edits = fixtureEvents("edits.sse");
|
|
12
|
+
|
|
13
|
+
function replay(events: readonly NotebookEvent[], from = stream.initial) {
|
|
14
|
+
return events.reduce((state, event) => stream.reduce(state, event), from);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const confirmed = () => replay(snapshot);
|
|
18
|
+
const current = (state: ReturnType<typeof replay>) =>
|
|
19
|
+
state.resources.document.current;
|
|
20
|
+
|
|
21
|
+
describe("document resource", () => {
|
|
22
|
+
it("publishes the snapshot at ready, then each completed edit and save", () => {
|
|
23
|
+
const ready = snapshot.findIndex((event) => event.event === "ready");
|
|
24
|
+
const opening = replay(snapshot.slice(0, ready));
|
|
25
|
+
expect(current(opening)).toBeNull();
|
|
26
|
+
const first = current(replay(snapshot.slice(ready), opening));
|
|
27
|
+
expect(first?.revision.order).toEqual(["c1", "c3", "k7f2"]);
|
|
28
|
+
expect([...first!.cells.keys()]).toEqual(first?.revision.order);
|
|
29
|
+
|
|
30
|
+
const [edit, save] = edits.filter((event) => event.event === "document");
|
|
31
|
+
const pending = replay(
|
|
32
|
+
edits.filter((event) => event.event !== "document"),
|
|
33
|
+
confirmed(),
|
|
34
|
+
);
|
|
35
|
+
expect(current(pending)).toEqual(first);
|
|
36
|
+
const changed = stream.reduce(pending, edit);
|
|
37
|
+
expect(current(changed)?.revision).toEqual(edit.data.document);
|
|
38
|
+
expect([...current(changed)!.cells.keys()].toSorted()).toEqual(
|
|
39
|
+
edit.data.document.order.toSorted(),
|
|
40
|
+
);
|
|
41
|
+
const saved = current(stream.reduce(changed, save));
|
|
42
|
+
expect(saved?.revision).toEqual(save.data.document);
|
|
43
|
+
expect(saved?.cells.get("c3")?.code).toBe("x = 22");
|
|
44
|
+
expect(saved?.cells.has("k7f2")).toBe(false);
|
|
45
|
+
expect(saved?.revision.saved_etag).toBe(saved?.revision.etag);
|
|
46
|
+
expect(first?.cells.get("c3")?.code).toBe("x = 21");
|
|
47
|
+
expect(first?.cells.has("k7f2")).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("applies order-only changes and distinguishes an empty notebook from no document", () => {
|
|
51
|
+
const state = confirmed();
|
|
52
|
+
const revision = current(state)!.revision;
|
|
53
|
+
const reordered = {
|
|
54
|
+
...revision,
|
|
55
|
+
order: revision.order.toReversed(),
|
|
56
|
+
version: revision.version + 1,
|
|
57
|
+
};
|
|
58
|
+
const changed = stream.reduce(state, {
|
|
59
|
+
event: "document",
|
|
60
|
+
data: { document: reordered },
|
|
61
|
+
id: "200",
|
|
62
|
+
});
|
|
63
|
+
expect(current(changed)).toEqual({
|
|
64
|
+
revision: reordered,
|
|
65
|
+
cells: current(state)?.cells,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const empty = { ...revision, order: [] };
|
|
69
|
+
const opening = stream.reduce(stream.initial, {
|
|
70
|
+
event: "document",
|
|
71
|
+
data: { document: empty },
|
|
72
|
+
});
|
|
73
|
+
expect(current(opening)).toBeNull();
|
|
74
|
+
expect(
|
|
75
|
+
current(stream.reduce(opening, { event: "ready", data: {}, id: "0" })),
|
|
76
|
+
).toEqual({ revision: empty, cells: new Map() });
|
|
77
|
+
expect(
|
|
78
|
+
current(
|
|
79
|
+
stream.reduce(stream.initial, { event: "ready", data: {}, id: "0" }),
|
|
80
|
+
),
|
|
81
|
+
).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("keeps the document while a live update is pending and after a replay completes it", () => {
|
|
85
|
+
const state = confirmed();
|
|
86
|
+
const [cell, , removed, published] = edits;
|
|
87
|
+
if (published.event !== "document") {
|
|
88
|
+
throw new Error("Fixture does not publish a document");
|
|
89
|
+
}
|
|
90
|
+
const partial = replay([cell, removed], state);
|
|
91
|
+
expect(current(partial)).toBe(current(state));
|
|
92
|
+
expect(stream.resume(partial)).toBe(partial);
|
|
93
|
+
const completed = replay(
|
|
94
|
+
[published, { event: "ready", data: {}, id: "113" }],
|
|
95
|
+
partial,
|
|
96
|
+
);
|
|
97
|
+
expect(current(completed)?.revision).toEqual(published.data.document);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("removes a notebook without waiting for individual cell removals", () => {
|
|
101
|
+
const removed = stream.reduce(confirmed(), {
|
|
102
|
+
event: "notebook.removed",
|
|
103
|
+
data: { id: "n1", time: "2026-10-01T09:30:00Z" },
|
|
104
|
+
id: "200",
|
|
105
|
+
});
|
|
106
|
+
expect(current(removed)).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("rejects records that cannot form a complete revision", () => {
|
|
110
|
+
const state = confirmed();
|
|
111
|
+
const revision = current(state)!.revision;
|
|
112
|
+
expect(() =>
|
|
113
|
+
stream.reduce(state, {
|
|
114
|
+
event: "document",
|
|
115
|
+
data: { document: { ...revision, order: ["missing"] } },
|
|
116
|
+
id: "200",
|
|
117
|
+
}),
|
|
118
|
+
).toThrow("Document records do not match");
|
|
119
|
+
const partial = stream.reduce(state, edits[0]);
|
|
120
|
+
expect(() =>
|
|
121
|
+
stream.reduce(partial, { event: "ready", data: {}, id: "200" }),
|
|
122
|
+
).toThrow("incomplete document update");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { parseNotebookEvent } from "../events";
|
|
6
|
+
|
|
7
|
+
describe("notebook messages", () => {
|
|
8
|
+
it("normalizes session and host envelopes for native marimo messages", () => {
|
|
9
|
+
const notification = {
|
|
10
|
+
op: "variables",
|
|
11
|
+
variables: [{ name: "x", value: Number.NaN }],
|
|
12
|
+
};
|
|
13
|
+
const host = { event: "marimo", data: { marimo: notification } };
|
|
14
|
+
expect(parseNotebookEvent({ data: notification })).toEqual(host);
|
|
15
|
+
expect(parseNotebookEvent({ ...host, id: "42" })).toEqual({
|
|
16
|
+
...host,
|
|
17
|
+
id: "42",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("retains unknown event cursors without interpreting their payloads", () => {
|
|
22
|
+
expect(
|
|
23
|
+
parseNotebookEvent({ event: "future", id: "42", data: null }),
|
|
24
|
+
).toEqual({ event: "unknown", name: "future", id: "42" });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("decodes known events outside the client's subscription filters", () => {
|
|
28
|
+
const event = {
|
|
29
|
+
event: "run.removed",
|
|
30
|
+
id: "42",
|
|
31
|
+
data: { id: "r1", time: "2026-10-19T12:00:00Z" },
|
|
32
|
+
};
|
|
33
|
+
expect(parseNotebookEvent(event)).toEqual(event);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it.each([
|
|
37
|
+
{ event: "cell", id: "42", data: { cell: { id: "c1" } } },
|
|
38
|
+
{ event: "run.removed", id: "42", data: {} },
|
|
39
|
+
{ event: "ready", data: {} },
|
|
40
|
+
{ event: "marimo", data: {} },
|
|
41
|
+
{},
|
|
42
|
+
null,
|
|
43
|
+
])("rejects malformed known messages: %j", (message) => {
|
|
44
|
+
expect(() => parseNotebookEvent(message)).toThrow(z.ZodError);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import NodeFs from "node:fs";
|
|
4
|
+
import NodeUrl from "node:url";
|
|
5
|
+
import { parseNotebookEvent, type NotebookEvent } from "../events";
|
|
6
|
+
|
|
7
|
+
/** Read the protocol's portable examples as transport messages. */
|
|
8
|
+
export function fixtureMessages(name: string) {
|
|
9
|
+
const path = new NodeUrl.URL(
|
|
10
|
+
`../../../../../protocols/host/fixtures/${name}`,
|
|
11
|
+
import.meta.url,
|
|
12
|
+
);
|
|
13
|
+
return NodeFs.readFileSync(path, "utf8")
|
|
14
|
+
.split(/\r?\n\r?\n/)
|
|
15
|
+
.flatMap((frame) => {
|
|
16
|
+
const event = /^event: (.+)$/m.exec(frame)?.[1];
|
|
17
|
+
if (!event) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const data = frame
|
|
21
|
+
.split(/\r?\n/)
|
|
22
|
+
.filter((line) => line.startsWith("data:"))
|
|
23
|
+
.map((line) => line.slice(5).trimStart())
|
|
24
|
+
.join("\n");
|
|
25
|
+
const payload: unknown = JSON.parse(data);
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
event,
|
|
29
|
+
id: /^id: (.+)$/m.exec(frame)?.[1],
|
|
30
|
+
data: payload,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Read the protocol's portable examples as decoded notebook events. */
|
|
37
|
+
export function fixtureEvents(name: string): NotebookEvent[] {
|
|
38
|
+
return fixtureMessages(name).map(parseNotebookEvent);
|
|
39
|
+
}
|