@marimo-team/frontend 0.24.3-dev63 → 0.24.3-dev68

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 (115) hide show
  1. package/dist/assets/{ConnectedDataExplorerComponent-BSvwB270.js → ConnectedDataExplorerComponent-B8qRI1J3.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-TsEoIDRr.js → ErrorBoundary-B24BGOv-.js} +2 -2
  3. package/dist/assets/{LazyAnyLanguageCodeMirror-BSKy620l.js → LazyAnyLanguageCodeMirror-BiRrYKh3.js} +2 -2
  4. package/dist/assets/{RunButton-Dul9caMU.js → RunButton-CCeB-E93.js} +1 -1
  5. package/dist/assets/__vite-browser-external-Cw9Ccr_O.js +1 -0
  6. package/dist/assets/__vite-browser-external-DBuuG05M.js +1 -0
  7. package/dist/assets/{abnfDiagram-VCTEODGH-Cjyff0Dj.js → abnfDiagram-VCTEODGH-BAf7PJTE.js} +1 -1
  8. package/dist/assets/{add-cell-with-ai-BhDPL21V.js → add-cell-with-ai-Bfw26bS2.js} +1 -1
  9. package/dist/assets/{agent-panel-CYUY6ClW.js → agent-panel-DDQFUPQQ.js} +4 -4
  10. package/dist/assets/{ai-model-dropdown-CnDRzQfQ.js → ai-model-dropdown-BveigXNm.js} +1 -1
  11. package/dist/assets/{any-language-editor-C6lBHXNY.js → any-language-editor-qIp2874m.js} +1 -1
  12. package/dist/assets/{app-config-button-9I3Y-dHh.js → app-config-button-D12AZ5Jw.js} +1 -1
  13. package/dist/assets/{architectureDiagram-5GKGNRK7-BBYvsdwV.js → architectureDiagram-5GKGNRK7-DODMm5nu.js} +1 -1
  14. package/dist/assets/{blockDiagram-I7D4REHJ-XAi6eabU.js → blockDiagram-I7D4REHJ-CwnPIH04.js} +1 -1
  15. package/dist/assets/{cache-panel-DKXBQfDt.js → cache-panel-B_aqFT1r.js} +1 -1
  16. package/dist/assets/{cells-DdE_4Bcx.js → cells-BY2lhea7.js} +1 -1
  17. package/dist/assets/{chat-display-BlIEWw3Q.js → chat-display-DJTGR5xt.js} +1 -1
  18. package/dist/assets/{chat-panel-C_XE2bS-.js → chat-panel-Csz1liLp.js} +1 -1
  19. package/dist/assets/{chat-ui-BRq__jk9.js → chat-ui-CQkBUB_Q.js} +1 -1
  20. package/dist/assets/{chunk-IMKFNOWR-bmBDB0cN.js → chunk-IMKFNOWR-CMvWRWPq.js} +1 -1
  21. package/dist/assets/{chunk-SHT3W25Y-DFrgoI5E.js → chunk-SHT3W25Y-XssGvFwO.js} +1 -1
  22. package/dist/assets/{chunk-SVP7TREG-usES5uzt.js → chunk-SVP7TREG-JYPY-7he.js} +1 -1
  23. package/dist/assets/{chunk-TICWLB2K-DmYGvTDJ.js → chunk-TICWLB2K-DmYV5I8j.js} +1 -1
  24. package/dist/assets/{classDiagram-ZZMXUADV-8oWUrTSA.js → classDiagram-ZZMXUADV-DGESMhgO.js} +1 -1
  25. package/dist/assets/{classDiagram-v2-VYDZK3BY-8oWUrTSA.js → classDiagram-v2-VYDZK3BY-DGESMhgO.js} +1 -1
  26. package/dist/assets/{code-visibility-ChrgD3_b.js → code-visibility-DcZtpJ7Y.js} +1 -1
  27. package/dist/assets/{command-palette-D_70H77j.js → command-palette-DB5rqESX.js} +1 -1
  28. package/dist/assets/{common-BfKDjmi0.js → common-DUhY30A6.js} +1 -1
  29. package/dist/assets/{components-DyBejhtt.js → components-BTJRth05.js} +1 -1
  30. package/dist/assets/{components-BFKAEF3R.js → components-mqvQ3nnU.js} +1 -1
  31. package/dist/assets/{cynefinDiagram-5FMLGOSQ-CGpZt6Mr.js → cynefinDiagram-5FMLGOSQ-5s8Gxv8p.js} +1 -1
  32. package/dist/assets/{dagre-6C2JCFmZ.js → dagre-DIObFfj5.js} +1 -1
  33. package/dist/assets/{dagre-GXQ25YYZ-DwPg2iyK.js → dagre-GXQ25YYZ-DLWNkQz-.js} +1 -1
  34. package/dist/assets/{data-grid-overlay-editor-BHzBco-G.js → data-grid-overlay-editor-BhBuKg9_.js} +1 -1
  35. package/dist/assets/{dependency-graph-panel-DxbTpdSy.js → dependency-graph-panel-DbYPnFAW.js} +1 -1
  36. package/dist/assets/{diagram-S7CK7UJ4-D3rnjIpt.js → diagram-S7CK7UJ4-Vc_7tbGR.js} +1 -1
  37. package/dist/assets/{diagram-UQ7AKVKN--mvb4nAR.js → diagram-UQ7AKVKN-sFxmIB_B.js} +1 -1
  38. package/dist/assets/{diagram-VX7I27RA-DLd2xbGj.js → diagram-VX7I27RA-CeT45gHf.js} +1 -1
  39. package/dist/assets/{diagram-Z3DM3KII-DCQ7kQxu.js → diagram-Z3DM3KII-3qePplNH.js} +1 -1
  40. package/dist/assets/{documentation-panel-D1oy9mJX.js → documentation-panel-StHaPmjq.js} +1 -1
  41. package/dist/assets/{ebnfDiagram-PWID7BFC-BEPSCGTU.js → ebnfDiagram-PWID7BFC-fltqffql.js} +1 -1
  42. package/dist/assets/{edit-page-KRRlhyb9.js → edit-page-ByS6kRQA.js} +4 -4
  43. package/dist/assets/{erDiagram-RLTQ6QDP-DVP8JuYC.js → erDiagram-RLTQ6QDP-Bow67h2D.js} +1 -1
  44. package/dist/assets/{error-entries-8TMYgS7M.js → error-entries-BGsjaQkq.js} +1 -1
  45. package/dist/assets/{error-panel-DSHUnrwV.js → error-panel-CD7f6MTl.js} +1 -1
  46. package/dist/assets/{feedback-button-C-rqcadY.js → feedback-button-BfgkWpt7.js} +1 -1
  47. package/dist/assets/{file-explorer-panel-W4vRK0IK.js → file-explorer-panel-BAL_Pzqi.js} +1 -1
  48. package/dist/assets/{file-name-input-BQURTAD0.js → file-name-input-ClwxBT3X.js} +1 -1
  49. package/dist/assets/{flowDiagram-HODETNUW-CxI8veho.js → flowDiagram-HODETNUW-CC0MXK8f.js} +1 -1
  50. package/dist/assets/{gallery-page-CCiwxBoC.js → gallery-page-BfqIPyEv.js} +1 -1
  51. package/dist/assets/{glide-data-editor-YdUhj2-W.js → glide-data-editor-cUeC-J43.js} +4 -4
  52. package/dist/assets/{graphlib-Dqk_AhmH.js → graphlib-DNXvxVH3.js} +1 -1
  53. package/dist/assets/has-BWBvNXSE.js +1 -0
  54. package/dist/assets/{home-page-Bhfe_Ooj.js → home-page-CZOroZdb.js} +1 -1
  55. package/dist/assets/{index-kGHM0O4l.js → index-DPnCyt1L.js} +35 -35
  56. package/dist/assets/{infoDiagram-27XIBGKW-vkRRAmNG.js → infoDiagram-27XIBGKW-DUckrGOc.js} +1 -1
  57. package/dist/assets/{ishikawaDiagram-5VMMS53U-DwnkA3qK.js → ishikawaDiagram-5VMMS53U-BI5lrbqL.js} +1 -1
  58. package/dist/assets/{kanban-definition-UXKFOSKX-DTM4IJzH.js → kanban-definition-UXKFOSKX-BE_mJxg6.js} +1 -1
  59. package/dist/assets/{logs-panel-CpmZ43Xd.js → logs-panel-X7YYNXWi.js} +1 -1
  60. package/dist/assets/{mermaid-BYaoBhgB.js → mermaid-I9sRPejB.js} +4 -4
  61. package/dist/assets/{mindmap-definition-YA3MSWOX-BtrIeuN2.js → mindmap-definition-YA3MSWOX-BMFx2VBe.js} +1 -1
  62. package/dist/assets/{number-overlay-editor-uOD-jX8J.js → number-overlay-editor-EQ27ERwk.js} +1 -1
  63. package/dist/assets/{outline-panel-DHBB8jr2.js → outline-panel-DbnyiE-B.js} +1 -1
  64. package/dist/assets/{packages-panel-Ba0BIKw-.js → packages-panel-wCsQlp5Z.js} +1 -1
  65. package/dist/assets/{panels-CVG8esk7.js → panels-8_Hfok8J.js} +3 -3
  66. package/dist/assets/{pegDiagram-XKGWAZYB-72Js5lku.js → pegDiagram-XKGWAZYB-Bke1_i45.js} +1 -1
  67. package/dist/assets/{pieDiagram-E7YTZNPT-Bj3RCLfN.js → pieDiagram-E7YTZNPT-4n1GeAi8.js} +1 -1
  68. package/dist/assets/{process-output-cqVv_LQh.js → process-output-DDr6kJst.js} +1 -1
  69. package/dist/assets/{railroadDiagram-O6MQD6OU-B10zJ5xk.js → railroadDiagram-O6MQD6OU-DzSnQBmP.js} +1 -1
  70. package/dist/assets/{requirementDiagram-BXWQKSXE-BF4LA-gO.js → requirementDiagram-BXWQKSXE-Bjqcm878.js} +1 -1
  71. package/dist/assets/{reveal-component-Cvagx4WC.js → reveal-component-MnNIqkZg.js} +1 -1
  72. package/dist/assets/{run-page-O9BXJwTz.js → run-page-BGQahlCc.js} +1 -1
  73. package/dist/assets/{save-worker-CMa6Cvnx.js → save-worker-Dw8ybFWq.js} +3 -3
  74. package/dist/assets/{scratchpad-panel-CJacTdzi.js → scratchpad-panel-Wt_FZRjv.js} +1 -1
  75. package/dist/assets/{secrets-panel-BvtVCy_x.js → secrets-panel-DiIOsz3V.js} +1 -1
  76. package/dist/assets/{sequenceDiagram-WJ2MYXX4-Cz9d4L3l.js → sequenceDiagram-WJ2MYXX4-r-c3v7Ho.js} +1 -1
  77. package/dist/assets/{session-panel-C7l7daPJ.js → session-panel-F6G-YcGG.js} +1 -1
  78. package/dist/assets/{snippets-panel-BP4UI0bt.js → snippets-panel-mWSX1anY.js} +1 -1
  79. package/dist/assets/{stateDiagram-D77RDMKH-BYVxo4-Y.js → stateDiagram-D77RDMKH-COUIwUQY.js} +1 -1
  80. package/dist/assets/{stateDiagram-v2-MP3YSRHH-BO2Qvl2_.js → stateDiagram-v2-MP3YSRHH-DxCCjAQm.js} +1 -1
  81. package/dist/assets/{swimlanes-42K2YHIH-nJMxtt_e.js → swimlanes-42K2YHIH-CFQCC6A2.js} +1 -1
  82. package/dist/assets/{swimlanesDiagram-VR7AAH4N-2_BeIocM.js → swimlanesDiagram-VR7AAH4N-BEVDZiRo.js} +1 -1
  83. package/dist/assets/{swiper-component-VHJ-MBld.js → swiper-component-DGc_akcB.js} +1 -1
  84. package/dist/assets/{terminal-DfWCJPGz.js → terminal-BdlA-Q6e.js} +1 -1
  85. package/dist/assets/{timeline-definition-24CTP7MA-LaRikknb.js → timeline-definition-24CTP7MA-BJ-VJeHJ.js} +1 -1
  86. package/dist/assets/{tracing-njWlk06F.js → tracing-Bsl9kRwF.js} +1 -1
  87. package/dist/assets/{tracing-panel-BFMefFq8.js → tracing-panel-Cpes4Agb.js} +2 -2
  88. package/dist/assets/{useCellActionButton-Cie43jOq.js → useCellActionButton-BEOOTct7.js} +1 -1
  89. package/dist/assets/{vega-component-CrJlHxXg.js → vega-component-UjBXVqkX.js} +1 -1
  90. package/dist/assets/{vega-embed-container-DkDtQznq.js → vega-embed-container-NxkXguyz.js} +1 -1
  91. package/dist/assets/{vennDiagram-4TSXK5OY-y3U656IE.js → vennDiagram-4TSXK5OY-DT7_rCm5.js} +1 -1
  92. package/dist/assets/{wardleyDiagram-VM6X3IG4-COdor5IG.js → wardleyDiagram-VM6X3IG4-Ce6qiu-G.js} +1 -1
  93. package/dist/assets/{worker-B8c-1v-F.js → worker-ByzyDs4N.js} +3 -3
  94. package/dist/assets/{xychartDiagram-S5SC5T6Z-BHauP6L-.js → xychartDiagram-S5SC5T6Z-jjkhfqC-.js} +1 -1
  95. package/dist/index.html +4 -4
  96. package/package.json +1 -1
  97. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +383 -0
  98. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +10 -8
  99. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  100. package/src/components/chat/acp/agent-panel.tsx +70 -30
  101. package/src/components/chat/acp/state.ts +1 -1
  102. package/src/core/cells/__tests__/cells.test.ts +144 -0
  103. package/src/core/cells/cells.ts +7 -17
  104. package/src/core/wasm/__tests__/bridge.test.ts +5 -4
  105. package/src/core/wasm/__tests__/runtime-config.test.ts +8 -1
  106. package/src/core/wasm/bridge.ts +7 -8
  107. package/src/core/wasm/runtime-config.ts +4 -0
  108. package/src/core/wasm/worker/constants.ts +1 -0
  109. package/src/core/wasm/worker/getController.ts +3 -1
  110. package/src/core/wasm/worker/save-worker.ts +4 -10
  111. package/src/core/wasm/worker/types.ts +0 -1
  112. package/src/core/wasm/worker/worker.ts +4 -10
  113. package/dist/assets/__vite-browser-external-BKSyd8Ok.js +0 -1
  114. package/dist/assets/__vite-browser-external-ZTY8EBdt.js +0 -1
  115. package/dist/assets/has-BXV7_iwm.js +0 -1
