@marimo-team/islands 0.25.3-dev4 → 0.25.3-dev6
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/{ConnectedDataExplorerComponent-CNSxdKyJ.js → ConnectedDataExplorerComponent-CNMliVzw.js} +1 -1
- package/dist/{ErrorBoundary-CuU5CuVr.js → ErrorBoundary-Ca-Sa492.js} +1 -1
- package/dist/{abnfDiagram-VCTEODGH-DQDjNXt3.js → abnfDiagram-VCTEODGH-D-Ez91gV.js} +2 -2
- package/dist/{any-language-editor-B9REGlD9.js → any-language-editor-DZ_YTVus.js} +1 -1
- package/dist/{architectureDiagram-5GKGNRK7-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-CTpcNtO8.js} +1 -1
- package/dist/{chat-ui-BDpymeoe.js → chat-ui-0lKdOoQ3.js} +436 -437
- package/dist/{chunk-IMKFNOWR-CoIFEo-1.js → chunk-IMKFNOWR-DGxi-zeo.js} +1 -1
- package/dist/{chunk-SHT3W25Y-lHiGx3ZC.js → chunk-SHT3W25Y-CV9J3rAB.js} +1 -1
- package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-BL-9Fqgg.js} +1 -1
- package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-Cu_XMjDM.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Bt98lkkv.js → classDiagram-ZZMXUADV-BXXV13Hq.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-D9bfbcC3.js → classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js} +2 -2
- package/dist/{code-visibility-Lu0dkrFO.js → code-visibility-NGRjhbtA.js} +14 -14
- package/dist/{common-DiWGWDIM.js → common-B7_ZSg0P.js} +6426 -6256
- package/dist/{cynefinDiagram-5FMLGOSQ-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-CH96rwba.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CFZ8-35c.js → dagre-GXQ25YYZ-BD3PmpFY.js} +1 -1
- package/dist/{data-grid-overlay-editor-ga7ytgbj.js → data-grid-overlay-editor-B_cmrkBm.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-lCNQrW7d.js → diagram-S7CK7UJ4-BsSJJldB.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-muQtNZc4.js} +1 -1
- package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-CdhuZAqR.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-BD8mXjOj.js} +1 -1
- package/dist/{ebnfDiagram-PWID7BFC-D3NAyPFY.js → ebnfDiagram-PWID7BFC-BIN1sweE.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Tlu09_Nm.js → erDiagram-RLTQ6QDP-Ctp5cfoN.js} +1 -1
- package/dist/{error-banner-Da600cwz.js → error-banner-D-WCIiMt.js} +10 -5
- package/dist/{cells-C8iCYvLU.js → error-entries-DSvFWT_M.js} +2262 -2206
- package/dist/{feedback-button-DQA-pzxi.js → feedback-button-CTF2Nj9s.js} +115 -116
- package/dist/{flowDiagram-HODETNUW-CAYbpJmV.js → flowDiagram-HODETNUW-3EfLrlFb.js} +2 -2
- package/dist/{formats-BGh2thRc.js → formats-CeOmU1MU.js} +1 -1
- package/dist/{glide-data-editor-Bv75MH6g.js → glide-data-editor-BQHF9puC.js} +3 -3
- package/dist/{globals-EDQGFIvi.js → globals-Cj8iqMEp.js} +1 -1
- package/dist/{html-to-image-CZY1pIZH.js → html-to-image-B8s5SgGm.js} +23 -23
- package/dist/{infoDiagram-27XIBGKW-XrDxhr0x.js → infoDiagram-27XIBGKW-DipQoHxl.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CD1muzov.js → ishikawaDiagram-5VMMS53U-DgFMjSGz.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-Dg9W142_.js → kanban-definition-UXKFOSKX-w1ACVjgl.js} +1 -1
- package/dist/main.js +164 -163
- package/dist/{mermaid-CiAXjLpA.js → mermaid-CHo49Yv-.js} +33 -33
- package/dist/{mindmap-definition-YA3MSWOX-DgCidgYJ.js → mindmap-definition-YA3MSWOX-CrT0KSfX.js} +1 -1
- package/dist/{number-overlay-editor-BCP3MxhC.js → number-overlay-editor-DBcnm0SY.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-coOYYKB6.js → pegDiagram-XKGWAZYB-BqvywNJA.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BnXTc3bj.js → pieDiagram-E7YTZNPT-BfxJ_XCc.js} +1 -1
- package/dist/{process-output-e4jj9Ijt.js → process-output-CKRXyszb.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-8vFLVTPu.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-a_MxGvxe.js} +1 -1
- package/dist/{reveal-component-DO5BJB9l.js → reveal-component-DTBSuGkC.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-CXFMnxGh.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-DQeA_CwI.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CISYxma6.js → stateDiagram-v2-MP3YSRHH-BxxQucm1.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-Do6L-cYg.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-c8G5pGxC.js → swimlanesDiagram-VR7AAH4N-DUzPvSVU.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-D3c46WM5.js} +1 -1
- package/dist/{toDate-BNJf01u1.js → toDate-vIXljnsr.js} +39 -38
- package/dist/{vega-component-DG29TAB7.js → vega-component-DTG6zxaY.js} +3 -3
- package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-BSZD8JcZ.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-48cUWkfA.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-CswYgYeu.js} +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/editor/Output.tsx +9 -2
- 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 +372 -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/errors/__tests__/send-error-report-button.test.tsx +548 -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 +21 -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__/session.test.ts +50 -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/islands/__tests__/bootstrap.test.ts +26 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +193 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +9 -0
- package/src/mount.tsx +3 -3
- package/dist/error-entries-CzoYkIQZ.js +0 -56
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { Kbd } from "@/components/ui/kbd";
|
|
23
23
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
24
24
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
25
|
+
import { buildErrorReport } from "@/core/cells/error-report";
|
|
25
26
|
import type { CellId } from "@/core/cells/ids";
|
|
26
27
|
import { SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
27
28
|
import { toggleBreakpoint } from "@/core/codemirror/cells/debugger-state";
|
|
@@ -34,6 +35,7 @@ import { isWasm } from "@/core/wasm/utils";
|
|
|
34
35
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
35
36
|
import { sanitizeHtml } from "@/plugins/core/sanitize-html";
|
|
36
37
|
import { copyToClipboard } from "@/utils/copy";
|
|
38
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
37
39
|
import {
|
|
38
40
|
containsMangledLocal,
|
|
39
41
|
splitMangledLocals,
|
|
@@ -46,33 +48,45 @@ import {
|
|
|
46
48
|
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
47
49
|
import { AIFixButton, buildFixInChatPrompt } from "../errors/auto-fix";
|
|
48
50
|
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
51
|
+
import { SendErrorReportButton } from "../errors/send-error-report-button";
|
|
49
52
|
import { CellLinkTraceback } from "../links/cell-link";
|
|
50
53
|
import type { OnRefactorWithAI } from "../Output";
|
|
51
54
|
|
|
52
55
|
interface Props {
|
|
53
56
|
cellId: CellId | undefined;
|
|
54
57
|
traceback: string;
|
|
58
|
+
tracebackFormat?: "html" | "text";
|
|
59
|
+
code?: string;
|
|
55
60
|
onRefactorWithAI?: OnRefactorWithAI;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
/**
|
|
59
|
-
* List of errors due to violations of Marimo semantics.
|
|
60
|
-
*/
|
|
63
|
+
/** Render a traceback and actions for its captured execution. */
|
|
61
64
|
export const MarimoTracebackOutput = ({
|
|
62
65
|
onRefactorWithAI,
|
|
63
66
|
traceback,
|
|
67
|
+
tracebackFormat = "html",
|
|
64
68
|
cellId,
|
|
69
|
+
code,
|
|
65
70
|
}: Props): JSX.Element => {
|
|
66
|
-
const htmlTraceback =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const htmlTraceback =
|
|
72
|
+
tracebackFormat === "text" ? (
|
|
73
|
+
<pre>{traceback}</pre>
|
|
74
|
+
) : (
|
|
75
|
+
renderHTML({
|
|
76
|
+
html: traceback,
|
|
77
|
+
additionalReplacements: [
|
|
78
|
+
replaceTracebackFilenames,
|
|
79
|
+
replaceTracebackPrefix,
|
|
80
|
+
replaceMangledLocal,
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
);
|
|
74
84
|
|
|
75
|
-
const
|
|
85
|
+
const textTraceback =
|
|
86
|
+
tracebackFormat === "html"
|
|
87
|
+
? parseHtmlContent(sanitizeHtml(traceback))
|
|
88
|
+
: traceback;
|
|
89
|
+
const lastTracebackLine = lastLine(textTraceback);
|
|
76
90
|
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
77
91
|
const openAiAssistant = useOpenAiAssistant();
|
|
78
92
|
|
|
@@ -131,6 +145,13 @@ export const MarimoTracebackOutput = ({
|
|
|
131
145
|
</div>
|
|
132
146
|
)}
|
|
133
147
|
<div className="flex gap-2">
|
|
148
|
+
{cellId && lastTracebackLine && (
|
|
149
|
+
<SendErrorReportButton
|
|
150
|
+
getContent={() =>
|
|
151
|
+
buildErrorReport(cellId, { code, traceback, tracebackFormat })
|
|
152
|
+
}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
134
155
|
{showAIFix && (
|
|
135
156
|
<AIFixButton
|
|
136
157
|
tooltip="Fix with AI"
|
|
@@ -186,11 +207,7 @@ export const MarimoTracebackOutput = ({
|
|
|
186
207
|
</DropdownMenuItem>
|
|
187
208
|
<DropdownMenuItem
|
|
188
209
|
onClick={() => {
|
|
189
|
-
|
|
190
|
-
const div = document.createElement("div");
|
|
191
|
-
div.innerHTML = sanitizeHtml(traceback);
|
|
192
|
-
const textContent = div.textContent || "";
|
|
193
|
-
copyToClipboard(textContent);
|
|
210
|
+
copyToClipboard(textTraceback);
|
|
194
211
|
}}
|
|
195
212
|
>
|
|
196
213
|
<CopyIcon className="h-4 w-4 mr-2" />
|
|
@@ -205,9 +222,7 @@ export const MarimoTracebackOutput = ({
|
|
|
205
222
|
};
|
|
206
223
|
|
|
207
224
|
function lastLine(text: string): string {
|
|
208
|
-
const
|
|
209
|
-
el.innerHTML = sanitizeHtml(text);
|
|
210
|
-
const lines = el.textContent?.split("\n").filter(Boolean);
|
|
225
|
+
const lines = text.split("\n").filter(Boolean);
|
|
211
226
|
return lines?.at(-1) || "";
|
|
212
227
|
}
|
|
213
228
|
|
|
@@ -7,6 +7,24 @@ import {
|
|
|
7
7
|
TerminalBuffer,
|
|
8
8
|
} from "../ansi-reduce";
|
|
9
9
|
|
|
10
|
+
test("preserves source evidence through stateful output and serialization", () => {
|
|
11
|
+
const output = StatefulOutputMessage.create({
|
|
12
|
+
channel: "stderr",
|
|
13
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
14
|
+
timestamp: 1,
|
|
15
|
+
data: "<pre>ValueError:",
|
|
16
|
+
code: "raise ValueError('failed code')",
|
|
17
|
+
}).appendData(" failed code</pre>");
|
|
18
|
+
expect(output.code).toBe("raise ValueError('failed code')");
|
|
19
|
+
expect(output.toJSON()).toEqual({
|
|
20
|
+
channel: "stderr",
|
|
21
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
22
|
+
timestamp: 1,
|
|
23
|
+
data: "<pre>ValueError: failed code</pre>",
|
|
24
|
+
code: "raise ValueError('failed code')",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
10
28
|
describe("TerminalBuffer", () => {
|
|
11
29
|
test("writeChar writes single character", () => {
|
|
12
30
|
const buffer = new TerminalBuffer();
|
|
@@ -368,6 +368,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
368
368
|
public readonly mimetype: OutputMessage["mimetype"];
|
|
369
369
|
public readonly channel: OutputMessage["channel"];
|
|
370
370
|
public readonly timestamp: OutputMessage["timestamp"];
|
|
371
|
+
public readonly code: OutputMessage["code"];
|
|
371
372
|
private ansiReducer = new AnsiReducer();
|
|
372
373
|
private _data: string;
|
|
373
374
|
|
|
@@ -383,6 +384,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
383
384
|
message.channel,
|
|
384
385
|
message.timestamp,
|
|
385
386
|
ansiReducer,
|
|
387
|
+
message.code,
|
|
386
388
|
);
|
|
387
389
|
}
|
|
388
390
|
|
|
@@ -391,10 +393,12 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
391
393
|
channel: OutputMessage["channel"],
|
|
392
394
|
timestamp: OutputMessage["timestamp"],
|
|
393
395
|
ansiReducer: AnsiReducer,
|
|
396
|
+
code: OutputMessage["code"],
|
|
394
397
|
) {
|
|
395
398
|
this.mimetype = mimetype;
|
|
396
399
|
this.channel = channel;
|
|
397
400
|
this.timestamp = timestamp;
|
|
401
|
+
this.code = code;
|
|
398
402
|
this.ansiReducer = ansiReducer;
|
|
399
403
|
this._data = this.ansiReducer.render();
|
|
400
404
|
}
|
|
@@ -406,6 +410,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
406
410
|
this.channel,
|
|
407
411
|
this.timestamp,
|
|
408
412
|
this.ansiReducer,
|
|
413
|
+
this.code,
|
|
409
414
|
);
|
|
410
415
|
}
|
|
411
416
|
|
|
@@ -415,6 +420,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
415
420
|
channel: this.channel,
|
|
416
421
|
timestamp: this.timestamp,
|
|
417
422
|
data: this.data,
|
|
423
|
+
...(this.code === undefined ? {} : { code: this.code }),
|
|
418
424
|
};
|
|
419
425
|
}
|
|
420
426
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
|
|
4
|
+
import { act, render, renderHook } from "@testing-library/react";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { Toaster } from "@/components/ui/toaster";
|
|
7
|
+
import { clearToasts, toast, useToast } from "@/components/ui/use-toast";
|
|
8
|
+
import { agentToast } from "../toast";
|
|
9
|
+
|
|
10
|
+
describe("agent notifications", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.useFakeTimers();
|
|
13
|
+
clearToasts();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
act(() => {
|
|
18
|
+
clearToasts();
|
|
19
|
+
vi.runOnlyPendingTimers();
|
|
20
|
+
});
|
|
21
|
+
vi.useRealTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it.each([
|
|
25
|
+
{ variant: undefined, duration: 3000 },
|
|
26
|
+
{ variant: "danger" as const, duration: 5000 },
|
|
27
|
+
])("expires while the window is blurred: %j", ({ variant, duration }) => {
|
|
28
|
+
const { result } = renderHook(useToast);
|
|
29
|
+
render(<Toaster />);
|
|
30
|
+
act(() => agentToast({ title: "Agent notice", variant }));
|
|
31
|
+
act(() => window.dispatchEvent(new Event("blur")));
|
|
32
|
+
act(() => vi.advanceTimersByTime(duration - 1));
|
|
33
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
34
|
+
act(() => vi.advanceTimersByTime(1));
|
|
35
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("preserves pause behavior for ordinary notifications", () => {
|
|
39
|
+
const { result } = renderHook(useToast);
|
|
40
|
+
render(<Toaster />);
|
|
41
|
+
act(() => toast({ title: "Ordinary notice", duration: 3000 }));
|
|
42
|
+
act(() => window.dispatchEvent(new Event("blur")));
|
|
43
|
+
act(() => vi.advanceTimersByTime(5000));
|
|
44
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("does not let an older timer dismiss a newer notification", () => {
|
|
48
|
+
const { result } = renderHook(useToast);
|
|
49
|
+
act(() => agentToast({ title: "First" }));
|
|
50
|
+
act(() => vi.advanceTimersByTime(1000));
|
|
51
|
+
act(() => agentToast({ title: "Second" }));
|
|
52
|
+
act(() => vi.advanceTimersByTime(2000));
|
|
53
|
+
expect(result.current.toasts[0].title).toBe("Second");
|
|
54
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
55
|
+
act(() => vi.advanceTimersByTime(1000));
|
|
56
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { components } from "@marimo-team/marimo-api";
|
|
4
|
+
import { atom } from "jotai";
|
|
5
|
+
import { isConnectedAtom } from "@/core/network/connection";
|
|
6
|
+
|
|
7
|
+
export type Attachment = components["schemas"]["Attachment"];
|
|
8
|
+
|
|
9
|
+
export const attachmentsAtom = atom<Attachment[]>([]);
|
|
10
|
+
export const agentAttachmentsAtom = atom((get) => {
|
|
11
|
+
if (!get(isConnectedAtom)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return get(attachmentsAtom).filter(
|
|
15
|
+
(attachment) => attachment.kind === "agent",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export function attachmentLabel(attachment: Attachment): string {
|
|
20
|
+
return attachment.name || "Agent";
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { toast } from "@/components/ui/use-toast";
|
|
4
|
+
|
|
5
|
+
export function agentToast(
|
|
6
|
+
options: Pick<
|
|
7
|
+
Parameters<typeof toast>[0],
|
|
8
|
+
"title" | "variant" | "description"
|
|
9
|
+
>,
|
|
10
|
+
) {
|
|
11
|
+
const duration = options.variant === "danger" ? 5000 : 3000;
|
|
12
|
+
const { dismiss } = toast({ ...options, duration });
|
|
13
|
+
// Agent notices must also expire while the user focuses the agent terminal.
|
|
14
|
+
window.setTimeout(dismiss, duration);
|
|
15
|
+
}
|
|
@@ -789,6 +789,58 @@ describe("cell reducer", () => {
|
|
|
789
789
|
`);
|
|
790
790
|
});
|
|
791
791
|
|
|
792
|
+
it("keeps error evidence after an edit and a new queued run", () => {
|
|
793
|
+
const code = "raise ValueError('old failure')";
|
|
794
|
+
const output: OutputMessage = {
|
|
795
|
+
channel: "marimo-error",
|
|
796
|
+
mimetype: "application/vnd.marimo+error",
|
|
797
|
+
data: [
|
|
798
|
+
{
|
|
799
|
+
type: "exception",
|
|
800
|
+
msg: "old failure",
|
|
801
|
+
exception_type: "ValueError",
|
|
802
|
+
raising_cell: null,
|
|
803
|
+
traceback: "<pre>ValueError: old failure</pre>",
|
|
804
|
+
},
|
|
805
|
+
],
|
|
806
|
+
code,
|
|
807
|
+
timestamp: 1,
|
|
808
|
+
};
|
|
809
|
+
actions.handleCellMessage({
|
|
810
|
+
cell_id: firstCellId,
|
|
811
|
+
output,
|
|
812
|
+
console: {
|
|
813
|
+
channel: "stderr",
|
|
814
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
815
|
+
data: "<pre>ValueError: old failure</pre>",
|
|
816
|
+
code,
|
|
817
|
+
timestamp: 1,
|
|
818
|
+
},
|
|
819
|
+
status: "idle",
|
|
820
|
+
stale_inputs: null,
|
|
821
|
+
timestamp: 1,
|
|
822
|
+
});
|
|
823
|
+
actions.updateCellCode({
|
|
824
|
+
cellId: firstCellId,
|
|
825
|
+
code: "print('new code')",
|
|
826
|
+
formattingChange: false,
|
|
827
|
+
});
|
|
828
|
+
actions.prepareForRun({ cellId: firstCellId });
|
|
829
|
+
actions.handleCellMessage({
|
|
830
|
+
cell_id: firstCellId,
|
|
831
|
+
output: null,
|
|
832
|
+
console: null,
|
|
833
|
+
status: "queued",
|
|
834
|
+
stale_inputs: null,
|
|
835
|
+
timestamp: 2,
|
|
836
|
+
});
|
|
837
|
+
const runtime = state.cellRuntime[firstCellId];
|
|
838
|
+
expect(state.cellData[firstCellId].lastCodeRun).toBe("print('new code')");
|
|
839
|
+
expect(runtime.output).toEqual(output);
|
|
840
|
+
expect(runtime.consoleOutputs[0].code).toBe(code);
|
|
841
|
+
expect(runtime.consoleOutputs[0].data).toContain("old failure");
|
|
842
|
+
});
|
|
843
|
+
|
|
792
844
|
it("can run cell and receive cell messages", () => {
|
|
793
845
|
// HAPPY PATH
|
|
794
846
|
/////////////////
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { cellId } from "@/__tests__/branded";
|
|
6
|
+
import type { MarimoError } from "@/core/kernel/messages";
|
|
7
|
+
import { buildErrorReport, ERROR_MESSAGE_INSTRUCTIONS } from "../error-report";
|
|
8
|
+
|
|
9
|
+
const CELL_ID = cellId("failed-cell");
|
|
10
|
+
|
|
11
|
+
describe("buildErrorReport", () => {
|
|
12
|
+
it("preserves plain traceback text without treating angle brackets as HTML", () => {
|
|
13
|
+
const traceback =
|
|
14
|
+
' File "<cell>", line 1\nValueError: expected <value> & got <other>';
|
|
15
|
+
expect(
|
|
16
|
+
buildErrorReport(CELL_ID, { traceback, tracebackFormat: "text" }).output,
|
|
17
|
+
).toEqual({
|
|
18
|
+
type: "error",
|
|
19
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
20
|
+
message: "ValueError: expected <value> & got <other>",
|
|
21
|
+
traceback,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
it("owns the instruction to repair the live notebook", () => {
|
|
25
|
+
const report = buildErrorReport(CELL_ID, {
|
|
26
|
+
errors: [{ type: "syntax", msg: "invalid code" }],
|
|
27
|
+
});
|
|
28
|
+
expect(report.output.instructions).toEqual("Fix this notebook error.");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("keeps repair intent visible before oversized evidence", () => {
|
|
32
|
+
const report = buildErrorReport(CELL_ID, {
|
|
33
|
+
code: "# long code\n".repeat(10_000),
|
|
34
|
+
errors: [
|
|
35
|
+
{
|
|
36
|
+
type: "exception",
|
|
37
|
+
exception_type: "ValueError",
|
|
38
|
+
msg: "long error ".repeat(10_000),
|
|
39
|
+
raising_cell: null,
|
|
40
|
+
traceback: `<pre>Traceback\n${"long frame\n".repeat(10_000)}</pre>`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
const serialized = JSON.stringify(report);
|
|
45
|
+
|
|
46
|
+
expect(serialized.length).toBeGreaterThan(8192);
|
|
47
|
+
expect(serialized.slice(0, 256)).toContain(
|
|
48
|
+
'"instructions":"Fix this notebook error."',
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("retains every message and complete traceback without display limits", () => {
|
|
53
|
+
const traceback = `Traceback\n${"long frame\n".repeat(10_000)}ValueError: bad <value>`;
|
|
54
|
+
const errors: MarimoError[] = [
|
|
55
|
+
{
|
|
56
|
+
type: "exception",
|
|
57
|
+
msg: "bad <value>",
|
|
58
|
+
exception_type: "ValueError",
|
|
59
|
+
raising_cell: null,
|
|
60
|
+
traceback: `<pre>${traceback.replaceAll("<", "<").replaceAll(">", ">")}</pre>`,
|
|
61
|
+
},
|
|
62
|
+
{ type: "syntax", msg: "another error" },
|
|
63
|
+
];
|
|
64
|
+
expect(buildErrorReport(CELL_ID, { code: "failed()", errors })).toEqual({
|
|
65
|
+
type: "cell-output",
|
|
66
|
+
cell_id: CELL_ID,
|
|
67
|
+
code: "failed()",
|
|
68
|
+
output: {
|
|
69
|
+
type: "error",
|
|
70
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
71
|
+
message: "bad <value>\nanother error",
|
|
72
|
+
traceback,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("leaves source unknown for a restored legacy exception", () => {
|
|
78
|
+
expect(
|
|
79
|
+
buildErrorReport(CELL_ID, {
|
|
80
|
+
errors: [
|
|
81
|
+
{
|
|
82
|
+
type: "exception",
|
|
83
|
+
exception_type: "exception",
|
|
84
|
+
msg: "legacy error",
|
|
85
|
+
raising_cell: null,
|
|
86
|
+
traceback: null,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
}),
|
|
90
|
+
).toEqual({
|
|
91
|
+
type: "cell-output",
|
|
92
|
+
cell_id: CELL_ID,
|
|
93
|
+
output: {
|
|
94
|
+
type: "error",
|
|
95
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
96
|
+
message: "legacy error",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("preserves empty captured code", () => {
|
|
102
|
+
expect(
|
|
103
|
+
buildErrorReport(CELL_ID, {
|
|
104
|
+
code: "",
|
|
105
|
+
errors: [{ type: "syntax", msg: "invalid code" }],
|
|
106
|
+
}),
|
|
107
|
+
).toEqual({
|
|
108
|
+
type: "cell-output",
|
|
109
|
+
cell_id: CELL_ID,
|
|
110
|
+
code: "",
|
|
111
|
+
output: {
|
|
112
|
+
type: "error",
|
|
113
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
114
|
+
message: "invalid code",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("keeps separate structured tracebacks in order", () => {
|
|
120
|
+
const errors: MarimoError[] = ["first", "second"].map((message) => ({
|
|
121
|
+
type: "exception",
|
|
122
|
+
exception_type: "ValueError",
|
|
123
|
+
raising_cell: null,
|
|
124
|
+
msg: message,
|
|
125
|
+
traceback: `<pre>Traceback\nValueError: ${message}</pre>`,
|
|
126
|
+
}));
|
|
127
|
+
expect(buildErrorReport(CELL_ID, { errors })).toEqual({
|
|
128
|
+
type: "cell-output",
|
|
129
|
+
cell_id: CELL_ID,
|
|
130
|
+
output: {
|
|
131
|
+
type: "error",
|
|
132
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
133
|
+
message: "first\nsecond",
|
|
134
|
+
traceback:
|
|
135
|
+
"Traceback\nValueError: first\n\nTraceback\nValueError: second",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -174,8 +174,11 @@ describe("notebookStateFromSession", () => {
|
|
|
174
174
|
channel: "marimo-error",
|
|
175
175
|
data: [
|
|
176
176
|
{
|
|
177
|
-
type: "
|
|
177
|
+
type: "exception",
|
|
178
|
+
exception_type: "ValueError",
|
|
178
179
|
msg: "Something went wrong",
|
|
180
|
+
raising_cell: null,
|
|
181
|
+
traceback: "Traceback line 1",
|
|
179
182
|
},
|
|
180
183
|
],
|
|
181
184
|
mimetype: "application/vnd.marimo+error",
|
|
@@ -183,6 +186,52 @@ describe("notebookStateFromSession", () => {
|
|
|
183
186
|
});
|
|
184
187
|
});
|
|
185
188
|
|
|
189
|
+
it("restores matching code from the session", () => {
|
|
190
|
+
const code = "raise ValueError('cached failure')";
|
|
191
|
+
const session = createSession([
|
|
192
|
+
createSessionCell(
|
|
193
|
+
"cell-1",
|
|
194
|
+
[
|
|
195
|
+
{
|
|
196
|
+
type: "error",
|
|
197
|
+
ename: "ValueError",
|
|
198
|
+
evalue: "cached failure",
|
|
199
|
+
traceback: ["Traceback\n", "ValueError: cached failure"],
|
|
200
|
+
code,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
{
|
|
205
|
+
type: "stream",
|
|
206
|
+
name: "stderr",
|
|
207
|
+
text: "ValueError: cached <failure>",
|
|
208
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
209
|
+
traceback_format: "text",
|
|
210
|
+
code,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
),
|
|
214
|
+
]);
|
|
215
|
+
const state = notebookStateFromSession(session, null);
|
|
216
|
+
invariant(state, "state is null");
|
|
217
|
+
expect(state.cellRuntime[CELL_1].output).toMatchObject({
|
|
218
|
+
code,
|
|
219
|
+
data: [
|
|
220
|
+
{
|
|
221
|
+
type: "exception",
|
|
222
|
+
exception_type: "ValueError",
|
|
223
|
+
msg: "cached failure",
|
|
224
|
+
traceback: "Traceback\nValueError: cached failure",
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
expect(state.cellRuntime[CELL_1].consoleOutputs[0].code).toBe(code);
|
|
229
|
+
expect(state.cellRuntime[CELL_1].consoleOutputs[0]).toMatchObject({
|
|
230
|
+
traceback_format: "text",
|
|
231
|
+
data: "ValueError: cached <failure>",
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
186
235
|
it("handles data output in session cell", () => {
|
|
187
236
|
const dataOutput = {
|
|
188
237
|
type: "data" as const,
|
|
@@ -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);
|