@marimo-team/frontend 0.25.2-dev38 → 0.25.2-dev39
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-CRNbTOIs.js → CellStatus-C24XF8TO.js} +1 -1
- package/dist/assets/{ErrorBoundary-CHPy195L.js → ErrorBoundary-B0A5dBH2.js} +2 -2
- package/dist/assets/{JsonOutput-BdpXVhxV.js → JsonOutput-Cx3AQC7R.js} +3 -3
- package/dist/assets/{MarimoErrorOutput-K6C_dFmm.js → MarimoErrorOutput-CMVdJiaK.js} +1 -1
- package/dist/assets/{RenderHTML-ClrDrx3F.js → RenderHTML-DjS-DdUL.js} +1 -1
- package/dist/assets/{RunButton-BA02OhJe.js → RunButton-DhhAS4nD.js} +1 -1
- package/dist/assets/{add-cell-with-ai-sgoMoiI9.js → add-cell-with-ai-BPu32863.js} +1 -1
- package/dist/assets/{add-connection-dialog-BOV2U067.js → add-connection-dialog-DZj5amkT.js} +1 -1
- package/dist/assets/{agent-panel-CRzjBUdB.js → agent-panel-DKJ-xqqe.js} +1 -1
- package/dist/assets/ai-model-dropdown-D_5kXqGw.js +5 -0
- package/dist/assets/{api-C5RyTgDk.js → api-DMoBWmQT.js} +1 -1
- package/dist/assets/{app-config-button-BAQBx4II.js → app-config-button-BBBqdwwv.js} +1 -1
- package/dist/assets/{blob-BKNVs7hM.js → blob-mVTRazkn.js} +1 -1
- package/dist/assets/{cell-editor-CEABNkmY.js → cell-editor-BNAwiIir.js} +2 -2
- package/dist/assets/cell-link-BFoYN4G4.js +1 -0
- package/dist/assets/{cells-DIjn-wIE.js → cells-Bboj_mzH.js} +1 -1
- package/dist/assets/{chat-display-Bpl-2umz.js → chat-display-BclykCDT.js} +1 -1
- package/dist/assets/{chat-panel-C6NjK6Ig.js → chat-panel-DvGZ0ahc.js} +1 -1
- package/dist/assets/{chat-ui-C5Ulv8Sh.js → chat-ui-B1xpc2Iq.js} +1 -1
- package/dist/assets/{code-visibility-DbW2QNho.js → code-visibility-DStVsztB.js} +1 -1
- package/dist/assets/{column-preview-CZ2TL09N.js → column-preview-B_HNw6Xa.js} +1 -1
- package/dist/assets/{command-palette-DaHlZzjG.js → command-palette-B8pPDrL3.js} +1 -1
- package/dist/assets/{common-BQbHQAab.js → common-CAoKKAj9.js} +1 -1
- package/dist/assets/{components-DJ4W2IzC.js → components-0tF5D2G2.js} +1 -1
- package/dist/assets/{components-Dt0cuD4C.js → components-Scfns93C.js} +1 -1
- package/dist/assets/{config-D7CP5wR5.js → config-VEBoxYs0.js} +1 -1
- package/dist/assets/{datasource-Ccdyd-O2.js → datasource-DXrVF3A7.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DIQpBTOZ.js → dependency-graph-panel-BKerKbW0.js} +1 -1
- package/dist/assets/{documentation-panel-DTDeaE2n.js → documentation-panel-BBqGQovG.js} +1 -1
- package/dist/assets/{download-DBXnO5qS.js → download-CsvgDpk8.js} +1 -1
- package/dist/assets/{edit-page-HpmTxp7Y.js → edit-page-J1s9SrSW.js} +6 -6
- package/dist/assets/{error-entries-BB7FNt10.js → error-entries-EhYWazUP.js} +1 -1
- package/dist/assets/{error-panel-0aG3Tdrl.js → error-panel-BBwaFurl.js} +1 -1
- package/dist/assets/feedback-button-BXARphau.js +1 -0
- package/dist/assets/{feedback-button-Ck0Mp9so.js → feedback-button-D4FkbdpF.js} +1 -1
- package/dist/assets/{file-explorer-panel-Dqxfg5xm.js → file-explorer-panel-DOuYHyNb.js} +1 -1
- package/dist/assets/{file-name-input-CUxuxMjv.js → file-name-input-oZ_OFDwA.js} +1 -1
- package/dist/assets/{floating-outline-yJVtlHMv.js → floating-outline-CuOLwf13.js} +1 -1
- package/dist/assets/{gallery-page-D70P7AOr.js → gallery-page-DnObr5r7.js} +1 -1
- package/dist/assets/{glide-data-editor-YqrbKh4R.js → glide-data-editor-SRFol8N3.js} +1 -1
- package/dist/assets/{home-page-Dnv5d4Ek.js → home-page-CrRCUhHM.js} +1 -1
- package/dist/assets/{hooks-ZWyI6fMV.js → hooks-DDexlabY.js} +1 -1
- package/dist/assets/{index-BUQP_BOv.js → index-h-GQYxNi.js} +6 -6
- package/dist/assets/kiosk-mode-CEKB1dXx.js +1 -0
- package/dist/assets/{layout-C8pvkupF.js → layout-4WXAZ1eW.js} +4 -4
- package/dist/assets/links-Bc4DEgui.js +1 -0
- package/dist/assets/{logs-panel-Bdb_qtEg.js → logs-panel-BYUR5w2M.js} +1 -1
- package/dist/assets/{markdown-renderer-DT34-_9e.js → markdown-renderer-BCB0NTfn.js} +1 -1
- package/dist/assets/{name-cell-input-E95KyKqv.js → name-cell-input-C-myEA1c.js} +1 -1
- package/dist/assets/{outline-panel-1AnW7CLy.js → outline-panel-CM2lVI29.js} +1 -1
- package/dist/assets/{packages-panel-t-WIjOjO.js → packages-panel-BwJc45sJ.js} +1 -1
- package/dist/assets/{pair-DrvWpc2_.js → pair-_FSgYVX4.js} +1 -1
- package/dist/assets/{panel-accordion-state-_r3T-AGo.js → panel-accordion-state-XgB7A-Pf.js} +1 -1
- package/dist/assets/panels-SK_rec-s.js +4 -0
- package/dist/assets/{process-output-gyG2tEQt.js → process-output-Bpzcx6ny.js} +1 -1
- package/dist/assets/{readonly-code-display-DVP0M5ji.js → readonly-code-display-DykeAqVP.js} +1 -1
- package/dist/assets/{readonly-python-code-CPhK2DqE.js → readonly-python-code-NhpM1k7Y.js} +1 -1
- package/dist/assets/{reveal-component-CICzazBY.js → reveal-component-DvtSwOtf.js} +1 -1
- package/dist/assets/run-page-DtMQZ9fB.js +1 -0
- package/dist/assets/{sandbox-panel-CcKUdNwc.js → sandbox-panel-BTJZoLQf.js} +1 -1
- package/dist/assets/{scratchpad-panel-Bju5WCrd.js → scratchpad-panel-DAR9LXOp.js} +1 -1
- package/dist/assets/{session-ztCnZJHI.js → session-D7QkhMHj.js} +1 -1
- package/dist/assets/{session-panel-BhoASF58.js → session-panel-BFDg1epj.js} +1 -1
- package/dist/assets/{snippets-panel-BGO2n9mY.js → snippets-panel-D5jPm-dQ.js} +1 -1
- package/dist/assets/{state-OJSVjDAX.js → state-C2hpK3R0.js} +1 -1
- package/dist/assets/{state-0X7um_9u.js → state-ClA31EHO.js} +3 -3
- package/dist/assets/{terminal-BaYFckM3.js → terminal-DgXAZSkT.js} +1 -1
- package/dist/assets/{tracing-BwkQnRsr.js → tracing-CDAILi-O.js} +1 -1
- package/dist/assets/{tracing-panel-Bari_aJY.js → tracing-panel-Clom4CyL.js} +2 -2
- package/dist/assets/{tree-actions-DpLu3KP0.js → tree-actions-gTQxtJX8.js} +1 -1
- package/dist/assets/{useAddCell-DCbFUjci.js → useAddCell-Cz3QoshN.js} +1 -1
- package/dist/assets/{useBoolean-C44TyhOc.js → useBoolean-iz9l8Cuk.js} +1 -1
- package/dist/assets/{useCellActionButton-Dd3_WkrO.js → useCellActionButton-BMuXaKhK.js} +1 -1
- package/dist/assets/{useDeleteCell-BJ6lgiBJ.js → useDeleteCell-DieDZaIf.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-CyNU7lXE.js → useDependencyPanelTab-B8hZ99Ij.js} +1 -1
- package/dist/assets/{useInstallPackage-DO1N1lCX.js → useInstallPackage-DsbNVe8R.js} +1 -1
- package/dist/assets/useNotebookActions-9w4zO1hI.js +1 -0
- package/dist/assets/{useRunCells-k0IpRLOw.js → useRunCells-BtJbnrtV.js} +1 -1
- package/dist/assets/{useSplitCell-AE67LfQn.js → useSplitCell-Bb3bloRI.js} +1 -1
- package/dist/assets/{vega-component-Bw0srfks.js → vega-component-CyO7yyQw.js} +1 -1
- package/dist/index.html +25 -26
- package/package.json +1 -1
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +32 -46
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +29 -40
- package/src/components/editor/actions/pair-with-agent-commands.ts +3 -4
- package/src/components/editor/actions/pair-with-agent-modal.tsx +4 -7
- package/src/core/config/pair.ts +0 -1
- package/src/core/kernel/session.ts +9 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +10 -1
- package/dist/assets/ai-model-dropdown-B547mMnB.js +0 -5
- package/dist/assets/cell-link-CEmc-Cr5.js +0 -1
- package/dist/assets/feedback-button-DFFJadk1.js +0 -1
- package/dist/assets/filename-BlDwe-3Z.js +0 -1
- package/dist/assets/kiosk-mode-Cvh6wszH.js +0 -1
- package/dist/assets/links-Dz_7YAlE.js +0 -1
- package/dist/assets/panels-HoIP3cDr.js +0 -4
- package/dist/assets/run-page-DWUXQLtA.js +0 -1
- package/dist/assets/useNotebookActions-D7J_QMJo.js +0 -1
|
@@ -154,63 +154,54 @@ describe("getTerminalCommand", () => {
|
|
|
154
154
|
).toBe(
|
|
155
155
|
String.raw`${cli} "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
156
156
|
--url http://localhost:8000 \
|
|
157
|
-
--file notebooks/example.py \
|
|
158
157
|
--session s_ab12cd \
|
|
159
158
|
--with-token)"`,
|
|
160
159
|
);
|
|
161
160
|
},
|
|
162
161
|
);
|
|
163
162
|
|
|
164
|
-
it("includes the url and
|
|
163
|
+
it("includes the url and agent flag for each agent", () => {
|
|
165
164
|
expect(getTerminalCommand("claude", CONNECTION, false)).toBe(
|
|
166
|
-
`claude "$(uv run marimo pair prompt --url http://localhost:8000 --
|
|
165
|
+
`claude "$(uv run marimo pair prompt --url http://localhost:8000 --claude)"`,
|
|
167
166
|
);
|
|
168
167
|
expect(getTerminalCommand("codex", CONNECTION, false)).toBe(
|
|
169
|
-
`codex "$(uv run marimo pair prompt --url http://localhost:8000 --
|
|
168
|
+
`codex "$(uv run marimo pair prompt --url http://localhost:8000 --codex)"`,
|
|
170
169
|
);
|
|
171
170
|
expect(getTerminalCommand("opencode", CONNECTION, false)).toBe(
|
|
172
|
-
`opencode --prompt "$(uv run marimo pair prompt --url http://localhost:8000 --
|
|
171
|
+
`opencode --prompt "$(uv run marimo pair prompt --url http://localhost:8000 --opencode)"`,
|
|
173
172
|
);
|
|
174
173
|
});
|
|
175
174
|
|
|
176
|
-
it("
|
|
177
|
-
const command = getTerminalCommand(
|
|
178
|
-
"claude",
|
|
179
|
-
CONNECTION_WITHOUT_FILE,
|
|
180
|
-
false,
|
|
181
|
-
);
|
|
175
|
+
it("never passes a file to marimo pair prompt", () => {
|
|
176
|
+
const command = getTerminalCommand("claude", CONNECTION, false);
|
|
182
177
|
expect(command).not.toContain("--file");
|
|
183
|
-
expect(command).not.toContain("
|
|
178
|
+
expect(command).not.toContain("notebooks/example.py");
|
|
184
179
|
});
|
|
185
180
|
|
|
186
|
-
it("
|
|
187
|
-
|
|
188
|
-
"
|
|
189
|
-
{ url: "http://host:8000?auth=a&b", file: "notebook.py" },
|
|
190
|
-
false,
|
|
181
|
+
it("omits the session outside the preview", () => {
|
|
182
|
+
expect(getTerminalCommand("claude", CONNECTION, false)).not.toContain(
|
|
183
|
+
"--session",
|
|
191
184
|
);
|
|
192
|
-
expect(command).toContain("--url 'http://host:8000?auth=a&b'");
|
|
193
185
|
});
|
|
194
186
|
|
|
195
|
-
it
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
])("shell-escapes file path %s", (file, expected) => {
|
|
187
|
+
it("omits the session when it is unknown", () => {
|
|
188
|
+
expect(
|
|
189
|
+
getTerminalCommand(
|
|
190
|
+
"claude",
|
|
191
|
+
CONNECTION_WITHOUT_FILE,
|
|
192
|
+
false,
|
|
193
|
+
PAIR_PREVIEW,
|
|
194
|
+
),
|
|
195
|
+
).not.toContain("--session");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("shell-escapes a url containing metacharacters", () => {
|
|
208
199
|
const command = getTerminalCommand(
|
|
209
200
|
"claude",
|
|
210
|
-
{ url:
|
|
201
|
+
{ url: "http://host:8000?auth=a&b" },
|
|
211
202
|
false,
|
|
212
203
|
);
|
|
213
|
-
expect(command).toContain(
|
|
204
|
+
expect(command).toContain("--url 'http://host:8000?auth=a&b'");
|
|
214
205
|
});
|
|
215
206
|
|
|
216
207
|
it("adds --with-token before the agent flag when requested", () => {
|
|
@@ -230,23 +221,22 @@ describe("getRawPrompt", () => {
|
|
|
230
221
|
expect(
|
|
231
222
|
getRawPrompt(
|
|
232
223
|
{
|
|
233
|
-
url: "http://host/{
|
|
234
|
-
|
|
235
|
-
session: "{command}",
|
|
224
|
+
url: "http://host/{session}",
|
|
225
|
+
session: "{command}/it's",
|
|
236
226
|
},
|
|
237
|
-
"tok'en {
|
|
227
|
+
"tok'en {session} $&",
|
|
238
228
|
{
|
|
239
229
|
...PAIR_PREVIEW,
|
|
240
230
|
command: "custom marimo",
|
|
241
231
|
templates: {
|
|
242
232
|
...PAIR_PREVIEW.templates,
|
|
243
|
-
prompt: "{command}\n{url}\n{
|
|
233
|
+
prompt: "{command}\n{url}\n{session}{authentication}",
|
|
244
234
|
},
|
|
245
235
|
},
|
|
246
236
|
),
|
|
247
237
|
).toBe(
|
|
248
|
-
"custom marimo\nhttp://host/{
|
|
249
|
-
`\n\nFor authenticated Pair commands, set \`export MARIMO_TOKEN='tok'"'"'en {
|
|
238
|
+
"custom marimo\nhttp://host/{session}\nSession: {command}/it's\n" +
|
|
239
|
+
`\n\nFor authenticated Pair commands, set \`export MARIMO_TOKEN='tok'"'"'en {session} $&'\` in the shell that runs marimo.`,
|
|
250
240
|
);
|
|
251
241
|
});
|
|
252
242
|
|
|
@@ -256,10 +246,7 @@ describe("getRawPrompt", () => {
|
|
|
256
246
|
{ ...CONNECTION_WITHOUT_FILE, session: "s_ab12cd" },
|
|
257
247
|
"Session: s_ab12cd\n",
|
|
258
248
|
],
|
|
259
|
-
[
|
|
260
|
-
{ ...CONNECTION_WITHOUT_FILE, file: "notebook.py" },
|
|
261
|
-
"File: notebook.py\n",
|
|
262
|
-
],
|
|
249
|
+
[{ ...CONNECTION_WITHOUT_FILE, file: "notebook.py" }, ""],
|
|
263
250
|
])("omits absent preview fields for %j", (connection, fields) => {
|
|
264
251
|
expect(getRawPrompt(connection, null, PAIR_PREVIEW)).toBe(
|
|
265
252
|
"Pair with me on this running marimo notebook.\n\n" +
|
|
@@ -270,13 +257,12 @@ describe("getRawPrompt", () => {
|
|
|
270
257
|
);
|
|
271
258
|
});
|
|
272
259
|
|
|
273
|
-
it("renders the shared preview template with
|
|
260
|
+
it("renders the shared preview template with the session and no file", () => {
|
|
274
261
|
expect(
|
|
275
262
|
getRawPrompt({ ...CONNECTION, session: "s_ab12cd" }, null, PAIR_PREVIEW),
|
|
276
263
|
).toBe(
|
|
277
264
|
"Pair with me on this running marimo notebook.\n\n" +
|
|
278
265
|
"URL: http://localhost:8000\n" +
|
|
279
|
-
"File: notebooks/example.py\n" +
|
|
280
266
|
"Session: s_ab12cd\n\n" +
|
|
281
267
|
"Run `uvx marimo@latest pair --help` first.\n" +
|
|
282
268
|
"Use `uvx marimo@latest` for all marimo commands.\n\n" +
|
|
@@ -15,7 +15,10 @@ import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
|
|
|
15
15
|
import { Dialog } from "@/components/ui/dialog";
|
|
16
16
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
17
17
|
import { pairPreviewAtom } from "@/core/config/pair";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
type StableSessionId,
|
|
20
|
+
stableSessionIdAtom,
|
|
21
|
+
} from "@/core/kernel/session";
|
|
19
22
|
import { filenameAtom } from "@/core/saving/file-state";
|
|
20
23
|
import {
|
|
21
24
|
DEFAULT_RUNTIME_CONFIG,
|
|
@@ -39,10 +42,13 @@ function wrapper({ children }: { children: React.ReactNode }) {
|
|
|
39
42
|
);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
const STABLE_ID = "sess-stable-1" as StableSessionId;
|
|
46
|
+
|
|
42
47
|
describe("PairWithAgentModal", () => {
|
|
43
48
|
beforeEach(() => {
|
|
44
49
|
vi.clearAllMocks();
|
|
45
50
|
store.set(pairPreviewAtom, PAIR_PREVIEW);
|
|
51
|
+
store.set(stableSessionIdAtom, STABLE_ID);
|
|
46
52
|
store.set(filenameAtom, "/project/my notebook.py");
|
|
47
53
|
store.set(runtimeConfigAtom, {
|
|
48
54
|
...DEFAULT_RUNTIME_CONFIG,
|
|
@@ -57,12 +63,13 @@ describe("PairWithAgentModal", () => {
|
|
|
57
63
|
|
|
58
64
|
afterEach(() => {
|
|
59
65
|
store.set(pairPreviewAtom, undefined);
|
|
66
|
+
store.set(stableSessionIdAtom, null);
|
|
60
67
|
store.set(filenameAtom, null);
|
|
61
68
|
vi.unstubAllGlobals();
|
|
62
69
|
});
|
|
63
70
|
|
|
64
71
|
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
65
|
-
"omits skill installation and
|
|
72
|
+
"omits skill installation and targets the stable session in %s preview",
|
|
66
73
|
async (tab) => {
|
|
67
74
|
await act(async () => {
|
|
68
75
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
@@ -74,59 +81,41 @@ describe("PairWithAgentModal", () => {
|
|
|
74
81
|
|
|
75
82
|
const panel = screen.getByRole("tabpanel");
|
|
76
83
|
expect(within(panel).queryAllByText(/skill/i)).toEqual([]);
|
|
77
|
-
expect(panel).toHaveTextContent(
|
|
78
|
-
expect(panel).toHaveTextContent(
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
expect(panel).not.toHaveTextContent("/project/my notebook.py");
|
|
84
|
+
expect(panel).toHaveTextContent(STABLE_ID);
|
|
85
|
+
expect(panel).not.toHaveTextContent("--file");
|
|
86
|
+
expect(panel).not.toHaveTextContent("File:");
|
|
87
|
+
expect(panel).not.toHaveTextContent("notebook.py");
|
|
82
88
|
expect(panel).toHaveTextContent(
|
|
83
89
|
tab === "Prompt" ? "1. Copy this prompt" : "1. Run in your terminal",
|
|
84
90
|
);
|
|
85
|
-
},
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
89
|
-
"includes the known notebook file in %s preview when the URL has no file",
|
|
90
|
-
async (tab) => {
|
|
91
|
-
window.history.replaceState({}, "", "/");
|
|
92
|
-
await act(async () => {
|
|
93
|
-
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
94
|
-
});
|
|
95
|
-
fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
|
|
96
|
-
button: 0,
|
|
97
|
-
ctrlKey: false,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const panel = screen.getByRole("tabpanel");
|
|
101
|
-
const fileHint =
|
|
102
|
-
tab === "Prompt"
|
|
103
|
-
? "File: /project/my notebook.py"
|
|
104
|
-
: "--file '/project/my notebook.py'";
|
|
105
|
-
expect(panel).toHaveTextContent(fileHint);
|
|
106
91
|
fireEvent.click(
|
|
107
92
|
within(panel).getByRole("button", {
|
|
108
93
|
name: tab === "Prompt" ? "Copy prompt" : "Copy command",
|
|
109
94
|
}),
|
|
110
95
|
);
|
|
111
96
|
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
112
|
-
|
|
97
|
+
const copied = vi.mocked(copyToClipboard).mock.calls[0][0];
|
|
98
|
+
expect(copied).toContain(STABLE_ID);
|
|
99
|
+
expect(copied).not.toContain("notebook.py");
|
|
113
100
|
},
|
|
114
101
|
);
|
|
115
102
|
|
|
116
|
-
it.each([
|
|
117
|
-
"omits the
|
|
118
|
-
async (
|
|
119
|
-
|
|
120
|
-
store.set(filenameAtom, filename);
|
|
103
|
+
it.each(["Claude", "Prompt"])(
|
|
104
|
+
"omits the session in %s preview before kernel-ready arrives",
|
|
105
|
+
async (tab) => {
|
|
106
|
+
store.set(stableSessionIdAtom, null);
|
|
121
107
|
await act(async () => {
|
|
122
108
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
123
109
|
});
|
|
124
|
-
|
|
125
|
-
fireEvent.mouseDown(screen.getByRole("tab", { name: "Prompt" }), {
|
|
110
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
|
|
126
111
|
button: 0,
|
|
127
112
|
ctrlKey: false,
|
|
128
113
|
});
|
|
129
|
-
|
|
114
|
+
|
|
115
|
+
const panel = screen.getByRole("tabpanel");
|
|
116
|
+
expect(panel).not.toHaveTextContent("--session");
|
|
117
|
+
expect(panel).not.toHaveTextContent("Session:");
|
|
118
|
+
expect(panel).toHaveTextContent("http://localhost:8000");
|
|
130
119
|
},
|
|
131
120
|
);
|
|
132
121
|
|
|
@@ -147,7 +136,8 @@ describe("PairWithAgentModal", () => {
|
|
|
147
136
|
expect(
|
|
148
137
|
within(panel).getByText("npx skills add marimo-team/marimo-pair"),
|
|
149
138
|
).toBeVisible();
|
|
150
|
-
expect(panel).not.toHaveTextContent(
|
|
139
|
+
expect(panel).not.toHaveTextContent(STABLE_ID);
|
|
140
|
+
expect(panel).not.toHaveTextContent("--file");
|
|
151
141
|
expect(panel).not.toHaveTextContent("MARIMO_PAIR_NEXT");
|
|
152
142
|
expect(panel).not.toHaveTextContent("/project/my notebook.py");
|
|
153
143
|
expect(panel).toHaveTextContent(
|
|
@@ -205,8 +195,7 @@ describe("PairWithAgentModal", () => {
|
|
|
205
195
|
expect(copyToClipboard).toHaveBeenCalledWith(
|
|
206
196
|
String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
207
197
|
--url http://localhost:8000/ \
|
|
208
|
-
--
|
|
209
|
-
--session ${getSessionId()} \
|
|
198
|
+
--session ${STABLE_ID} \
|
|
210
199
|
--with-token)"`,
|
|
211
200
|
),
|
|
212
201
|
);
|
|
@@ -38,8 +38,9 @@ function getFileFlag(file: string | undefined): string {
|
|
|
38
38
|
/** Identifies the specific running notebook to pair on. */
|
|
39
39
|
export interface ConnectionInfo {
|
|
40
40
|
url: string;
|
|
41
|
-
/** The server's file key
|
|
41
|
+
/** The server's file key, when known. */
|
|
42
42
|
file?: string;
|
|
43
|
+
/** The stable session ID, when known. */
|
|
43
44
|
session?: string;
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -49,7 +50,7 @@ export interface ConnectionInfo {
|
|
|
49
50
|
*/
|
|
50
51
|
export function getTerminalCommand(
|
|
51
52
|
agent: Exclude<AgentTab, "prompt">,
|
|
52
|
-
{ url,
|
|
53
|
+
{ url, session }: ConnectionInfo,
|
|
53
54
|
withToken: boolean,
|
|
54
55
|
preview?: PairPreviewConfig,
|
|
55
56
|
): string {
|
|
@@ -58,7 +59,6 @@ export function getTerminalCommand(
|
|
|
58
59
|
const base = [
|
|
59
60
|
`${prefix}${command} pair prompt`,
|
|
60
61
|
`--url ${shellQuote(url)}`,
|
|
61
|
-
file ? `--file ${shellQuote(file)}` : "",
|
|
62
62
|
preview && session ? `--session ${shellQuote(session)}` : "",
|
|
63
63
|
withToken ? "--with-token" : "",
|
|
64
64
|
preview ? "" : `--${agent}`,
|
|
@@ -92,7 +92,6 @@ export function getRawPrompt(
|
|
|
92
92
|
return formatPrompt(templates.prompt, {
|
|
93
93
|
command,
|
|
94
94
|
url,
|
|
95
|
-
file: file ? formatPrompt(templates.file, { file }) : "",
|
|
96
95
|
session: session ? formatPrompt(templates.session, { session }) : "",
|
|
97
96
|
authentication: token
|
|
98
97
|
? formatPrompt(templates.token, { token: shellQuote(token) })
|
|
@@ -18,8 +18,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
18
18
|
import { asRemoteURL, useRuntimeManager } from "@/core/runtime/config";
|
|
19
19
|
import { API } from "@/core/network/api";
|
|
20
20
|
import { pairPreviewAtom } from "@/core/config/pair";
|
|
21
|
-
import {
|
|
22
|
-
import { useFilename } from "@/core/saving/filename";
|
|
21
|
+
import { stableSessionIdAtom } from "@/core/kernel/session";
|
|
23
22
|
import {
|
|
24
23
|
AGENT_LABELS,
|
|
25
24
|
AGENT_TABS,
|
|
@@ -54,16 +53,14 @@ export const PairWithAgentModal: React.FC<{
|
|
|
54
53
|
const [activeTab, setActiveTab] = useState<AgentTab>("claude");
|
|
55
54
|
const runtimeManager = useRuntimeManager();
|
|
56
55
|
const preview = useAtomValue(pairPreviewAtom);
|
|
57
|
-
const
|
|
56
|
+
const stableSessionId = useAtomValue(stableSessionIdAtom);
|
|
58
57
|
const commandStep = preview ? 1 : 2;
|
|
59
58
|
const authToken = useAuthToken();
|
|
60
59
|
const hasToken = Boolean(authToken);
|
|
61
60
|
const connection: ConnectionInfo = {
|
|
62
61
|
url: runtimeManager.httpURL.toString(),
|
|
63
|
-
file:
|
|
64
|
-
|
|
65
|
-
(preview ? filename || undefined : undefined),
|
|
66
|
-
session: preview ? getSessionId() : undefined,
|
|
62
|
+
file: preview ? undefined : getFileFromURL(window.location.href),
|
|
63
|
+
session: preview ? (stableSessionId ?? undefined) : undefined,
|
|
67
64
|
};
|
|
68
65
|
|
|
69
66
|
return (
|
package/src/core/config/pair.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { init } from "@paralleldrive/cuid2";
|
|
3
|
+
import { atom } from "jotai";
|
|
3
4
|
import { Logger } from "@/utils/Logger";
|
|
4
5
|
import type { TypedString } from "@/utils/typed";
|
|
5
6
|
import { updateQueryParams } from "@/utils/urls";
|
|
@@ -9,6 +10,14 @@ import { store } from "../state/jotai";
|
|
|
9
10
|
|
|
10
11
|
export type SessionId = TypedString<"SessionId">;
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Server-owned identity of the notebook Session. It survives browser reloads
|
|
15
|
+
* and reconnects and ends with the Session.
|
|
16
|
+
*/
|
|
17
|
+
export type StableSessionId = TypedString<"StableSessionId">;
|
|
18
|
+
|
|
19
|
+
export const stableSessionIdAtom = atom<StableSessionId | null>(null);
|
|
20
|
+
|
|
12
21
|
const createId = init({ length: 6 });
|
|
13
22
|
|
|
14
23
|
export function generateSessionId(): SessionId {
|
|
@@ -60,7 +60,11 @@ import {
|
|
|
60
60
|
handleRemoveUIElements,
|
|
61
61
|
} from "../kernel/handlers";
|
|
62
62
|
import { queryParamHandlers } from "../kernel/queryParamHandlers";
|
|
63
|
-
import
|
|
63
|
+
import {
|
|
64
|
+
type SessionId,
|
|
65
|
+
type StableSessionId,
|
|
66
|
+
stableSessionIdAtom,
|
|
67
|
+
} from "../kernel/session";
|
|
64
68
|
import { initialRunCompletedAtom, kernelStateAtom } from "../kernel/state";
|
|
65
69
|
import { type LayoutState, useLayoutActions } from "../layout/state";
|
|
66
70
|
import { kioskModeAtom } from "../mode";
|
|
@@ -231,6 +235,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
231
235
|
addStartupLog,
|
|
232
236
|
} = useAlertActions();
|
|
233
237
|
const setKioskMode = useSetAtom(kioskModeAtom);
|
|
238
|
+
const setStableSessionId = useSetAtom(stableSessionIdAtom);
|
|
234
239
|
const setCapabilities = useSetAtom(capabilitiesAtom);
|
|
235
240
|
const runtimeManager = useRuntimeManager();
|
|
236
241
|
const transportType = useAtomValue(connectionTransportTypeAtom);
|
|
@@ -284,6 +289,10 @@ export function useMarimoKernelConnection(opts: {
|
|
|
284
289
|
invalidateDataSourceDiscovery();
|
|
285
290
|
}
|
|
286
291
|
setKioskMode(msg.data.kiosk);
|
|
292
|
+
setStableSessionId(
|
|
293
|
+
(msg.data.stable_session_id as StableSessionId | null | undefined) ??
|
|
294
|
+
null,
|
|
295
|
+
);
|
|
287
296
|
// A freshly started kernel has no breakpoints of its own; re-push
|
|
288
297
|
// the client's set so they still apply, and clear the stale
|
|
289
298
|
// highlighted line from the previous kernel instance.
|