package/dist/index.html CHANGED
@@ -66,7 +66,7 @@
66
66
  <marimo-server-token data-token="{{ server_token }}" hidden></marimo-server-token>
67
67
  <!-- /TODO -->
68
68
  <title>{{ title }}</title>
69
- <script type="module" crossorigin src="./assets/index-kGHM0O4l.js"></script>
69
+ <script type="module" crossorigin src="./assets/index-DPnCyt1L.js"></script>
70
70
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-B0Z9INg1.js">
71
71
  <link rel="modulepreload" crossorigin href="./assets/react-CUdNIagt.js">
72
72
  <link rel="modulepreload" crossorigin href="./assets/compiler-runtime-BQ9S8nyo.js">
@@ -139,16 +139,16 @@
139
139
  <link rel="modulepreload" crossorigin href="./assets/useTheme-Ho3YdAx3.js">
140
140
  <link rel="modulepreload" crossorigin href="./assets/useAsyncData-DucTwMkf.js">
141
141
  <link rel="modulepreload" crossorigin href="./assets/purify.es-CYRiAeUx.js">
142
- <link rel="modulepreload" crossorigin href="./assets/cells-DdE_4Bcx.js">
142
+ <link rel="modulepreload" crossorigin href="./assets/cells-BY2lhea7.js">
143
143
  <link rel="modulepreload" crossorigin href="./assets/get-DjNf4SIX.js">
