@marimo-team/frontend 0.24.3-dev64 → 0.24.3-dev69
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/{ConnectedDataExplorerComponent-BSvwB270.js → ConnectedDataExplorerComponent-B8qRI1J3.js} +1 -1
- package/dist/assets/{ErrorBoundary-TsEoIDRr.js → ErrorBoundary-B24BGOv-.js} +2 -2
- package/dist/assets/{LazyAnyLanguageCodeMirror-BSKy620l.js → LazyAnyLanguageCodeMirror-BiRrYKh3.js} +2 -2
- package/dist/assets/{RunButton-Dul9caMU.js → RunButton-CCeB-E93.js} +1 -1
- package/dist/assets/__vite-browser-external-Cw9Ccr_O.js +1 -0
- package/dist/assets/__vite-browser-external-DBuuG05M.js +1 -0
- package/dist/assets/{abnfDiagram-VCTEODGH-Cjyff0Dj.js → abnfDiagram-VCTEODGH-BAf7PJTE.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BhDPL21V.js → add-cell-with-ai-Bfw26bS2.js} +1 -1
- package/dist/assets/{agent-panel-CYUY6ClW.js → agent-panel-DDQFUPQQ.js} +4 -4
- package/dist/assets/{ai-model-dropdown-CnDRzQfQ.js → ai-model-dropdown-BveigXNm.js} +1 -1
- package/dist/assets/{any-language-editor-C6lBHXNY.js → any-language-editor-qIp2874m.js} +1 -1
- package/dist/assets/{app-config-button-9I3Y-dHh.js → app-config-button-D12AZ5Jw.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-BBYvsdwV.js → architectureDiagram-5GKGNRK7-DODMm5nu.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-XAi6eabU.js → blockDiagram-I7D4REHJ-CwnPIH04.js} +1 -1
- package/dist/assets/{cache-panel-DKXBQfDt.js → cache-panel-B_aqFT1r.js} +1 -1
- package/dist/assets/{cells-DdE_4Bcx.js → cells-BY2lhea7.js} +1 -1
- package/dist/assets/{chat-display-BlIEWw3Q.js → chat-display-DJTGR5xt.js} +1 -1
- package/dist/assets/{chat-panel-C_XE2bS-.js → chat-panel-Csz1liLp.js} +1 -1
- package/dist/assets/{chat-ui-BRq__jk9.js → chat-ui-CQkBUB_Q.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-bmBDB0cN.js → chunk-IMKFNOWR-CMvWRWPq.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-DFrgoI5E.js → chunk-SHT3W25Y-XssGvFwO.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-usES5uzt.js → chunk-SVP7TREG-JYPY-7he.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-DmYGvTDJ.js → chunk-TICWLB2K-DmYV5I8j.js} +1 -1
- package/dist/assets/{classDiagram-ZZMXUADV-8oWUrTSA.js → classDiagram-ZZMXUADV-DGESMhgO.js} +1 -1
- package/dist/assets/{classDiagram-v2-VYDZK3BY-8oWUrTSA.js → classDiagram-v2-VYDZK3BY-DGESMhgO.js} +1 -1
- package/dist/assets/{code-visibility-ChrgD3_b.js → code-visibility-DcZtpJ7Y.js} +1 -1
- package/dist/assets/{command-palette-D_70H77j.js → command-palette-DB5rqESX.js} +1 -1
- package/dist/assets/{common-BfKDjmi0.js → common-DUhY30A6.js} +1 -1
- package/dist/assets/{components-DyBejhtt.js → components-BTJRth05.js} +1 -1
- package/dist/assets/{components-BFKAEF3R.js → components-mqvQ3nnU.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-CGpZt6Mr.js → cynefinDiagram-5FMLGOSQ-5s8Gxv8p.js} +1 -1
- package/dist/assets/{dagre-6C2JCFmZ.js → dagre-DIObFfj5.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-DwPg2iyK.js → dagre-GXQ25YYZ-DLWNkQz-.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-BHzBco-G.js → data-grid-overlay-editor-BhBuKg9_.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DxbTpdSy.js → dependency-graph-panel-DbYPnFAW.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-D3rnjIpt.js → diagram-S7CK7UJ4-Vc_7tbGR.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN--mvb4nAR.js → diagram-UQ7AKVKN-sFxmIB_B.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-DLd2xbGj.js → diagram-VX7I27RA-CeT45gHf.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-DCQ7kQxu.js → diagram-Z3DM3KII-3qePplNH.js} +1 -1
- package/dist/assets/{documentation-panel-D1oy9mJX.js → documentation-panel-StHaPmjq.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-BEPSCGTU.js → ebnfDiagram-PWID7BFC-fltqffql.js} +1 -1
- package/dist/assets/{edit-page-KRRlhyb9.js → edit-page-ByS6kRQA.js} +4 -4
- package/dist/assets/{erDiagram-RLTQ6QDP-DVP8JuYC.js → erDiagram-RLTQ6QDP-Bow67h2D.js} +1 -1
- package/dist/assets/{error-entries-8TMYgS7M.js → error-entries-BGsjaQkq.js} +1 -1
- package/dist/assets/{error-panel-DSHUnrwV.js → error-panel-CD7f6MTl.js} +1 -1
- package/dist/assets/{feedback-button-C-rqcadY.js → feedback-button-BfgkWpt7.js} +1 -1
- package/dist/assets/{file-explorer-panel-W4vRK0IK.js → file-explorer-panel-BAL_Pzqi.js} +1 -1
- package/dist/assets/{file-name-input-BQURTAD0.js → file-name-input-ClwxBT3X.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-CxI8veho.js → flowDiagram-HODETNUW-CC0MXK8f.js} +1 -1
- package/dist/assets/{gallery-page-CCiwxBoC.js → gallery-page-BfqIPyEv.js} +1 -1
- package/dist/assets/{glide-data-editor-YdUhj2-W.js → glide-data-editor-cUeC-J43.js} +4 -4
- package/dist/assets/{graphlib-Dqk_AhmH.js → graphlib-DNXvxVH3.js} +1 -1
- package/dist/assets/has-BWBvNXSE.js +1 -0
- package/dist/assets/{home-page-Bhfe_Ooj.js → home-page-CZOroZdb.js} +1 -1
- package/dist/assets/{index-kGHM0O4l.js → index-DPnCyt1L.js} +35 -35
- package/dist/assets/{infoDiagram-27XIBGKW-vkRRAmNG.js → infoDiagram-27XIBGKW-DUckrGOc.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-DwnkA3qK.js → ishikawaDiagram-5VMMS53U-BI5lrbqL.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-DTM4IJzH.js → kanban-definition-UXKFOSKX-BE_mJxg6.js} +1 -1
- package/dist/assets/{logs-panel-CpmZ43Xd.js → logs-panel-X7YYNXWi.js} +1 -1
- package/dist/assets/{mermaid-BYaoBhgB.js → mermaid-I9sRPejB.js} +4 -4
- package/dist/assets/{mindmap-definition-YA3MSWOX-BtrIeuN2.js → mindmap-definition-YA3MSWOX-BMFx2VBe.js} +1 -1
- package/dist/assets/{number-overlay-editor-uOD-jX8J.js → number-overlay-editor-EQ27ERwk.js} +1 -1
- package/dist/assets/{outline-panel-DHBB8jr2.js → outline-panel-DbnyiE-B.js} +1 -1
- package/dist/assets/{packages-panel-Ba0BIKw-.js → packages-panel-wCsQlp5Z.js} +1 -1
- package/dist/assets/{panels-CVG8esk7.js → panels-8_Hfok8J.js} +3 -3
- package/dist/assets/{pegDiagram-XKGWAZYB-72Js5lku.js → pegDiagram-XKGWAZYB-Bke1_i45.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-Bj3RCLfN.js → pieDiagram-E7YTZNPT-4n1GeAi8.js} +1 -1
- package/dist/assets/{process-output-cqVv_LQh.js → process-output-DDr6kJst.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-B10zJ5xk.js → railroadDiagram-O6MQD6OU-DzSnQBmP.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-BF4LA-gO.js → requirementDiagram-BXWQKSXE-Bjqcm878.js} +1 -1
- package/dist/assets/{reveal-component-Cvagx4WC.js → reveal-component-MnNIqkZg.js} +1 -1
- package/dist/assets/{run-page-O9BXJwTz.js → run-page-BGQahlCc.js} +1 -1
- package/dist/assets/{save-worker-CMa6Cvnx.js → save-worker-Dw8ybFWq.js} +3 -3
- package/dist/assets/{scratchpad-panel-CJacTdzi.js → scratchpad-panel-Wt_FZRjv.js} +1 -1
- package/dist/assets/{secrets-panel-BvtVCy_x.js → secrets-panel-DiIOsz3V.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-Cz9d4L3l.js → sequenceDiagram-WJ2MYXX4-r-c3v7Ho.js} +1 -1
- package/dist/assets/{session-panel-C7l7daPJ.js → session-panel-F6G-YcGG.js} +1 -1
- package/dist/assets/{snippets-panel-BP4UI0bt.js → snippets-panel-mWSX1anY.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-BYVxo4-Y.js → stateDiagram-D77RDMKH-COUIwUQY.js} +1 -1
- package/dist/assets/{stateDiagram-v2-MP3YSRHH-BO2Qvl2_.js → stateDiagram-v2-MP3YSRHH-DxCCjAQm.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-nJMxtt_e.js → swimlanes-42K2YHIH-CFQCC6A2.js} +1 -1
- package/dist/assets/{swimlanesDiagram-VR7AAH4N-2_BeIocM.js → swimlanesDiagram-VR7AAH4N-BEVDZiRo.js} +1 -1
- package/dist/assets/{swiper-component-VHJ-MBld.js → swiper-component-DGc_akcB.js} +1 -1
- package/dist/assets/{terminal-DfWCJPGz.js → terminal-BdlA-Q6e.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-LaRikknb.js → timeline-definition-24CTP7MA-BJ-VJeHJ.js} +1 -1
- package/dist/assets/{tracing-njWlk06F.js → tracing-Bsl9kRwF.js} +1 -1
- package/dist/assets/{tracing-panel-BFMefFq8.js → tracing-panel-Cpes4Agb.js} +2 -2
- package/dist/assets/{useCellActionButton-Cie43jOq.js → useCellActionButton-BEOOTct7.js} +1 -1
- package/dist/assets/{vega-component-CrJlHxXg.js → vega-component-UjBXVqkX.js} +1 -1
- package/dist/assets/{vega-embed-container-DkDtQznq.js → vega-embed-container-NxkXguyz.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-y3U656IE.js → vennDiagram-4TSXK5OY-DT7_rCm5.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-COdor5IG.js → wardleyDiagram-VM6X3IG4-Ce6qiu-G.js} +1 -1
- package/dist/assets/{worker-B8c-1v-F.js → worker-ByzyDs4N.js} +3 -3
- package/dist/assets/{xychartDiagram-S5SC5T6Z-BHauP6L-.js → xychartDiagram-S5SC5T6Z-jjkhfqC-.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +383 -0
- package/src/components/chat/acp/__tests__/agent-panel.test.tsx +10 -8
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/agent-panel.tsx +70 -30
- package/src/components/chat/acp/state.ts +1 -1
- package/src/core/cells/__tests__/cells.test.ts +144 -0
- package/src/core/cells/cells.ts +7 -17
- package/src/core/wasm/__tests__/bridge.test.ts +5 -4
- package/src/core/wasm/__tests__/runtime-config.test.ts +8 -1
- package/src/core/wasm/bridge.ts +7 -8
- package/src/core/wasm/runtime-config.ts +4 -0
- package/src/core/wasm/worker/constants.ts +1 -0
- package/src/core/wasm/worker/getController.ts +3 -1
- package/src/core/wasm/worker/save-worker.ts +4 -10
- package/src/core/wasm/worker/types.ts +0 -1
- package/src/core/wasm/worker/worker.ts +4 -10
- package/dist/assets/__vite-browser-external-BKSyd8Ok.js +0 -1
- package/dist/assets/__vite-browser-external-ZTY8EBdt.js +0 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
@@ -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
|
-
|
|
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 @
|
|
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 @
|
|
683
|
+
"npx stdio-to-ws "cmd /c npx @agentclientprotocol/claude-agent-acp" --port 3017"
|
|
684
684
|
`);
|
|
685
685
|
});
|
|
686
686
|
|