@marimo-team/frontend 0.25.2-dev37 → 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.
Files changed (98) hide show
  1. package/dist/assets/{CellStatus-CRNbTOIs.js → CellStatus-C24XF8TO.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-CHPy195L.js → ErrorBoundary-B0A5dBH2.js} +2 -2
  3. package/dist/assets/{JsonOutput-BdpXVhxV.js → JsonOutput-Cx3AQC7R.js} +3 -3
  4. package/dist/assets/{MarimoErrorOutput-K6C_dFmm.js → MarimoErrorOutput-CMVdJiaK.js} +1 -1
  5. package/dist/assets/{RenderHTML-ClrDrx3F.js → RenderHTML-DjS-DdUL.js} +1 -1
  6. package/dist/assets/{RunButton-BA02OhJe.js → RunButton-DhhAS4nD.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-sgoMoiI9.js → add-cell-with-ai-BPu32863.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-BOV2U067.js → add-connection-dialog-DZj5amkT.js} +1 -1
  9. package/dist/assets/{agent-panel-CRzjBUdB.js → agent-panel-DKJ-xqqe.js} +1 -1
  10. package/dist/assets/ai-model-dropdown-D_5kXqGw.js +5 -0
  11. package/dist/assets/{api-C5RyTgDk.js → api-DMoBWmQT.js} +1 -1
  12. package/dist/assets/{app-config-button-BAQBx4II.js → app-config-button-BBBqdwwv.js} +1 -1
  13. package/dist/assets/{blob-BKNVs7hM.js → blob-mVTRazkn.js} +1 -1
  14. package/dist/assets/{cell-editor-CEABNkmY.js → cell-editor-BNAwiIir.js} +2 -2
  15. package/dist/assets/cell-link-BFoYN4G4.js +1 -0
  16. package/dist/assets/{cells-DIjn-wIE.js → cells-Bboj_mzH.js} +1 -1
  17. package/dist/assets/{chat-display-Bpl-2umz.js → chat-display-BclykCDT.js} +1 -1
  18. package/dist/assets/{chat-panel-C6NjK6Ig.js → chat-panel-DvGZ0ahc.js} +1 -1
  19. package/dist/assets/{chat-ui-C5Ulv8Sh.js → chat-ui-B1xpc2Iq.js} +1 -1
  20. package/dist/assets/{code-visibility-DbW2QNho.js → code-visibility-DStVsztB.js} +1 -1
  21. package/dist/assets/{column-preview-CZ2TL09N.js → column-preview-B_HNw6Xa.js} +1 -1
  22. package/dist/assets/{command-palette-DaHlZzjG.js → command-palette-B8pPDrL3.js} +1 -1
  23. package/dist/assets/{common-BQbHQAab.js → common-CAoKKAj9.js} +1 -1
  24. package/dist/assets/{components-DJ4W2IzC.js → components-0tF5D2G2.js} +1 -1
  25. package/dist/assets/{components-Dt0cuD4C.js → components-Scfns93C.js} +1 -1
  26. package/dist/assets/{config-D7CP5wR5.js → config-VEBoxYs0.js} +1 -1
  27. package/dist/assets/{datasource-Ccdyd-O2.js → datasource-DXrVF3A7.js} +1 -1
  28. package/dist/assets/{dependency-graph-panel-DIQpBTOZ.js → dependency-graph-panel-BKerKbW0.js} +1 -1
  29. package/dist/assets/{documentation-panel-DTDeaE2n.js → documentation-panel-BBqGQovG.js} +1 -1
  30. package/dist/assets/{download-DBXnO5qS.js → download-CsvgDpk8.js} +1 -1
  31. package/dist/assets/{edit-page-HpmTxp7Y.js → edit-page-J1s9SrSW.js} +6 -6
  32. package/dist/assets/{error-entries-BB7FNt10.js → error-entries-EhYWazUP.js} +1 -1
  33. package/dist/assets/{error-panel-0aG3Tdrl.js → error-panel-BBwaFurl.js} +1 -1
  34. package/dist/assets/feedback-button-BXARphau.js +1 -0
  35. package/dist/assets/{feedback-button-Ck0Mp9so.js → feedback-button-D4FkbdpF.js} +1 -1
  36. package/dist/assets/{file-explorer-panel-Dqxfg5xm.js → file-explorer-panel-DOuYHyNb.js} +1 -1
  37. package/dist/assets/{file-name-input-CUxuxMjv.js → file-name-input-oZ_OFDwA.js} +1 -1
  38. package/dist/assets/{floating-outline-yJVtlHMv.js → floating-outline-CuOLwf13.js} +1 -1
  39. package/dist/assets/{gallery-page-D70P7AOr.js → gallery-page-DnObr5r7.js} +1 -1
  40. package/dist/assets/{glide-data-editor-YqrbKh4R.js → glide-data-editor-SRFol8N3.js} +1 -1
  41. package/dist/assets/{home-page-Dnv5d4Ek.js → home-page-CrRCUhHM.js} +1 -1
  42. package/dist/assets/{hooks-ZWyI6fMV.js → hooks-DDexlabY.js} +1 -1
  43. package/dist/assets/{index-BUQP_BOv.js → index-h-GQYxNi.js} +6 -6
  44. package/dist/assets/kiosk-mode-CEKB1dXx.js +1 -0
  45. package/dist/assets/{layout-C8pvkupF.js → layout-4WXAZ1eW.js} +4 -4
  46. package/dist/assets/links-Bc4DEgui.js +1 -0
  47. package/dist/assets/{logs-panel-Bdb_qtEg.js → logs-panel-BYUR5w2M.js} +1 -1
  48. package/dist/assets/{markdown-renderer-DT34-_9e.js → markdown-renderer-BCB0NTfn.js} +1 -1
  49. package/dist/assets/{name-cell-input-E95KyKqv.js → name-cell-input-C-myEA1c.js} +1 -1
  50. package/dist/assets/{outline-panel-1AnW7CLy.js → outline-panel-CM2lVI29.js} +1 -1
  51. package/dist/assets/{packages-panel-t-WIjOjO.js → packages-panel-BwJc45sJ.js} +1 -1
  52. package/dist/assets/{pair-DrvWpc2_.js → pair-_FSgYVX4.js} +1 -1
  53. package/dist/assets/{panel-accordion-state-_r3T-AGo.js → panel-accordion-state-XgB7A-Pf.js} +1 -1
  54. package/dist/assets/panels-SK_rec-s.js +4 -0
  55. package/dist/assets/{process-output-gyG2tEQt.js → process-output-Bpzcx6ny.js} +1 -1
  56. package/dist/assets/{readonly-code-display-DVP0M5ji.js → readonly-code-display-DykeAqVP.js} +1 -1
  57. package/dist/assets/{readonly-python-code-CPhK2DqE.js → readonly-python-code-NhpM1k7Y.js} +1 -1
  58. package/dist/assets/{reveal-component-CICzazBY.js → reveal-component-DvtSwOtf.js} +1 -1
  59. package/dist/assets/run-page-DtMQZ9fB.js +1 -0
  60. package/dist/assets/{sandbox-panel-CcKUdNwc.js → sandbox-panel-BTJZoLQf.js} +1 -1
  61. package/dist/assets/{scratchpad-panel-Bju5WCrd.js → scratchpad-panel-DAR9LXOp.js} +1 -1
  62. package/dist/assets/{session-ztCnZJHI.js → session-D7QkhMHj.js} +1 -1
  63. package/dist/assets/{session-panel-BhoASF58.js → session-panel-BFDg1epj.js} +1 -1
  64. package/dist/assets/{snippets-panel-BGO2n9mY.js → snippets-panel-D5jPm-dQ.js} +1 -1
  65. package/dist/assets/{state-OJSVjDAX.js → state-C2hpK3R0.js} +1 -1
  66. package/dist/assets/{state-0X7um_9u.js → state-ClA31EHO.js} +3 -3
  67. package/dist/assets/{terminal-BaYFckM3.js → terminal-DgXAZSkT.js} +1 -1
  68. package/dist/assets/{tracing-BwkQnRsr.js → tracing-CDAILi-O.js} +1 -1
  69. package/dist/assets/{tracing-panel-Bari_aJY.js → tracing-panel-Clom4CyL.js} +2 -2
  70. package/dist/assets/{tree-actions-DpLu3KP0.js → tree-actions-gTQxtJX8.js} +1 -1
  71. package/dist/assets/{useAddCell-DCbFUjci.js → useAddCell-Cz3QoshN.js} +1 -1
  72. package/dist/assets/{useBoolean-C44TyhOc.js → useBoolean-iz9l8Cuk.js} +1 -1
  73. package/dist/assets/{useCellActionButton-Dd3_WkrO.js → useCellActionButton-BMuXaKhK.js} +1 -1
  74. package/dist/assets/{useDeleteCell-BJ6lgiBJ.js → useDeleteCell-DieDZaIf.js} +1 -1
  75. package/dist/assets/{useDependencyPanelTab-CyNU7lXE.js → useDependencyPanelTab-B8hZ99Ij.js} +1 -1
  76. package/dist/assets/{useInstallPackage-DO1N1lCX.js → useInstallPackage-DsbNVe8R.js} +1 -1
  77. package/dist/assets/useNotebookActions-9w4zO1hI.js +1 -0
  78. package/dist/assets/{useRunCells-k0IpRLOw.js → useRunCells-BtJbnrtV.js} +1 -1
  79. package/dist/assets/{useSplitCell-AE67LfQn.js → useSplitCell-Bb3bloRI.js} +1 -1
  80. package/dist/assets/{vega-component-Bw0srfks.js → vega-component-CyO7yyQw.js} +1 -1
  81. package/dist/index.html +25 -26
  82. package/package.json +1 -1
  83. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +32 -46
  84. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +29 -40
  85. package/src/components/editor/actions/pair-with-agent-commands.ts +3 -4
  86. package/src/components/editor/actions/pair-with-agent-modal.tsx +4 -7
  87. package/src/core/config/pair.ts +0 -1
  88. package/src/core/kernel/session.ts +9 -0
  89. package/src/core/websocket/useMarimoKernelConnection.tsx +10 -1
  90. package/dist/assets/ai-model-dropdown-B547mMnB.js +0 -5
  91. package/dist/assets/cell-link-CEmc-Cr5.js +0 -1
  92. package/dist/assets/feedback-button-DFFJadk1.js +0 -1
  93. package/dist/assets/filename-BlDwe-3Z.js +0 -1
  94. package/dist/assets/kiosk-mode-Cvh6wszH.js +0 -1
  95. package/dist/assets/links-Dz_7YAlE.js +0 -1
  96. package/dist/assets/panels-HoIP3cDr.js +0 -4
  97. package/dist/assets/run-page-DWUXQLtA.js +0 -1
  98. 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 file for each agent", () => {
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 --file notebooks/example.py --claude)"`,
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 --file notebooks/example.py --codex)"`,
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 --file notebooks/example.py --opencode)"`,
171
+ `opencode --prompt "$(uv run marimo pair prompt --url http://localhost:8000 --opencode)"`,
173
172
  );