144
144
  <link rel="modulepreload" crossorigin href="./assets/copy-RyCOE8jo.js">
145
145
  <link rel="modulepreload" crossorigin href="./assets/copy-BhJktFeI.js">
146
146
  <link rel="modulepreload" crossorigin href="./assets/copy-icon-DFhUGBjO.js">
147
- <link rel="modulepreload" crossorigin href="./assets/ErrorBoundary-TsEoIDRr.js">
147
+ <link rel="modulepreload" crossorigin href="./assets/ErrorBoundary-B24BGOv-.js">
148
148
  <link rel="modulepreload" crossorigin href="./assets/config-CMqNngzA.js">
149
149
  <link rel="modulepreload" crossorigin href="./assets/chevron-right-DOPs2jx8.js">
150
150
  <link rel="modulepreload" crossorigin href="./assets/dropdown-menu-VuyoeQOD.js">
151
- <link rel="modulepreload" crossorigin href="./assets/LazyAnyLanguageCodeMirror-BSKy620l.js">
151
+ <link rel="modulepreload" crossorigin href="./assets/LazyAnyLanguageCodeMirror-BiRrYKh3.js">
152
152
  <link rel="modulepreload" crossorigin href="./assets/hat-glasses-DVgK5i5l.js">
153
153
  <link rel="modulepreload" crossorigin href="./assets/package-BeYhVWvM.js">
