@marimo-team/islands 0.25.3-dev250 → 0.25.3-dev252
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/{ConnectedDataExplorerComponent-BBihxHrr.js → ConnectedDataExplorerComponent-DGOAQFtf.js} +11 -11
- package/dist/{ErrorBoundary-Ks8L_DFj.js → ErrorBoundary-By2DNeOL.js} +7 -7
- package/dist/{abnfDiagram-VCTEODGH-DBcJVxYP.js → abnfDiagram-VCTEODGH-DzrZZXRD.js} +2 -2
- package/dist/{any-language-editor-jGaR0EAo.js → any-language-editor-Be7dA2VK.js} +3 -3
- package/dist/{architectureDiagram-5GKGNRK7-BcHZXF4S.js → architectureDiagram-5GKGNRK7-iF_snyFL.js} +1 -1
- package/dist/{button-B6RjDKJx.js → button-0-RqY1DW.js} +2 -1
- package/dist/{capabilities-CkVzWJEX.js → capabilities-B6yy_CBN.js} +1 -1
- package/dist/{chat-ui-DgaVxFrc.js → chat-ui-zbfp80GW.js} +1547 -1482
- package/dist/{check-Z_vtI3OL.js → check-CSvxZk5I.js} +1 -1
- package/dist/{chunk-IMKFNOWR-CTZlwBAA.js → chunk-IMKFNOWR-B7xytD4B.js} +1 -1
- package/dist/{chunk-SHT3W25Y-53g_RZHm.js → chunk-SHT3W25Y-I8ZFP5L1.js} +1 -1
- package/dist/{chunk-SVP7TREG-P6abNGV_.js → chunk-SVP7TREG-Di-uAPdT.js} +1 -1
- package/dist/{chunk-TICWLB2K-BTJJqkyF.js → chunk-TICWLB2K-BFpUy0ud.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-BUGqOrTm.js → classDiagram-ZZMXUADV-Cqm4JGpx.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-CXpQvO-s.js → classDiagram-v2-VYDZK3BY-DFHRJpN3.js} +2 -2
- package/dist/{code-visibility-CCCqFZYh.js → code-visibility-Byb3BN4h.js} +4 -4
- package/dist/{common-JXDK9IjO.js → common-CgWyYqpC.js} +262 -262
- package/dist/{config-DbjzjwxX.js → config-mwEjAmvP.js} +2 -2
- package/dist/{copy-icon-CG57cxwb.js → copy-icon-DD8bdw4L.js} +3 -3
- package/dist/{createReducer-DePYJ0F3.js → createReducer-Bx81cw8_.js} +1 -1
- package/dist/{cynefinDiagram-5FMLGOSQ-DOcnl8wW.js → cynefinDiagram-5FMLGOSQ-cuGOeTBC.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CuHR9QfG.js → dagre-GXQ25YYZ-DJ76aC4k.js} +1 -1
- package/dist/{data-grid-overlay-editor-QAJSeJpH.js → data-grid-overlay-editor-BP_fYnBa.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BPiG4kso.js → diagram-S7CK7UJ4-DLLc0sw7.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-_piBOvOC.js → diagram-UQ7AKVKN-Tz_d6qmk.js} +1 -1
- package/dist/{diagram-VX7I27RA-DCzVeJ8M.js → diagram-VX7I27RA-D7SzPrDN.js} +1 -1
- package/dist/{diagram-Z3DM3KII-D64CF1jQ.js → diagram-Z3DM3KII-qr37XV58.js} +1 -1
- package/dist/{dialog-DsZ49mmU.js → dialog-BHYPX87J.js} +3 -3
- package/dist/{dropdown-menu-BRiE7Vnp.js → dropdown-menu-Blu7V6Mk.js} +6 -6
- package/dist/{ebnfDiagram-PWID7BFC-BapQPML-.js → ebnfDiagram-PWID7BFC-BKOQb4J0.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CNQvC2_Q.js → erDiagram-RLTQ6QDP-CM15Vq68.js} +1 -1
- package/dist/{error-banner-IsonnvA0.js → error-banner-DbRFOd2O.js} +2 -2
- package/dist/{error-entries-CRPfxMqm.js → error-entries-BuIE3Ius.js} +9 -9
- package/dist/{feedback-button-Bg7zGcid.js → feedback-button-DPWFUVWl.js} +7 -7
- package/dist/{flowDiagram-HODETNUW-BBt6S6oe.js → flowDiagram-HODETNUW-Dt3N5yFT.js} +2 -2
- package/dist/{formats-BK4VefeF.js → formats-gGospKWJ.js} +2 -2
- package/dist/{glide-data-editor-BuFe7QfV.js → glide-data-editor-GvhMU_kg.js} +10 -10
- package/dist/{glide-utils-CQddGpa-.js → glide-utils-qbkRZpEy.js} +3 -3
- package/dist/{globals-8EK96XwF.js → globals-RlhgvYCL.js} +2 -2
- package/dist/{html-to-image-Gx4q3ZoD.js → html-to-image-BXrAd5q8.js} +460 -455
- package/dist/{infoDiagram-27XIBGKW-Hd8Ifz82.js → infoDiagram-27XIBGKW-C7axJMCl.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CP7hnHWx.js → ishikawaDiagram-5VMMS53U-CdC9Eur7.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CX91sa9b.js → kanban-definition-UXKFOSKX-BHnMIgD7.js} +1 -1
- package/dist/{loader-BisjOLiW.js → loader-BVEtQ4F3.js} +1 -1
- package/dist/main.js +236 -236
- package/dist/{mermaid-Djb3Ni9t.js → mermaid-DFYN_PXd.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-DgCNo07O.js → mindmap-definition-YA3MSWOX-BagHoCGU.js} +1 -1
- package/dist/{number-overlay-editor-BDJF1c8h.js → number-overlay-editor-Dg1EfcEs.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-SGJhnIKo.js → pegDiagram-XKGWAZYB-8IIZczip.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-1stXIWCQ.js → pieDiagram-E7YTZNPT-CE6dKBIr.js} +1 -1
- package/dist/{process-output-D2ISIyIs.js → process-output-D2ZjxrHe.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-DY1mojzB.js → railroadDiagram-O6MQD6OU-CkexPq3b.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-gHzhUp0D.js → requirementDiagram-BXWQKSXE-B0-OkuAe.js} +1 -1
- package/dist/{reveal-component-B_hNJfj-.js → reveal-component-UPs4Rd1M.js} +8 -8
- package/dist/{select-BSxlITBo.js → select-CcidPqlM.js} +5 -5
- package/dist/{sequenceDiagram-WJ2MYXX4-rjPtD9fB.js → sequenceDiagram-WJ2MYXX4-Dxysg8DX.js} +1 -1
- package/dist/{spec-Dq8RNplp.js → spec-ZwfZcgsm.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DMNxjUxV.js → stateDiagram-D77RDMKH-DTEN3-OJ.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CumAaqGo.js → stateDiagram-v2-MP3YSRHH-vktk3Xc5.js} +2 -2
- package/dist/{strings-BGW-fXVJ.js → strings-BYJNyKmg.js} +3 -3
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-BU14_Tej.js → swimlanes-42K2YHIH-Cjveh9OM.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-CieAnPqJ.js → swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js} +2 -2
- package/dist/{swiper-component-Dir1o8ml.js → swiper-component-Dvj-goaD.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-CrVj4E1K.js → timeline-definition-24CTP7MA-KIuDcefn.js} +1 -1
- package/dist/{toast-BWIPi3aF.js → toast-CkB5Jq9K.js} +4 -4
- package/dist/{tooltip-C3UnvsRe.js → tooltip-Bzl4k00l.js} +2 -2
- package/dist/{use-restore-focus-BWO0z1lo.js → use-restore-focus-CmflVLWJ.js} +1 -1
- package/dist/{useDateFormatter-ChB4eyim.js → useDateFormatter-CYo5fm8s.js} +2 -2
- package/dist/{useIframeCapabilities-cMNQ80CK.js → useIframeCapabilities-BIolGsF1.js} +1 -1
- package/dist/{useLifecycle-BquH9ET-.js → useLifecycle-GNgvNwI-.js} +2 -2
- package/dist/{useTheme-DgOLVISn.js → useTheme-BZ2GGLgm.js} +141 -139
- package/dist/{vega-component-Bwg_Kuh-.js → vega-component-DOtem7Hf.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-ZLxQ8yhm.js → vennDiagram-4TSXK5OY-DrIOUHfb.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-D88pcLY1.js → wardleyDiagram-VM6X3IG4-CJNjf4OH.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-_13d4g8g.js → xychartDiagram-S5SC5T6Z-DEshFho-.js} +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/pair-config.ts +1 -1
- package/src/components/app-config/__tests__/state.test.tsx +50 -0
- package/src/components/app-config/ai-config.tsx +1 -1
- package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
- package/src/components/chat/__tests__/chat-display.test.tsx +125 -0
- package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
- package/src/components/chat/__tests__/chat-utils.test.ts +51 -1
- package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
- package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
- package/src/components/chat/acp/agent-panel.tsx +4 -3
- package/src/components/chat/acp/blocks.tsx +10 -10
- package/src/components/chat/acp/common.tsx +4 -5
- package/src/components/chat/acp/model-selector.tsx +1 -1
- package/src/components/chat/capabilities-popover.tsx +1 -6
- package/src/components/chat/chat-components.tsx +7 -4
- package/src/components/chat/chat-display.tsx +51 -6
- package/src/components/chat/chat-history-popover.tsx +6 -1
- package/src/components/chat/chat-markdown.tsx +15 -0
- package/src/components/chat/chat-panel.tsx +105 -119
- package/src/components/chat/chat-typography.css +108 -0
- package/src/components/chat/chat-utils.ts +8 -3
- package/src/components/chat/notebook-access-selector.tsx +1 -1
- package/src/components/chat/reasoning-accordion.tsx +3 -3
- package/src/components/chat/tool-call/tool-activity-group.tsx +49 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +3 -30
- package/src/components/chat/tool-call/tool-result.tsx +1 -1
- package/src/components/chat/use-chat-generation.ts +19 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +18 -37
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +5 -8
- package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +5 -19
- package/src/components/editor/actions/pair-with-agent-commands.ts +0 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +7 -11
- package/src/components/editor/actions/pair-with-agent.tsx +3 -31
- package/src/components/editor/actions/useCellActionButton.tsx +1 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/prompt-input.tsx +189 -0
- package/src/components/editor/cell/StagedAICell.tsx +3 -3
- package/src/components/editor/cell/code/cell-editor.tsx +14 -1
- package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
- package/src/components/editor/chrome/types.ts +1 -1
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +53 -45
- package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +89 -72
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +5 -8
- package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
- package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
- package/src/components/editor/renderers/cell-array.tsx +10 -67
- package/src/components/markdown/markdown-renderer.tsx +35 -20
- package/src/components/mcp/mcp-status-indicator.tsx +18 -14
- package/src/core/ai/__tests__/staged-cells.test.ts +1 -467
- package/src/core/ai/context/context.ts +2 -0
- package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
- package/src/core/ai/context/providers/cell.ts +54 -0
- package/src/core/ai/staged-cells.ts +4 -280
- package/src/core/ai/state.ts +3 -0
- package/src/core/codemirror/__tests__/setup.test.ts +32 -0
- package/src/core/codemirror/cells/state.ts +1 -0
- package/src/core/codemirror/cm.ts +5 -1
- package/src/core/codemirror/config/types.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +16 -0
- package/src/core/codemirror/placeholder/extensions.ts +57 -19
- package/src/core/hotkeys/hotkeys.ts +2 -1
- package/src/core/notebook/document/__tests__/codemirror.test.ts +1 -0
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +1 -1
- package/src/plugins/impl/chat/chat-ui.tsx +1 -1
- package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
- package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
- package/src/components/editor/ai/staged-cell-submission.ts +0 -46
- package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -66
|
@@ -6,7 +6,7 @@ import { pairConfigSchema } from "@/core/config/pair";
|
|
|
6
6
|
|
|
7
7
|
// Read the Python-owned resource only in tests to check the shared contract.
|
|
8
8
|
export const PAIR_CONFIG = pairConfigSchema.parse({
|
|
9
|
-
command: "
|
|
9
|
+
command: "marimo",
|
|
10
10
|
hostEnabled: true,
|
|
11
11
|
templates: JSON.parse(
|
|
12
12
|
readFileSync(
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import { ConfigButton } from "../app-config-button";
|
|
8
|
+
import { aiSettingsSubTabAtom, useOpenSettingsToTab } from "../state";
|
|
9
|
+
import { activeUserConfigCategoryAtom } from "../user-config-form";
|
|
10
|
+
|
|
11
|
+
vi.mock("../user-config-form", async () => {
|
|
12
|
+
const { atom } = await import("jotai");
|
|
13
|
+
return {
|
|
14
|
+
activeUserConfigCategoryAtom: atom("editor"),
|
|
15
|
+
UserConfigForm: () => <div>Settings form</div>,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
vi.mock("../app-config-form", () => ({
|
|
19
|
+
AppConfigForm: () => null,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
function OpenAiSettings() {
|
|
23
|
+
const { handleClick } = useOpenSettingsToTab();
|
|
24
|
+
return (
|
|
25
|
+
<button type="button" onClick={() => handleClick("ai")}>
|
|
26
|
+
AI Settings
|
|
27
|
+
</button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("useOpenSettingsToTab", () => {
|
|
32
|
+
it("opens the settings dialog on the AI category from an external button", () => {
|
|
33
|
+
const store = createStore();
|
|
34
|
+
store.set(aiSettingsSubTabAtom, "ai-providers");
|
|
35
|
+
render(
|
|
36
|
+
<Provider store={store}>
|
|
37
|
+
<TooltipProvider>
|
|
38
|
+
<OpenAiSettings />
|
|
39
|
+
<ConfigButton />
|
|
40
|
+
</TooltipProvider>
|
|
41
|
+
</Provider>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
fireEvent.click(screen.getByRole("button", { name: "AI Settings" }));
|
|
45
|
+
|
|
46
|
+
expect(screen.getByRole("dialog", { name: "User settings" })).toBeVisible();
|
|
47
|
+
expect(store.get(activeUserConfigCategoryAtom)).toBe("ai");
|
|
48
|
+
expect(store.get(aiSettingsSubTabAtom)).toBe("ai-features");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1351,7 +1351,7 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
|
1351
1351
|
description={
|
|
1352
1352
|
<span>
|
|
1353
1353
|
Model to use for code editing with the{" "}
|
|
1354
|
-
<Kbd className="inline">
|
|
1354
|
+
<Kbd className="inline">Edit with AI</Kbd> action.
|
|
1355
1355
|
</span>
|
|
1356
1356
|
}
|
|
1357
1357
|
forRole="edit"
|
|
@@ -27,7 +27,6 @@ describe("CapabilitiesPopover", () => {
|
|
|
27
27
|
|
|
28
28
|
expect(webSearch).toBeChecked();
|
|
29
29
|
expect(trigger).toHaveAttribute("data-active", "true");
|
|
30
|
-
expect(trigger).toHaveClass("bg-primary/15", "text-primary", "ring-1");
|
|
31
30
|
expect(trigger).toHaveAttribute(
|
|
32
31
|
"title",
|
|
33
32
|
"Capabilities (web search enabled)",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import type { ToolUIPart, UIMessage } from "ai";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { renderUIMessage } from "../chat-display";
|
|
7
|
+
|
|
8
|
+
vi.mock("../tool-call/tool-call-view", () => ({
|
|
9
|
+
ToolCallView: ({ toolName }: { toolName: string }) => <div>{toolName}</div>,
|
|
10
|
+
}));
|
|
11
|
+
vi.mock("../../markdown/markdown-renderer", () => ({
|
|
12
|
+
MarkdownRenderer: ({ content }: { content: string }) => <p>{content}</p>,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const done = (name = "edit_notebook_tool"): ToolUIPart => ({
|
|
16
|
+
type: `tool-${name}`,
|
|
17
|
+
toolCallId: crypto.randomUUID(),
|
|
18
|
+
state: "output-available",
|
|
19
|
+
input: {},
|
|
20
|
+
output: {},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function show(parts: UIMessage["parts"], isActive = false) {
|
|
24
|
+
return render(
|
|
25
|
+
renderUIMessage({
|
|
26
|
+
message: { id: "message", role: "assistant", parts },
|
|
27
|
+
isStreamingReasoning: false,
|
|
28
|
+
isLast: true,
|
|
29
|
+
isActive,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("chat tool activity", () => {
|
|
35
|
+
it("collapses consecutive edits across SDK step boundaries and exposes details on demand", () => {
|
|
36
|
+
show([done(), { type: "step-start" }, done(), done(), done()]);
|
|
37
|
+
const summary = screen.getByRole("button", {
|
|
38
|
+
name: "Edited notebook · 4 operations",
|
|
39
|
+
});
|
|
40
|
+
expect(summary.getAttribute("aria-expanded")).toBe("false");
|
|
41
|
+
expect(screen.queryByText("tool-edit_notebook_tool")).toBeNull();
|
|
42
|
+
fireEvent.click(summary);
|
|
43
|
+
expect(screen.getAllByText("tool-edit_notebook_tool")).toHaveLength(4);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("starts a new group after prose and summarizes mixed tool calls", () => {
|
|
47
|
+
show([
|
|
48
|
+
done(),
|
|
49
|
+
{ type: "text", text: "Checking the result." },
|
|
50
|
+
done("lint_notebook"),
|
|
51
|
+
{
|
|
52
|
+
type: "dynamic-tool",
|
|
53
|
+
toolName: "get_lightweight_cell_map",
|
|
54
|
+
toolCallId: "dynamic",
|
|
55
|
+
state: "output-available",
|
|
56
|
+
input: {},
|
|
57
|
+
output: {},
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
expect(screen.getByText("Checking the result.")).toBeTruthy();
|
|
61
|
+
expect(
|
|
62
|
+
screen.getByRole("button", { name: "Edited notebook · 1 operation" }),
|
|
63
|
+
).toBeTruthy();
|
|
64
|
+
expect(
|
|
65
|
+
screen.getByRole("button", { name: "Completed 2 steps" }),
|
|
66
|
+
).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps errors, denials, and approval requests outside collapsed groups", () => {
|
|
70
|
+
show([
|
|
71
|
+
done(),
|
|
72
|
+
{
|
|
73
|
+
type: "tool-failed",
|
|
74
|
+
toolCallId: "error",
|
|
75
|
+
state: "output-error",
|
|
76
|
+
input: {},
|
|
77
|
+
errorText: "Failed",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "tool-approval",
|
|
81
|
+
toolCallId: "approval",
|
|
82
|
+
state: "approval-requested",
|
|
83
|
+
input: {},
|
|
84
|
+
approval: { id: "approval" },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: "tool-denied",
|
|
88
|
+
toolCallId: "denied",
|
|
89
|
+
state: "output-denied",
|
|
90
|
+
input: {},
|
|
91
|
+
approval: { id: "denied", approved: false },
|
|
92
|
+
},
|
|
93
|
+
done(),
|
|
94
|
+
]);
|
|
95
|
+
expect(screen.getByText("tool-failed")).toBeTruthy();
|
|
96
|
+
expect(screen.getByText("tool-approval")).toBeTruthy();
|
|
97
|
+
expect(screen.getByText("tool-denied")).toBeTruthy();
|
|
98
|
+
expect(
|
|
99
|
+
screen.getAllByRole("button", { name: "Edited notebook · 1 operation" }),
|
|
100
|
+
).toHaveLength(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("distinguishes running edits from interrupted activity", () => {
|
|
104
|
+
const parts: UIMessage["parts"] = [
|
|
105
|
+
done(),
|
|
106
|
+
{
|
|
107
|
+
type: "tool-edit_notebook_tool",
|
|
108
|
+
toolCallId: "pending",
|
|
109
|
+
state: "input-available",
|
|
110
|
+
input: {},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
const view = show(parts, true);
|
|
114
|
+
expect(
|
|
115
|
+
screen.getByRole("button", { name: "Editing notebook…" }),
|
|
116
|
+
).toBeTruthy();
|
|
117
|
+
view.unmount();
|
|
118
|
+
show(parts);
|
|
119
|
+
expect(
|
|
120
|
+
screen.getByRole("button", {
|
|
121
|
+
name: "Stopped before completing all steps",
|
|
122
|
+
}),
|
|
123
|
+
).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import { expect, it, vi } from "vitest";
|
|
6
|
+
import { ChatMarkdown } from "../chat-markdown";
|
|
7
|
+
|
|
8
|
+
const { aiAtom } = await vi.hoisted(async () => {
|
|
9
|
+
const { atom } = await import("jotai");
|
|
10
|
+
return {
|
|
11
|
+
aiAtom: atom<{ mode?: "ask" | "manual" | "agent" | "code_mode" }>({}),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
vi.mock("@/core/config/config", async (importOriginal) => ({
|
|
15
|
+
...(await importOriginal<object>()),
|
|
16
|
+
aiAtom,
|
|
17
|
+
}));
|
|
18
|
+
vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
|
|
19
|
+
LazyAnyLanguageCodeMirror: ({ value }: { value: string }) => (
|
|
20
|
+
<pre>{value}</pre>
|
|
21
|
+
),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
it("updates snippet actions when switching between read-only and editing modes", async () => {
|
|
25
|
+
const store = createStore();
|
|
26
|
+
render(
|
|
27
|
+
<Provider store={store}>
|
|
28
|
+
<ChatMarkdown content={"```python\nx = 1\n```"} />
|
|
29
|
+
</Provider>,
|
|
30
|
+
);
|
|
31
|
+
expect(
|
|
32
|
+
await screen.findByRole("button", { name: "Add to Notebook" }),
|
|
33
|
+
).toBeInTheDocument();
|
|
34
|
+
for (const mode of ["agent", "code_mode", "ask", "manual"] as const) {
|
|
35
|
+
act(() => store.set(aiAtom, { mode }));
|
|
36
|
+
expect(screen.getByRole("button", { name: /^Copy$/ })).toBeInTheDocument();
|
|
37
|
+
expect(screen.getByText("x = 1")).toBeInTheDocument();
|
|
38
|
+
const insertButton = screen.queryByRole("button", {
|
|
39
|
+
name: "Add to Notebook",
|
|
40
|
+
});
|
|
41
|
+
expect(insertButton !== null).toBe(mode === "ask" || mode === "manual");
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { UIMessage } from "ai";
|
|
4
|
-
import {
|
|
4
|
+
import { createStore } from "jotai";
|
|
5
|
+
import { FRONTEND_TOOL_REGISTRY } from "@/core/ai/tools/registry";
|
|
6
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
7
|
import {
|
|
8
|
+
handleToolCall,
|
|
6
9
|
hasPendingToolCalls,
|
|
7
10
|
hasUnresolvedToolCalls,
|
|
8
11
|
shouldFlushQueue,
|
|
@@ -510,3 +513,50 @@ describe("shouldFlushQueue", () => {
|
|
|
510
513
|
).toBe(false);
|
|
511
514
|
});
|
|
512
515
|
});
|
|
516
|
+
|
|
517
|
+
describe("handleToolCall", () => {
|
|
518
|
+
it.each(["frontend", "backend", "exception"])(
|
|
519
|
+
"reports %s failures as errors rather than successful output",
|
|
520
|
+
async (source) => {
|
|
521
|
+
const hasTool = vi
|
|
522
|
+
.spyOn(FRONTEND_TOOL_REGISTRY, "has")
|
|
523
|
+
.mockReturnValue(source === "frontend");
|
|
524
|
+
const response = {
|
|
525
|
+
tool_name: "test_tool",
|
|
526
|
+
result: null,
|
|
527
|
+
error: "Edit failed",
|
|
528
|
+
};
|
|
529
|
+
const invokeFrontend = vi
|
|
530
|
+
.spyOn(FRONTEND_TOOL_REGISTRY, "invoke")
|
|
531
|
+
.mockResolvedValue(response);
|
|
532
|
+
const invokeAiTool = vi.fn().mockImplementation(async () => {
|
|
533
|
+
if (source === "exception") {
|
|
534
|
+
throw new Error("Edit failed");
|
|
535
|
+
}
|
|
536
|
+
return response;
|
|
537
|
+
});
|
|
538
|
+
const addToolOutput = vi.fn();
|
|
539
|
+
try {
|
|
540
|
+
await handleToolCall({
|
|
541
|
+
invokeAiTool,
|
|
542
|
+
addToolOutput,
|
|
543
|
+
toolCall: { toolName: "test_tool", toolCallId: "call-1", input: {} },
|
|
544
|
+
toolContext: {
|
|
545
|
+
store: createStore(),
|
|
546
|
+
addStagedCell: vi.fn(),
|
|
547
|
+
createCell: vi.fn(),
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
expect(addToolOutput).toHaveBeenCalledExactlyOnceWith({
|
|
551
|
+
tool: "test_tool",
|
|
552
|
+
toolCallId: "call-1",
|
|
553
|
+
state: "output-error",
|
|
554
|
+
errorText: "Edit failed",
|
|
555
|
+
});
|
|
556
|
+
} finally {
|
|
557
|
+
hasTool.mockRestore();
|
|
558
|
+
invokeFrontend.mockRestore();
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
);
|
|
562
|
+
});
|
|
@@ -52,7 +52,7 @@ vi.mock("use-acp", async (importOriginal) => ({
|
|
|
52
52
|
clearNotifications: vi.fn(),
|
|
53
53
|
}),
|
|
54
54
|
}));
|
|
55
|
-
vi.mock("@/components/editor/ai/
|
|
55
|
+
vi.mock("@/components/editor/ai/prompt-input", () => ({
|
|
56
56
|
PromptInput: () => null,
|
|
57
57
|
}));
|
|
58
58
|
vi.mock("../session-tabs", () => ({ SessionTabs: () => null }));
|
|
@@ -12,7 +12,7 @@ import type { ComponentProps } from "react";
|
|
|
12
12
|
import { JsonRpcError } from "use-acp";
|
|
13
13
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
14
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
15
|
-
import type { PromptInput } from "@/components/editor/ai/
|
|
15
|
+
import type { PromptInput } from "@/components/editor/ai/prompt-input";
|
|
16
16
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
17
17
|
import { toast } from "@/components/ui/use-toast";
|
|
18
18
|
import { pendingAiPromptAtom } from "@/core/ai/state";
|
|
@@ -54,7 +54,7 @@ vi.mock("use-acp", async (importOriginal) => ({
|
|
|
54
54
|
useAcpClient: () => client,
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
-
vi.mock("@/components/editor/ai/
|
|
57
|
+
vi.mock("@/components/editor/ai/prompt-input", () => ({
|
|
58
58
|
PromptInput: ({
|
|
59
59
|
value,
|
|
60
60
|
onChange,
|
|
@@ -254,19 +254,24 @@ describe("AgentPanel prompt submission", () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
it(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
it.each([0, 1])(
|
|
258
|
+
"clears busy state after cancellation through Stop button %i",
|
|
259
|
+
async (buttonIndex) => {
|
|
260
|
+
const response = Promise.withResolvers<{ stopReason: string }>();
|
|
261
|
+
agent.prompt.mockReturnValueOnce(response.promise);
|
|
262
|
+
await renderPanel();
|
|
263
|
+
submitPrompt();
|
|
264
|
+
await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
|
|
265
|
+
expect(screen.getByText("Agent is working...")).toBeInTheDocument();
|
|
266
|
+
const stopButtons = screen.getAllByRole("button", { name: "Stop" });
|
|
267
|
+
expect(stopButtons).toHaveLength(2);
|
|
268
|
+
fireEvent.click(stopButtons[buttonIndex]);
|
|
269
|
+
await act(async () => response.resolve({ stopReason: "cancelled" }));
|
|
270
|
+
expect(agent.cancel).toHaveBeenCalledWith({ sessionId: "session-1" });
|
|
271
|
+
expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
|
|
272
|
+
expect(
|
|
273
|
+
screen.queryByRole("button", { name: "Dismiss" }),
|
|
274
|
+
).not.toBeInTheDocument();
|
|
275
|
+
},
|
|
276
|
+
);
|
|
272
277
|
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
type AdditionalCompletions,
|
|
18
18
|
PromptInput,
|
|
19
|
-
} from "@/components/editor/ai/
|
|
19
|
+
} from "@/components/editor/ai/prompt-input";
|
|
20
20
|
import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
|
|
21
21
|
import { Spinner } from "@/components/icons/spinner";
|
|
22
22
|
import { Button } from "@/components/ui/button";
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from "./state";
|
|
40
40
|
import { AgentThread } from "./thread";
|
|
41
41
|
import "./agent-panel.css";
|
|
42
|
+
import "../chat-typography.css";
|
|
42
43
|
import type { Completion } from "@codemirror/autocomplete";
|
|
43
44
|
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
44
45
|
import type {
|
|
@@ -441,7 +442,7 @@ const ModeSelector = memo<ModeSelectorProps>(
|
|
|
441
442
|
|
|
442
443
|
return (
|
|
443
444
|
<Select value={currentModeId} onValueChange={onModeChange}>
|
|
444
|
-
<SelectTrigger className="h-6 text-xs border-border
|
|
445
|
+
<SelectTrigger className="chat-control chat-selector h-6 text-xs border-border py-0 px-2 gap-1 capitalize">
|
|
445
446
|
{currentMode?.label ?? currentModeId}
|
|
446
447
|
</SelectTrigger>
|
|
447
448
|
<SelectContent>
|
|
@@ -1266,7 +1267,7 @@ const AgentPanel: React.FC = () => {
|
|
|
1266
1267
|
};
|
|
1267
1268
|
|
|
1268
1269
|
return (
|
|
1269
|
-
<div className="flex flex-col flex-1 overflow-hidden mo-agent-panel">
|
|
1270
|
+
<div className="mo-ai-panel flex flex-col flex-1 overflow-hidden mo-agent-panel font-prose text-sm leading-6">
|
|
1270
1271
|
<AgentPanelHeader
|
|
1271
1272
|
connectionState={connectionState}
|
|
1272
1273
|
currentAgentId={selectedTab?.agentId}
|
|
@@ -167,11 +167,11 @@ export const ReadyToChatBlock = () => {
|
|
|
167
167
|
return (
|
|
168
168
|
<div className="flex-1 flex items-center justify-center h-full min-h-[200px] flex-col">
|
|
169
169
|
<div className="text-center space-y-3">
|
|
170
|
-
<div className="w-12 h-12 mx-auto rounded-full bg-
|
|
171
|
-
<BotMessageSquareIcon className="h-6 w-6 text-
|
|
170
|
+
<div className="w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center">
|
|
171
|
+
<BotMessageSquareIcon className="h-6 w-6 text-muted-foreground" />
|
|
172
172
|
</div>
|
|
173
173
|
<div>
|
|
174
|
-
<h3 className="text-
|
|
174
|
+
<h3 className="text-base font-semibold text-foreground mb-1">
|
|
175
175
|
Agent is connected
|
|
176
176
|
</h3>
|
|
177
177
|
<p className="text-sm text-muted-foreground">
|
|
@@ -203,10 +203,10 @@ export const ConnectionChangeBlock = (props: {
|
|
|
203
203
|
icon: <WifiIcon className="h-4 w-4" />,
|
|
204
204
|
title: "Connected to Agent",
|
|
205
205
|
message: "Successfully established connection with the AI agent",
|
|
206
|
-
bgColor: "bg-
|
|
207
|
-
borderColor: "border-
|
|
208
|
-
textColor: "text-
|
|
209
|
-
iconColor: "text-
|
|
206
|
+
bgColor: "bg-transparent",
|
|
207
|
+
borderColor: "border-border",
|
|
208
|
+
textColor: "text-muted-foreground",
|
|
209
|
+
iconColor: "text-muted-foreground",
|
|
210
210
|
};
|
|
211
211
|
case "disconnected":
|
|
212
212
|
return {
|
|
@@ -318,7 +318,7 @@ export const PlansBlock = (props: { data: PlanNotificationEvent[] }) => {
|
|
|
318
318
|
plans = uniqueByTakeLast(plans, (item) => item.content);
|
|
319
319
|
|
|
320
320
|
return (
|
|
321
|
-
<div className="rounded-lg border bg-background p-2 text-
|
|
321
|
+
<div className="rounded-lg border bg-background p-2 text-sm">
|
|
322
322
|
<div className="flex items-center justify-between mb-2">
|
|
323
323
|
<span className="font-medium text-muted-foreground">
|
|
324
324
|
To-dos{" "}
|
|
@@ -345,7 +345,7 @@ export const PlansBlock = (props: { data: PlanNotificationEvent[] }) => {
|
|
|
345
345
|
/>
|
|
346
346
|
<span
|
|
347
347
|
className={cn(
|
|
348
|
-
"text-
|
|
348
|
+
"text-sm",
|
|
349
349
|
item.status === "completed" &&
|
|
350
350
|
"line-through text-muted-foreground",
|
|
351
351
|
)}
|
|
@@ -361,7 +361,7 @@ export const PlansBlock = (props: { data: PlanNotificationEvent[] }) => {
|
|
|
361
361
|
|
|
362
362
|
export const UserMessagesBlock = (props: { data: UserNotificationEvent[] }) => {
|
|
363
363
|
return (
|
|
364
|
-
<div className="flex flex-col gap-2 text-
|
|
364
|
+
<div className="flex flex-col gap-2 text-foreground border p-2 bg-background rounded wrap-break-word overflow-x-hidden">
|
|
365
365
|
<ContentBlocks data={props.data.map((item) => item.content)} />
|
|
366
366
|
</div>
|
|
367
367
|
);
|
|
@@ -45,7 +45,7 @@ export const SimpleAccordion: React.FC<SimpleAccordionProps> = ({
|
|
|
45
45
|
case "error":
|
|
46
46
|
return <XCircleIcon className="h-3 w-3 text-destructive" />;
|
|
47
47
|
case "success":
|
|
48
|
-
return
|
|
48
|
+
return null;
|
|
49
49
|
default:
|
|
50
50
|
return defaultIcon;
|
|
51
51
|
}
|
|
@@ -61,9 +61,8 @@ export const SimpleAccordion: React.FC<SimpleAccordionProps> = ({
|
|
|
61
61
|
<AccordionItem value="tool-call" className="border-0">
|
|
62
62
|
<AccordionTrigger
|
|
63
63
|
className={cn(
|
|
64
|
-
"py-1
|
|
64
|
+
"h-auto pl-0 pr-2 py-1 gap-2 rounded text-xs font-normal text-muted-foreground hover:bg-(--slate-2) hover:text-foreground hover:no-underline",
|
|
65
65
|
status === "error" && "text-destructive/80",
|
|
66
|
-
status === "success" && "text-(--blue-8)",
|
|
67
66
|
)}
|
|
68
67
|
>
|
|
69
68
|
<span className="flex items-center gap-1">
|
|
@@ -104,8 +103,8 @@ export const ConnectionStatus: React.FC<ConnectionStatusProps> = memo(
|
|
|
104
103
|
return {
|
|
105
104
|
icon: <WifiIcon className="h-3 w-3" />,
|
|
106
105
|
label: "Connected",
|
|
107
|
-
variant: "
|
|
108
|
-
className: "bg-
|
|
106
|
+
variant: "outline" as const,
|
|
107
|
+
className: "bg-transparent text-muted-foreground border-border",
|
|
109
108
|
};
|
|
110
109
|
case "connecting":
|
|
111
110
|
return {
|
|
@@ -35,7 +35,7 @@ export const ModelSelector = memo<ModelSelectorProps>(
|
|
|
35
35
|
onValueChange={onModelChange}
|
|
36
36
|
disabled={disabled}
|
|
37
37
|
>
|
|
38
|
-
<SelectTrigger className="h-6 text-xs border-border
|
|
38
|
+
<SelectTrigger className="chat-control chat-selector h-6 text-xs border-border py-0 px-2 gap-1">
|
|
39
39
|
{displayName}
|
|
40
40
|
</SelectTrigger>
|
|
41
41
|
<SelectContent>
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from "@/components/ui/popover";
|
|
12
12
|
import { Switch } from "@/components/ui/switch";
|
|
13
13
|
import { chatOptionsAtom } from "@/core/ai/state";
|
|
14
|
-
import { cn } from "@/utils/cn";
|
|
15
14
|
|
|
16
15
|
export const CapabilitiesPopover: React.FC = () => {
|
|
17
16
|
const [chatOptions, setChatOptions] = useAtom(chatOptionsAtom);
|
|
@@ -28,11 +27,7 @@ export const CapabilitiesPopover: React.FC = () => {
|
|
|
28
27
|
data-active={webSearchOn || undefined}
|
|
29
28
|
variant="text"
|
|
30
29
|
size="icon"
|
|
31
|
-
className=
|
|
32
|
-
"h-6 w-6 shrink-0 bg-muted hover:bg-muted/30",
|
|
33
|
-
webSearchOn &&
|
|
34
|
-
"bg-primary/15 text-primary ring-1 ring-primary/30 opacity-100 hover:bg-primary/20",
|
|
35
|
-
)}
|
|
30
|
+
className="chat-control h-6 w-6 shrink-0"
|
|
36
31
|
>
|
|
37
32
|
<SlidersHorizontalIcon className="h-3.5 w-3.5" />
|
|
38
33
|
</Button>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ImageIcon,
|
|
9
9
|
PaperclipIcon,
|
|
10
10
|
SendHorizontalIcon,
|
|
11
|
-
|
|
11
|
+
SquareIcon,
|
|
12
12
|
XIcon,
|
|
13
13
|
} from "lucide-react";
|
|
14
14
|
import { useState } from "react";
|
|
@@ -88,19 +88,20 @@ export const SendButton = ({
|
|
|
88
88
|
}) => {
|
|
89
89
|
const loadingContent = showStopLabel ? (
|
|
90
90
|
<div className="flex flex-row items-center gap-1 px-1.5">
|
|
91
|
-
<span className="text-xs
|
|
91
|
+
<span className="text-xs">Stop</span>
|
|
92
92
|
<Spinner size="small" />
|
|
93
93
|
</div>
|
|
94
94
|
) : (
|
|
95
|
-
<
|
|
95
|
+
<SquareIcon className="size-3.5" aria-hidden={true} />
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
return (
|
|
99
99
|
<Tooltip content={isLoading ? "Stop" : "Submit"}>
|
|
100
100
|
<Button
|
|
101
|
+
aria-label={isLoading ? "Stop" : "Submit"}
|
|
101
102
|
variant="text"
|
|
102
103
|
size="sm"
|
|
103
|
-
className="h-6 min-w-6 p-0 hover:bg-muted/30 cursor-pointer"
|
|
104
|
+
className="chat-control h-6 min-w-6 p-0 hover:bg-muted/30 cursor-pointer"
|
|
104
105
|
onClick={isLoading ? onStop : onSendClick}
|
|
105
106
|
disabled={isLoading ? false : isEmpty}
|
|
106
107
|
>
|
|
@@ -124,6 +125,7 @@ export const AddContextButton = ({
|
|
|
124
125
|
return (
|
|
125
126
|
<Tooltip content="Add context">
|
|
126
127
|
<Button
|
|
128
|
+
className="chat-control"
|
|
127
129
|
aria-label="Add context"
|
|
128
130
|
variant="text"
|
|
129
131
|
size="icon"
|
|
@@ -149,6 +151,7 @@ export const AttachFileButton = ({
|
|
|
149
151
|
<>
|
|
150
152
|
<Tooltip content="Attach a file">
|
|
151
153
|
<Button
|
|
154
|
+
className="chat-control"
|
|
152
155
|
aria-label="Attach a file"
|
|
153
156
|
variant="text"
|
|
154
157
|
size="icon"
|