@marimo-team/islands 0.25.3-dev5 → 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/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-Ca-Sa492.js} +1 -1
- package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-NGRjhbtA.js} +1 -1
- package/dist/{common-mzWw4FdI.js → common-B7_ZSg0P.js} +4755 -4744
- package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B_cmrkBm.js} +1 -1
- package/dist/{feedback-button-BcT1FBud.js → feedback-button-CTF2Nj9s.js} +2 -2
- package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BQHF9puC.js} +3 -3
- package/dist/{globals-jRrLCkrP.js → globals-Cj8iqMEp.js} +1 -1
- package/dist/main.js +6 -6
- package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-DBcnm0SY.js} +1 -1
- package/dist/{reveal-component-79ez8-D5.js → reveal-component-DTBSuGkC.js} +2 -2
- package/dist/style.css +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 +5 -1
- 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/wrapper/__tests__/attachment-status.test.tsx +267 -62
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +127 -13
- package/src/components/editor/errors/send-error-report-button.tsx +10 -7
- package/src/components/editor/output/MarimoTracebackOutput.tsx +27 -21
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/error-report.test.ts +53 -3
- package/src/core/cells/__tests__/session.test.ts +18 -3
- package/src/core/cells/error-report.ts +14 -6
- package/src/core/cells/session.ts +19 -1
- package/src/core/config/pair.ts +5 -3
- package/src/mount.tsx +3 -3
|
@@ -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,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
|
+
}
|
|
@@ -4,11 +4,51 @@
|
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
5
|
import { cellId } from "@/__tests__/branded";
|
|
6
6
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
7
|
-
import { buildErrorReport } from "../error-report";
|
|
7
|
+
import { buildErrorReport, ERROR_MESSAGE_INSTRUCTIONS } from "../error-report";
|
|
8
8
|
|
|
9
9
|
const CELL_ID = cellId("failed-cell");
|
|
10
10
|
|
|
11
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
|
+
|
|
12
52
|
it("retains every message and complete traceback without display limits", () => {
|
|
13
53
|
const traceback = `Traceback\n${"long frame\n".repeat(10_000)}ValueError: bad <value>`;
|
|
14
54
|
const errors: MarimoError[] = [
|
|
@@ -27,6 +67,7 @@ describe("buildErrorReport", () => {
|
|
|
27
67
|
code: "failed()",
|
|
28
68
|
output: {
|
|
29
69
|
type: "error",
|
|
70
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
30
71
|
message: "bad <value>\nanother error",
|
|
31
72
|
traceback,
|
|
32
73
|
},
|
|
@@ -49,7 +90,11 @@ describe("buildErrorReport", () => {
|
|
|
49
90
|
).toEqual({
|
|
50
91
|
type: "cell-output",
|
|
51
92
|
cell_id: CELL_ID,
|
|
52
|
-
output: {
|
|
93
|
+
output: {
|
|
94
|
+
type: "error",
|
|
95
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
96
|
+
message: "legacy error",
|
|
97
|
+
},
|
|
53
98
|
});
|
|
54
99
|
});
|
|
55
100
|
|
|
@@ -63,7 +108,11 @@ describe("buildErrorReport", () => {
|
|
|
63
108
|
type: "cell-output",
|
|
64
109
|
cell_id: CELL_ID,
|
|
65
110
|
code: "",
|
|
66
|
-
output: {
|
|
111
|
+
output: {
|
|
112
|
+
type: "error",
|
|
113
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
114
|
+
message: "invalid code",
|
|
115
|
+
},
|
|
67
116
|
});
|
|
68
117
|
});
|
|
69
118
|
|
|
@@ -80,6 +129,7 @@ describe("buildErrorReport", () => {
|
|
|
80
129
|
cell_id: CELL_ID,
|
|
81
130
|
output: {
|
|
82
131
|
type: "error",
|
|
132
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
83
133
|
message: "first\nsecond",
|
|
84
134
|
traceback:
|
|
85
135
|
"Traceback\nValueError: first\n\nTraceback\nValueError: second",
|
|
@@ -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",
|
|
@@ -201,8 +204,9 @@ describe("notebookStateFromSession", () => {
|
|
|
201
204
|
{
|
|
202
205
|
type: "stream",
|
|
203
206
|
name: "stderr",
|
|
204
|
-
text: "
|
|
207
|
+
text: "ValueError: cached <failure>",
|
|
205
208
|
mimetype: "application/vnd.marimo+traceback",
|
|
209
|
+
traceback_format: "text",
|
|
206
210
|
code,
|
|
207
211
|
},
|
|
208
212
|
],
|
|
@@ -212,9 +216,20 @@ describe("notebookStateFromSession", () => {
|
|
|
212
216
|
invariant(state, "state is null");
|
|
213
217
|
expect(state.cellRuntime[CELL_1].output).toMatchObject({
|
|
214
218
|
code,
|
|
215
|
-
data: [
|
|
219
|
+
data: [
|
|
220
|
+
{
|
|
221
|
+
type: "exception",
|
|
222
|
+
exception_type: "ValueError",
|
|
223
|
+
msg: "cached failure",
|
|
224
|
+
traceback: "Traceback\nValueError: cached failure",
|
|
225
|
+
},
|
|
226
|
+
],
|
|
216
227
|
});
|
|
217
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
|
+
});
|
|
218
233
|
});
|
|
219
234
|
|
|
220
235
|
it("handles data output in session cell", () => {
|
|
@@ -6,26 +6,29 @@ import { sanitizeHtml } from "@/plugins/core/sanitize-html";
|
|
|
6
6
|
import { parseHtmlContent } from "@/utils/dom";
|
|
7
7
|
import type { CellId } from "./ids";
|
|
8
8
|
|
|
9
|
-
export interface
|
|
9
|
+
export interface CellOutputMessage<Output> {
|
|
10
10
|
type: "cell-output";
|
|
11
11
|
cell_id: CellId;
|
|
12
12
|
code?: string;
|
|
13
13
|
output: Output;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export interface
|
|
16
|
+
export interface ErrorMessageOutput {
|
|
17
17
|
type: "error";
|
|
18
|
+
instructions: string;
|
|
18
19
|
message: string;
|
|
19
20
|
traceback?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
export const ERROR_MESSAGE_INSTRUCTIONS = "Fix this notebook error.";
|
|
24
|
+
|
|
22
25
|
export function buildErrorReport(
|
|
23
26
|
cellId: CellId,
|
|
24
27
|
evidence: { code?: string } & (
|
|
25
28
|
| { errors: MarimoError[] }
|
|
26
|
-
| { traceback: string }
|
|
29
|
+
| { traceback: string; tracebackFormat: "html" | "text" }
|
|
27
30
|
),
|
|
28
|
-
):
|
|
31
|
+
): CellOutputMessage<ErrorMessageOutput> {
|
|
29
32
|
let message: string;
|
|
30
33
|
let traceback: string;
|
|
31
34
|
if ("errors" in evidence) {
|
|
@@ -38,18 +41,23 @@ export function buildErrorReport(
|
|
|
38
41
|
)
|
|
39
42
|
.join("\n\n");
|
|
40
43
|
} else {
|
|
41
|
-
traceback =
|
|
44
|
+
traceback =
|
|
45
|
+
evidence.tracebackFormat === "html"
|
|
46
|
+
? parseHtmlContent(sanitizeHtml(evidence.traceback))
|
|
47
|
+
: evidence.traceback;
|
|
42
48
|
message = traceback.split("\n").findLast((line) => line.length > 0) ?? "";
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
// Put repair intent before evidence so it survives prefix truncation.
|
|
45
52
|
return {
|
|
46
53
|
type: "cell-output",
|
|
47
54
|
cell_id: cellId,
|
|
48
|
-
...(evidence.code === undefined ? {} : { code: evidence.code }),
|
|
49
55
|
output: {
|
|
50
56
|
type: "error",
|
|
57
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
51
58
|
message,
|
|
52
59
|
...(traceback ? { traceback } : {}),
|
|
53
60
|
},
|
|
61
|
+
...(evidence.code === undefined ? {} : { code: evidence.code }),
|
|
54
62
|
};
|
|
55
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",
|
|
@@ -240,6 +252,9 @@ function createCellRuntimeFromSession(
|
|
|
240
252
|
channel: "media",
|
|
241
253
|
data: consoleOutput.data,
|
|
242
254
|
mimetype: consoleOutput.mimetype,
|
|
255
|
+
...(consoleOutput.traceback_format === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: { traceback_format: consoleOutput.traceback_format }),
|
|
243
258
|
timestamp: DEFAULT_TIMESTAMP,
|
|
244
259
|
...(consoleOutput.code === undefined
|
|
245
260
|
? {}
|
|
@@ -251,6 +266,9 @@ function createCellRuntimeFromSession(
|
|
|
251
266
|
channel: consoleOutput.name === "stderr" ? "stderr" : "stdout",
|
|
252
267
|
data: consoleOutput.text,
|
|
253
268
|
mimetype: consoleOutput.mimetype ?? "text/plain",
|
|
269
|
+
...(consoleOutput.traceback_format === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { traceback_format: consoleOutput.traceback_format }),
|
|
254
272
|
timestamp: DEFAULT_TIMESTAMP,
|
|
255
273
|
...(consoleOutput.code === undefined
|
|
256
274
|
? {}
|
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/mount.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
userConfigAtom,
|
|
11
11
|
} from "@/core/config/config";
|
|
12
12
|
import { KnownQueryParams } from "@/core/constants";
|
|
13
|
-
import {
|
|
13
|
+
import { pairConfigAtom, pairConfigSchema } from "@/core/config/pair";
|
|
14
14
|
import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
|
|
15
15
|
import { getMarimoCode } from "@/core/meta/globals";
|
|
16
16
|
import {
|
|
@@ -209,7 +209,7 @@ const mountOptionsSchema = z.object({
|
|
|
209
209
|
* marimo config overrides
|
|
210
210
|
*/
|
|
211
211
|
configOverrides: passthroughObject,
|
|
212
|
-
|
|
212
|
+
pairConfig: pairConfigSchema.optional(),
|
|
213
213
|
/**
|
|
214
214
|
* marimo app config
|
|
215
215
|
*/
|
|
@@ -347,7 +347,7 @@ function initStore(options: unknown) {
|
|
|
347
347
|
);
|
|
348
348
|
store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
|
|
349
349
|
store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
|
|
350
|
-
store.set(
|
|
350
|
+
store.set(pairConfigAtom, parsedOptions.data.pairConfig);
|
|
351
351
|
|
|
352
352
|
// Runtime config
|
|
353
353
|
if (parsedOptions.data.runtimeConfig.length > 0) {
|