174
173
  });
175
174
 
176
- it("omits the file flag when the page URL has no file", () => {
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("--session");
178
+ expect(command).not.toContain("notebooks/example.py");
184
179
  });
185
180
 
186
- it("shell-escapes a url containing metacharacters", () => {
187
- const command = getTerminalCommand(
188
- "claude",
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.each([
196
- ["relative/my notebook.py", "--file 'relative/my notebook.py'"],
197
- ["/tmp/my notebook.py", "--file '/tmp/my notebook.py'"],
198
- [
199
- String.raw`C:\Users\Jane Doe\notebook.py`,
200
- String.raw`--file 'C:\Users\Jane Doe\notebook.py'`,
201
- ],
202
- [
203
- String.raw`\\server\share\my notebook.py`,
204
- String.raw`--file '\\server\share\my notebook.py'`,
205
- ],
206
- ["notebooks/it's.py", `--file 'notebooks/it'"'"'s.py'`],
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: CONNECTION.url, file },
201
+ { url: "http://host:8000?auth=a&b" },
211
202
  false,
212
203
  );
213
- expect(command).toContain(expected);
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/{file}",
234
- file: "{session}/it's.py",
235
- session: "{command}",
224
+ url: "http://host/{session}",
225
+ session: "{command}/it's",
236
226
  },
237
- "tok'en {file} $&",
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{file}{session}{authentication}",
233
+ prompt: "{command}\n{url}\n{session}{authentication}",
244
234
  },
245
235
  },