154
154
  <link rel="modulepreload" crossorigin href="./assets/errors-D9vyuMar.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.24.3-dev63",
3
+ "version": "0.24.3-dev68",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,383 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, fireEvent, render, screen } from "@testing-library/react";
4
+ import { Provider } from "jotai";
5
+ import { JsonRpcError, type useAcpClient } from "use-acp";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+ import { MockRequestClient } from "@/__mocks__/requests";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
9
+ import { pendingAiPromptAtom } from "@/core/ai/state";
10
+ import { requestClientAtom } from "@/core/network/requests";
11
+ import { cwdAtom, filenameAtom } from "@/core/saving/file-state";
12
+ import { store } from "@/core/state/jotai";
13
+ import AgentPanel from "../agent-panel";
14
+ import {
15
+ addSession,
16
+ agentSessionStateAtom,
17
+ updateSessionExternalAgentSessionId,
18
+ } from "../state";
19
+ import type { AgentConnectionState, ExternalAgentSessionId } from "../types";
20
+
21
+ type Agent = NonNullable<ReturnType<typeof useAcpClient>["agent"]>;
22
+ type InitializeResponse = Awaited<ReturnType<Agent["initialize"]>>;
23
+
24
+ const { client, connect, disconnect, setActiveSessionId } = vi.hoisted(() => {
25
+ const client: {
26
+ agent: ReturnType<typeof createAgent>["agent"] | null;
27
+ connectionState: AgentConnectionState;
28
+ activeSessionId: ExternalAgentSessionId | null;
29
+ } = {
30
+ agent: null,
31
+ connectionState: { status: "connected" },
32
+ activeSessionId: null,
33
+ };
34
+ return {
35
+ client,
36
+ connect: vi.fn().mockResolvedValue(undefined),
37
+ disconnect: vi.fn(),
38
+ setActiveSessionId: (sessionId: ExternalAgentSessionId | null) => {
39
+ client.activeSessionId = sessionId;
40
+ },
41
+ };
42
+ });
43
+
44
+ vi.mock("use-acp", async (importOriginal) => ({
45
+ ...(await importOriginal<typeof import("use-acp")>()),
46
+ useAcpClient: () => ({
47
+ ...client,
48
+ connect,
49
+ disconnect,
50
+ notifications: [],
51
+ setActiveSessionId,
52
+ clearNotifications: vi.fn(),
53
+ }),
54
+ }));
55
+ vi.mock("@/components/editor/ai/add-cell-with-ai", () => ({
56
+ PromptInput: () => null,
57
+ }));
58
+ vi.mock("../session-tabs", () => ({ SessionTabs: () => null }));
59
+ vi.mock("../blocks", () => ({ ReadyToChatBlock: () => null }));
60
+
61
+ const initialized: InitializeResponse = {
62
+ protocolVersion: 1,
63
+ agentCapabilities: {},
64
+ };
65
+ const advertisedAuth: InitializeResponse = {
66
+ ...initialized,
67
+ authMethods: [{ id: "login", name: "Log in" }],
68
+ };
69
+
70
+ function createAgent() {
71
+ const initialization = Promise.withResolvers<InitializeResponse>();
72
+ const agent = {
73
+ initialize: vi
74
+ .fn<Agent["initialize"]>()
75
+ .mockReturnValue(initialization.promise),
76
+ authenticate: vi.fn<Agent["authenticate"]>().mockResolvedValue(undefined),
77
+ newSession: vi.fn<Agent["newSession"]>().mockImplementation(async () => {
78
+ client.activeSessionId = "new-session" as ExternalAgentSessionId;
79
+ return { sessionId: "new-session" };
80
+ }),
81
+ loadSession: vi
82
+ .fn<NonNullable<Agent["loadSession"]>>()
83
+ .mockImplementation(async ({ sessionId }) => {
84
+ client.activeSessionId = sessionId as ExternalAgentSessionId;
85
+ return {};
86
+ }),
87
+ cancel: vi.fn<Agent["cancel"]>().mockResolvedValue(undefined),
88
+ prompt: vi
89
+ .fn<Agent["prompt"]>()
90
+ .mockResolvedValue({ stopReason: "end_turn" }),
91
+ };
92
+ return { agent, initialization };
93
+ }
94
+
95
+ function renderPanel() {
96
+ const panel = () => (
97
+ <Provider store={store}>
98
+ <TooltipProvider>
99
+ <AgentPanel />
100
+ </TooltipProvider>
101
+ </Provider>
102
+ );
103
+ const view = render(panel());
104
+ return { ...view, refresh: () => view.rerender(panel()) };
105
+ }
106
+
107
+ function expectNoSessionRequests(
108
+ agent: ReturnType<typeof createAgent>["agent"],
109
+ ) {
110
+ expect(agent.newSession).not.toHaveBeenCalled();
111
+ expect(agent.loadSession).not.toHaveBeenCalled();
112
+ }
113
+
114
+ describe("AgentPanel initialization", () => {
115
+ beforeEach(() => {
116
+ client.agent = null;
117
+ client.activeSessionId = null;
118
+ client.connectionState = { status: "connected" };
119
+ store.set(requestClientAtom, MockRequestClient.create());
120
+ store.set(filenameAtom, "notebook.py");
121
+ store.set(cwdAtom, "/notebooks");
122
+ store.set(pendingAiPromptAtom, null);
123
+ store.set(
124
+ agentSessionStateAtom,
125
+ addSession({ sessions: [], activeTabId: null }, { agentId: "claude" }),
126
+ );
127
+ });
128
+
129
+ afterEach(() => {
130
+ store.set(pendingAiPromptAtom, null);
131
+ store.set(agentSessionStateAtom, { sessions: [], activeTabId: null });
132
+ store.set(filenameAtom, null);
133
+ store.set(cwdAtom, null);
134
+ });
135
+
136
+ it.each([null, "saved-session"])(
137
+ "waits for initialization before creating/loading %s",
138
+ async (sessionId) => {
139
+ const { agent, initialization } = createAgent();
140
+ client.agent = agent;
141
+ if (sessionId) {
142
+ store.set(agentSessionStateAtom, (state) =>
143
+ updateSessionExternalAgentSessionId(
144
+ state,
145
+ sessionId as ExternalAgentSessionId,
146
+ ),
147
+ );
148
+ }
149
+ const { refresh } = renderPanel();
150
+ expect(agent.initialize).toHaveBeenCalledOnce();
151
+ expectNoSessionRequests(agent);
152
+ expect(
153
+ screen.queryByRole("button", { name: "Restart" }),
154
+ ).not.toBeInTheDocument();
155
+
156
+ await act(async () => initialization.resolve(initialized));
157
+ expect(agent.authenticate).not.toHaveBeenCalled();
158
+ if (sessionId) {
159
+ expect(agent.loadSession).toHaveBeenCalledExactlyOnceWith({
160
+ sessionId,
161
+ cwd: "/notebooks",
162
+ mcpServers: [],
163
+ });
164
+ expect(agent.newSession).not.toHaveBeenCalled();
165
+ } else {
166
+ expect(agent.newSession).toHaveBeenCalledExactlyOnceWith({
167
+ cwd: "/notebooks",
168
+ mcpServers: [],
169
+ _meta: undefined,
170
+ });
171
+ expect(agent.loadSession).not.toHaveBeenCalled();
172
+ }
173
+ expect(
174
+ screen.getByRole("button", { name: "Restart" }),
175
+ ).toBeInTheDocument();
176
+ refresh();
177
+ expect(
178
+ agent.newSession.mock.calls.length +
179
+ agent.loadSession.mock.calls.length,
180
+ ).toBe(1);
181
+ },
182
+ );
183
+
184
+ it("uses existing CLI credentials even when the agent advertises login methods", async () => {
185
+ const { agent, initialization } = createAgent();
186
+ agent.authenticate.mockRejectedValue(new Error("Method not implemented."));
187
+ client.agent = agent;
188
+ renderPanel();
189
+
190
+ await act(async () => initialization.resolve(advertisedAuth));
191
+ expect(agent.authenticate).not.toHaveBeenCalled();
192
+ expect(agent.newSession).toHaveBeenCalledOnce();
193
+ });
194
+
195
+ it("surfaces initialization failures and offers a connection retry", async () => {
196
+ const { agent, initialization } = createAgent();
197
+ client.agent = agent;
198
+ const { refresh } = renderPanel();
199
+
200
+ await act(async () => initialization.reject(new Error("Handshake failed")));
201
+ expect(await screen.findByText("Handshake failed")).toBeInTheDocument();
202
+ expectNoSessionRequests(agent);
203
+ fireEvent.click(screen.getByRole("button", { name: "Retry connection" }));
204
+ expect(disconnect).toHaveBeenCalledOnce();
205
+ expect(connect).toHaveBeenCalledTimes(2);
206
+
207
+ const retried = createAgent();
208
+ client.agent = retried.agent;
209
+ refresh();
210
+ expect(screen.queryByText("Handshake failed")).not.toBeInTheDocument();
211
+ expectNoSessionRequests(retried.agent);
212
+ await act(async () => retried.initialization.resolve(initialized));
213
+ expect(retried.agent.newSession).toHaveBeenCalledOnce();
214
+ });
215
+
216
+ it("surfaces missing CLI credentials and lets the user retry after login", async () => {
217
+ const { agent, initialization } = createAgent();
218
+ const authError = new JsonRpcError({
219
+ code: -32_000,
220
+ message: "Authentication required",
221
+ data: { message: "Run claude /login in the terminal" },
222
+ });
223
+ agent.newSession
224
+ .mockRejectedValueOnce(authError)
225
+ .mockRejectedValueOnce(authError);
226
+ client.agent = agent;
227
+ renderPanel();
228
+
229
+ await act(async () => initialization.resolve(advertisedAuth));
230
+ expect(
231
+ await screen.findByText("Run claude /login in the terminal"),
232
+ ).toBeInTheDocument();
233
+ expect(agent.authenticate).not.toHaveBeenCalled();
234
+ await act(async () => {
235
+ fireEvent.click(screen.getByRole("button", { name: "Restart session" }));
236
+ });
237
+ expect(agent.newSession).toHaveBeenCalledTimes(2);
238
+ expect(
239
+ screen.getByText("Run claude /login in the terminal"),
240
+ ).toBeInTheDocument();
241
+ await act(async () => {
242
+ fireEvent.click(screen.getByRole("button", { name: "Restart session" }));
243
+ });
244
+ expect(agent.newSession).toHaveBeenCalledTimes(3);
245
+ expect(
246
+ screen.queryByText("Run claude /login in the terminal"),
247
+ ).not.toBeInTheDocument();
248
+ });
249
+
250
+ it("does not authenticate or start a session for an agent switched away during initialization", async () => {
251
+ const old = createAgent();
252
+ const next = createAgent();
253
+ client.agent = old.agent;
254
+ renderPanel();
255
+ act(() => {
256
+ client.agent = next.agent;
257
+ store.set(agentSessionStateAtom, (state) =>
258
+ addSession(state, { agentId: "codex" }),
259
+ );
260
+ });
261
+
262
+ await act(async () => old.initialization.resolve(advertisedAuth));
263
+ expect(old.agent.authenticate).not.toHaveBeenCalled();
264
+ expectNoSessionRequests(old.agent);
265
+ expectNoSessionRequests(next.agent);
266
+ await act(async () => next.initialization.resolve(initialized));
267
+ expect(next.agent.newSession).toHaveBeenCalledOnce();
268
+ });
269
+
270
+ it("ignores a late initialization error from the previous agent", async () => {
271
+ const old = createAgent();
272
+ const next = createAgent();
273
+ client.agent = old.agent;
274
+ renderPanel();
275
+ act(() => {
276
+ client.agent = next.agent;
277
+ store.set(agentSessionStateAtom, (state) =>
278
+ addSession(state, { agentId: "codex" }),
279
+ );
280
+ });
281
+ await act(async () => next.initialization.resolve(initialized));
282
+ await act(async () =>
283
+ old.initialization.reject(new Error("Old agent failed")),
284
+ );
285
+ expect(screen.queryByText("Old agent failed")).not.toBeInTheDocument();
286
+ expect(next.agent.newSession).toHaveBeenCalledOnce();
287
+ });
288
+
289
+ it("ignores the old handshake when the same agent reconnects before it finishes", async () => {
290
+ const { agent, initialization } = createAgent();
291
+ const reinitialization = Promise.withResolvers<InitializeResponse>();
292
+ agent.initialize
293
+ .mockReturnValueOnce(initialization.promise)
294
+ .mockReturnValueOnce(reinitialization.promise);
295
+ client.agent = agent;
296
+ const { refresh } = renderPanel();
297
+
298
+ client.connectionState = { status: "disconnected" };
299
+ refresh();
300
+ client.connectionState = { status: "connected" };
301
+ refresh();
302
+ expect(agent.initialize).toHaveBeenCalledTimes(2);
303
+ await act(async () => initialization.resolve(advertisedAuth));
304
+ expect(agent.authenticate).not.toHaveBeenCalled();
305
+ expectNoSessionRequests(agent);
306
+ await act(async () => reinitialization.resolve(initialized));
307
+ expect(agent.newSession).toHaveBeenCalledOnce();
308
+ });
309
+
310
+ it("invalidates readiness on disconnect and reinitializes before resuming", async () => {
311
+ const { agent, initialization } = createAgent();
312
+ const reinitialization = Promise.withResolvers<InitializeResponse>();
313
+ agent.initialize
314
+ .mockReturnValueOnce(initialization.promise)
315
+ .mockReturnValueOnce(reinitialization.promise);
316
+ client.agent = agent;
317
+ const { refresh } = renderPanel();
318
+ await act(async () => initialization.resolve(initialized));
319
+ expect(agent.newSession).toHaveBeenCalledOnce();
320
+
321
+ client.connectionState = { status: "disconnected" };
322
+ refresh();
323
+ client.connectionState = { status: "connected" };
324
+ refresh();
325
+ expect(agent.loadSession).not.toHaveBeenCalled();
326
+ await act(async () => reinitialization.resolve(initialized));
327
+ expect(agent.loadSession).toHaveBeenCalledExactlyOnceWith({
328
+ sessionId: "new-session",
329
+ cwd: "/notebooks",
330
+ mcpServers: [],
331
+ });
332
+ expect(agent.newSession).toHaveBeenCalledOnce();
333
+ });
334
+
335
+ it("keeps a queued prompt pending until initialization and session loading finish", async () => {
336
+ const { agent, initialization } = createAgent();
337
+ const loading = Promise.withResolvers<undefined>();
338
+ agent.loadSession.mockImplementation(async ({ sessionId }) => {
339
+ await loading.promise;
340
+ client.activeSessionId = sessionId as ExternalAgentSessionId;
341
+ return {};
342
+ });
343
+ client.agent = agent;
344
+ client.activeSessionId = "saved-session" as ExternalAgentSessionId;
345
+ store.set(agentSessionStateAtom, (state) =>
346
+ updateSessionExternalAgentSessionId(
347
+ state,
348
+ "saved-session" as ExternalAgentSessionId,
349
+ ),
350
+ );
351
+ store.set(pendingAiPromptAtom, { prompt: "Help", submit: true });
352
+ renderPanel();
353
+ expect(store.get(pendingAiPromptAtom)).toEqual({
354
+ prompt: "Help",
355
+ submit: true,
356
+ });
357
+ expect(agent.prompt).not.toHaveBeenCalled();
358
+
359
+ await act(async () => initialization.resolve(initialized));
360
+ expect(agent.loadSession).toHaveBeenCalledOnce();
361
+ expect(agent.prompt).not.toHaveBeenCalled();
362
+ expect(store.get(pendingAiPromptAtom)).toEqual({
363
+ prompt: "Help",
364
+ submit: true,
365
+ });
366
+ await act(async () => loading.resolve(undefined));
367
+ expect(agent.prompt).toHaveBeenCalledOnce();
368
+ expect(agent.loadSession.mock.invocationCallOrder[0]).toBeLessThan(
369
+ agent.prompt.mock.invocationCallOrder[0],
370
+ );
371
+ expect(store.get(pendingAiPromptAtom)).toBeNull();
372
+ });
373
+
374
+ it("does not finish initialization after unmount", async () => {
375
+ const { agent, initialization } = createAgent();
376
+ client.agent = agent;
377
+ const { unmount } = renderPanel();
378
+ unmount();
379
+ await act(async () => initialization.resolve(advertisedAuth));
380
+ expect(agent.authenticate).not.toHaveBeenCalled();
381
+ expectNoSessionRequests(agent);
382
+ });
383
+ });
@@ -90,14 +90,16 @@ vi.mock("../context-utils", async (importOriginal) => ({
90
90
  }));
91
91
  vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
92
92
 
93
- function renderPanel() {
94
- return render(
93
+ async function renderPanel() {
94
+ const view = render(
95
95
  <Provider store={store}>
96
96
  <TooltipProvider>
97
97
  <AgentPanel />
98
98
  </TooltipProvider>
99
99
  </Provider>,
100
100
  );
101
+ await screen.findByRole("textbox", { name: "Prompt" });
102
+ return view;
101
103
  }
102
104
 
103
105
  function submitPrompt(prompt = "Explain this notebook") {
@@ -138,7 +140,7 @@ describe("AgentPanel prompt submission", () => {
138
140
 
139
141
  it("preserves the draft, attachment and title when the notebook is unnamed", async () => {
140
142
  store.set(filenameAtom, null);
141
- renderPanel();
143
+ await renderPanel();
142
144
  const title = store.get(selectedTabAtom)?.title;
143
145
  fireEvent.change(screen.getByLabelText("Attachment"), {
144
146
  target: {
@@ -181,7 +183,7 @@ describe("AgentPanel prompt submission", () => {
181
183
  "shows a rejected prompt and allows another submission after dismissal (%s)",
182
184
  async (error) => {
183
185
  agent.prompt.mockRejectedValueOnce(error);
184
- renderPanel();
186
+ await renderPanel();
185
187
  submitPrompt();
186
188
 
187
189
  expect(await screen.findByText(/Agent unavailable/)).toBeInTheDocument();
@@ -205,7 +207,7 @@ describe("AgentPanel prompt submission", () => {
205
207
  data: { message: "Sign in to the agent" },
206
208
  }),
207
209
  );
208
- renderPanel();
210
+ await renderPanel();
209
211
  submitPrompt();
210
212
 
211
213
  expect(await screen.findByText("Sign in to the agent")).toBeInTheDocument();
@@ -221,7 +223,7 @@ describe("AgentPanel prompt submission", () => {
221
223
  vi.mocked(parseContextFromPrompt).mockRejectedValueOnce(
222
224
  new Error("Context unavailable"),
223
225
  );
224
- renderPanel();
226
+ await renderPanel();
225
227
  submitPrompt();
226
228
 
227
229
  expect(await screen.findByText("Context unavailable")).toBeInTheDocument();
@@ -234,7 +236,7 @@ describe("AgentPanel prompt submission", () => {
234
236
 
235
237
  it("reports failures from a queued prompt through the same handler", async () => {
236
238
  agent.prompt.mockRejectedValueOnce(new Error("Queued prompt failed"));
237
- renderPanel();
239
+ await renderPanel();
238
240
  act(() =>
239
241
  store.set(pendingAiPromptAtom, {
240
242
  prompt: "Help with this cell",
@@ -255,7 +257,7 @@ describe("AgentPanel prompt submission", () => {
255
257
  it("clears busy state when a cancelled prompt settles", async () => {
256
258
  const response = Promise.withResolvers<{ stopReason: string }>();
257
259
  agent.prompt.mockReturnValueOnce(response.promise);
258
- renderPanel();
260
+ await renderPanel();
259
261
  submitPrompt();
260
262
  await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
261
263
  expect(screen.getByText("Agent is working...")).toBeInTheDocument();
@@ -673,14 +673,14 @@ describe("state utility functions", () => {
673
673
  it("should return correct command for claude on non-Windows", () => {
674
674
  vi.spyOn(shortcuts, "isPlatformWindows").mockReturnValue(false);
675
675
  expect(getAgentConnectionCommand("claude")).toMatchInlineSnapshot(`
676
- "npx stdio-to-ws "npx @zed-industries/claude-code-acp" --port 3017"
676
+ "npx stdio-to-ws "npx @agentclientprotocol/claude-agent-acp" --port 3017"
677
677
  `);
678
678
  });
679
679
 
680
680
  it("should return correct command for claude on Windows", () => {
681
681
  vi.spyOn(shortcuts, "isPlatformWindows").mockReturnValue(true);
682
682
  expect(getAgentConnectionCommand("claude")).toMatchInlineSnapshot(`
683
- "npx stdio-to-ws "cmd /c npx @zed-industries/claude-code-acp" --port 3017"
683
+ "npx stdio-to-ws "cmd /c npx @agentclientprotocol/claude-agent-acp" --port 3017"
684
684
  `);
685
685
  });
686
686