246
236
  ),
247
237
  ).toBe(
248
- "custom marimo\nhttp://host/{file}\nFile: {session}/it's.py\nSession: {command}\n" +
249
- `\n\nFor authenticated Pair commands, set \`export MARIMO_TOKEN='tok'"'"'en {file} $&'\` in the shell that runs marimo.`,
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 optional session context", () => {
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 { getSessionId } from "@/core/kernel/session";
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 includes the current session in %s preview",
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(getSessionId());
78
- expect(panel).toHaveTextContent(
79
- tab === "Prompt" ? "File: notebook.py" : "--file notebook.py",
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
- expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(fileHint);
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([null, ""])(
117
- "omits the file when the URL and notebook filename are absent (%s)",
118
- async (filename) => {
119
- window.history.replaceState({}, "", "/");
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
- expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--file");
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
- expect(screen.getByRole("tabpanel")).not.toHaveTextContent("File:");
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(getSessionId());
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
- --file notebook.py \
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 or notebook filename, when known. */
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, file, session }: ConnectionInfo,
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 { getSessionId } from "@/core/kernel/session";
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 filename = useFilename();
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
- getFileFromURL(window.location.href) ??
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 (
@@ -7,7 +7,6 @@ export const pairPreviewSchema = z.object({
7
7
  command: z.string(),
8
8
  templates: z.object({
9
9
  prompt: z.string(),
10
- file: z.string(),
11
10
  session: z.string(),
12
11
  token_file: z.string(),
13
12
  token: z.string(),
@@ -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 type { SessionId } from "../kernel/session";
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.