@marimo-team/islands 0.25.3-dev189 → 0.25.3-dev191

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 (77) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Le_OBfmt.js → ConnectedDataExplorerComponent-BBbOElk3.js} +4 -4
  2. package/dist/{ErrorBoundary-CWtCjh3m.js → ErrorBoundary--X0z83Yx.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-Tf1ghyQQ.js → abnfDiagram-VCTEODGH-DhMT6Uhd.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-xlnbTG0T.js → architectureDiagram-5GKGNRK7-BVbEiJLq.js} +1 -1
  5. package/dist/assets/__vite-browser-external-C8gQho07.js +1 -0
  6. package/dist/assets/{worker-C1pfNBsx.js → worker-C6e9ERyd.js} +2 -2
  7. package/dist/{chat-ui-X-cWrL9u.js → chat-ui-BguxTQEL.js} +7 -7
  8. package/dist/{chunk-IMKFNOWR-Cmd4a5jy.js → chunk-IMKFNOWR-By5hSbJJ.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-mSNp-WpO.js → chunk-SHT3W25Y-DFrEYG54.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-CWC49D8X.js → chunk-SVP7TREG-DFXGc9ew.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-De9FINtG.js → chunk-TICWLB2K-DZdNIpoZ.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-DweTkYdp.js → classDiagram-ZZMXUADV-Cgj_eEQA.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-A6MR2TiE.js → classDiagram-v2-VYDZK3BY-DLAUrSqj.js} +2 -2
  14. package/dist/{code-visibility-BZDPS5dP.js → code-visibility-uDyLo9_i.js} +4 -4
  15. package/dist/{common-rusLIanu.js → common-DWKY45Md.js} +9 -9
  16. package/dist/{cynefinDiagram-5FMLGOSQ-BSvo5le-.js → cynefinDiagram-5FMLGOSQ-Cikf9AKa.js} +1 -1
  17. package/dist/{dagre-GXQ25YYZ-tXRQslO6.js → dagre-GXQ25YYZ-C5KEi5_l.js} +1 -1
  18. package/dist/{data-grid-overlay-editor-Dc_HCUPe.js → data-grid-overlay-editor-CRnYWD1L.js} +1 -1
  19. package/dist/{diagram-S7CK7UJ4-r3t5VtwI.js → diagram-S7CK7UJ4-BaoBTQjR.js} +1 -1
  20. package/dist/{diagram-UQ7AKVKN-qgbC8YnT.js → diagram-UQ7AKVKN-DDufBsUA.js} +1 -1
  21. package/dist/{diagram-VX7I27RA-CiHxEwtp.js → diagram-VX7I27RA-CY6RALJ8.js} +1 -1
  22. package/dist/{diagram-Z3DM3KII-D95u15Ev.js → diagram-Z3DM3KII-Bc-LHsD6.js} +1 -1
  23. package/dist/{dropdown-menu-DCMbbQ35.js → dropdown-menu-drDjxJyV.js} +1 -1
  24. package/dist/{ebnfDiagram-PWID7BFC-P7e4bryK.js → ebnfDiagram-PWID7BFC-Cz0J-tcC.js} +2 -2
  25. package/dist/{erDiagram-RLTQ6QDP-CUHZcmRj.js → erDiagram-RLTQ6QDP-DTXTDgFu.js} +1 -1
  26. package/dist/{error-entries-mpoD5t79.js → error-entries-CZ1NQ6Kv.js} +4 -4
  27. package/dist/{feedback-button-C3gqiY5b.js → feedback-button-CDf1yRm_.js} +5 -5
  28. package/dist/{flowDiagram-HODETNUW-BGZ95AzI.js → flowDiagram-HODETNUW-RbwWf2zc.js} +2 -2
  29. package/dist/{formats-DI7HEVv7.js → formats-DbXnijtH.js} +1 -1
  30. package/dist/{glide-data-editor-defMDM3B.js → glide-data-editor-CvRiA49N.js} +5 -5
  31. package/dist/{globals-D9kDDdYk.js → globals-BR4CmGOS.js} +2 -2
  32. package/dist/{html-to-image-BoZthB4P.js → html-to-image-D_4_hQ8U.js} +5 -5
  33. package/dist/{infoDiagram-27XIBGKW-BF0YX70D.js → infoDiagram-27XIBGKW-DlmzZjmI.js} +1 -1
  34. package/dist/{ishikawaDiagram-5VMMS53U-_itsAAn-.js → ishikawaDiagram-5VMMS53U-EN3TYP_q.js} +1 -1
  35. package/dist/{kanban-definition-UXKFOSKX-VNW2Beqh.js → kanban-definition-UXKFOSKX-CK_-Ru7Q.js} +1 -1
  36. package/dist/main.js +22 -22
  37. package/dist/{mermaid-CZ6FrClS.js → mermaid-DU9ZRmkG.js} +34 -34
  38. package/dist/{mindmap-definition-YA3MSWOX-DR4ripvd.js → mindmap-definition-YA3MSWOX-C-hqgUvF.js} +1 -1
  39. package/dist/{number-overlay-editor-CR5KeIcI.js → number-overlay-editor-DiYGbxNa.js} +1 -1
  40. package/dist/{pegDiagram-XKGWAZYB-CFr3XlGl.js → pegDiagram-XKGWAZYB-BiMejYPB.js} +2 -2
  41. package/dist/{pieDiagram-E7YTZNPT-j91XYSLY.js → pieDiagram-E7YTZNPT-B1zCZ1eq.js} +1 -1
  42. package/dist/{process-output-DLS4Mqg_.js → process-output-DxSCHBWp.js} +2 -2
  43. package/dist/{railroadDiagram-O6MQD6OU-Cd47ZV9E.js → railroadDiagram-O6MQD6OU-BLcfaASl.js} +2 -2
  44. package/dist/{requirementDiagram-BXWQKSXE-eJH6gLtX.js → requirementDiagram-BXWQKSXE-DtBOf52-.js} +1 -1
  45. package/dist/{reveal-component-DxZaPN5S.js → reveal-component-D3DsXPIm.js} +6 -6
  46. package/dist/{sequenceDiagram-WJ2MYXX4-DdfFEIrm.js → sequenceDiagram-WJ2MYXX4-DIwF19_n.js} +1 -1
  47. package/dist/{spec-h69nC3SI.js → spec-DZDnnlgT.js} +1 -1
  48. package/dist/{stateDiagram-D77RDMKH-DkjDvip8.js → stateDiagram-D77RDMKH-sWk1AdYL.js} +2 -2
  49. package/dist/{stateDiagram-v2-MP3YSRHH-DBHACnOo.js → stateDiagram-v2-MP3YSRHH-CiNo-OI7.js} +2 -2
  50. package/dist/style.css +1 -1
  51. package/dist/{swimlanes-42K2YHIH-ogImlOmp.js → swimlanes-42K2YHIH-Bj3hiOY5.js} +1 -1
  52. package/dist/{swimlanesDiagram-VR7AAH4N-D6x-zsci.js → swimlanesDiagram-VR7AAH4N-BzEo2r_N.js} +2 -2
  53. package/dist/{timeline-definition-24CTP7MA-DQpLtsHD.js → timeline-definition-24CTP7MA-CrPQk42H.js} +1 -1
  54. package/dist/{toDate-CDzoofJo.js → toDate-Cu2pIZcC.js} +1 -1
  55. package/dist/{useAsyncData-Bn8SxUqM.js → useAsyncData-DEhLdRIu.js} +1 -1
  56. package/dist/{useDeepCompareMemoize-CC2tnPCk.js → useDeepCompareMemoize-RjZGVhBV.js} +1 -1
  57. package/dist/{useLifecycle-BJlxHqWa.js → useLifecycle-DtcB2s1z.js} +1 -1
  58. package/dist/{useTheme-DMfQYmlG.js → useTheme-CoEtr4mK.js} +1 -1
  59. package/dist/{vega-component-CovWHi60.js → vega-component-BhjTrP9-.js} +5 -5
  60. package/dist/{vennDiagram-4TSXK5OY-BD-SL_vq.js → vennDiagram-4TSXK5OY-BaYMoIpj.js} +1 -1
  61. package/dist/{wardleyDiagram-VM6X3IG4-BDgJV4ah.js → wardleyDiagram-VM6X3IG4-DjHeuj8p.js} +1 -1
  62. package/dist/{xychartDiagram-S5SC5T6Z-1hIHN6Rl.js → xychartDiagram-S5SC5T6Z-Bc_hcuXS.js} +1 -1
  63. package/package.json +1 -1
  64. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  65. package/src/components/chat/chat-panel.tsx +9 -91
  66. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  67. package/src/components/chat/notebook-access-selector.tsx +100 -0
  68. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +1 -5
  69. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  70. package/src/core/ai/tools/base.ts +0 -6
  71. package/src/core/ai/tools/edit-notebook-tool.ts +1 -2
  72. package/src/core/ai/tools/registry.ts +0 -2
  73. package/src/core/config/__tests__/config-schema.test.ts +7 -2
  74. package/src/core/config/config-schema.ts +4 -1
  75. package/dist/assets/__vite-browser-external-DVeFf2e7.js +0 -1
  76. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  77. package/src/core/ai/tools/run-cells-tool.ts +0 -303
@@ -9,7 +9,7 @@ import "./chunk-4HAMMTFA-D4eFPyFh.js";
9
9
  import "./chunk-GVQU2GXP-Cwbt-lF0.js";
10
10
  import "./chunk-L3NEJ4N5-n-zgIVSy.js";
11
11
  import { o as n } from "./chunk-OSK3NFVY-DXa-Cxbn.js";
12
- import { c as r } from "./mermaid-CZ6FrClS.js";
12
+ import { c as r } from "./mermaid-DU9ZRmkG.js";
13
13
  //#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/swimlanes-42K2YHIH.mjs
14
14
  var i = 5, a = 1e-5, o = 1e-6;
15
15
  function s(e) {
@@ -9,11 +9,11 @@ import "./chunk-4HAMMTFA-D4eFPyFh.js";
9
9
  import "./chunk-GVQU2GXP-Cwbt-lF0.js";
10
10
  import "./chunk-L3NEJ4N5-n-zgIVSy.js";
11
11
  import "./chunk-OSK3NFVY-DXa-Cxbn.js";
12
- import "./mermaid-CZ6FrClS.js";
12
+ import "./mermaid-DU9ZRmkG.js";
13
13
  import "./chunk-F27PBJKO-ufseIn5y.js";
14
14
  import "./chunk-XXDRQBXY-Dnl5VBqG.js";
15
15
  import "./chunk-POPQ4Y6H-hYOXbYH_.js";
16
- import { r as t, t as n } from "./chunk-SHT3W25Y-mSNp-WpO.js";
16
+ import { r as t, t as n } from "./chunk-SHT3W25Y-DFrEYG54.js";
17
17
  //#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/swimlanesDiagram-VR7AAH4N.mjs
18
18
  var r = n({
19
19
  defaultLayout: "swimlane",
@@ -3,7 +3,7 @@ import { p as t } from "./chunk-75Z2AOVW-D7gxIs2Q.js";
3
3
  import { n, t as r } from "./src-CA_m4ONl.js";
4
4
  import { n as i, t as a } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
5
5
  import { $ as o, J as s, a as c, b as l, et as u, nt as d, o as f, x as p } from "./chunk-DU6HZSFF-BwiyaNpg.js";
6
- import { p as m } from "./mermaid-CZ6FrClS.js";
6
+ import { p as m } from "./mermaid-DU9ZRmkG.js";
7
7
  //#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/timeline-definition-24CTP7MA.mjs
8
8
  var h = (function() {
9
9
  var e = /* @__PURE__ */ i(function(e, t, n, r) {
@@ -2,7 +2,7 @@ import { t as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { g as t } from "./button-BQRdqQi1.js";
3
3
  import { t as n } from "./compiler-runtime-CldRdSsv.js";
4
4
  import { m as r } from "./tooltip-s4zZxwKe.js";
5
- import { D as i, _ as a, a as o, w as s, x as c } from "./useTheme-DMfQYmlG.js";
5
+ import { D as i, _ as a, a as o, w as s, x as c } from "./useTheme-CoEtr4mK.js";
6
6
  import { t as l } from "./invariant-DLmrl-hA.js";
7
7
  var u = r("circle-question-mark", [
8
8
  ["circle", {
@@ -1,7 +1,7 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-sHtZdjeP.js";
3
3
  import { t as n } from "./compiler-runtime-CldRdSsv.js";
4
- import { A as r } from "./useTheme-DMfQYmlG.js";
4
+ import { A as r } from "./useTheme-CoEtr4mK.js";
5
5
  import { t as i } from "./invariant-DLmrl-hA.js";
6
6
  //#region src/hooks/useAsyncData.ts
7
7
  var a = n(), o = /* @__PURE__ */ e(t(), 1), s = {
@@ -1,6 +1,6 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-sHtZdjeP.js";
3
- import { b as n } from "./useTheme-DMfQYmlG.js";
3
+ import { b as n } from "./useTheme-CoEtr4mK.js";
4
4
  //#region src/hooks/useDeepCompareMemoize.ts
5
5
  var r = /* @__PURE__ */ e(t(), 1);
6
6
  function i(e) {
@@ -4,7 +4,7 @@ import { t as i } from "./react-sHtZdjeP.js";
4
4
  import { t as a } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { m as o } from "./tooltip-s4zZxwKe.js";
6
6
  import { t as s } from "./jsx-runtime-3BD1kT0s.js";
7
- import { D as c, T as l } from "./useTheme-DMfQYmlG.js";
7
+ import { D as c, T as l } from "./useTheme-CoEtr4mK.js";
8
8
  var u = o("calendar", [
9
9
  ["path", {
10
10
  d: "M8 2v4",
@@ -681,7 +681,7 @@ var ze = [
681
681
  enabled: p().prefault(!0),
682
682
  rules: u().prefault(""),
683
683
  max_tokens: d().int().positive().nullable().optional(),
684
- mode: m(We).prefault("manual"),
684
+ mode: m(We).transform((e) => e === "manual" ? "ask" : e).prefault("ask"),
685
685
  inline_tooltip: p().prefault(!1),
686
686
  allow_provider_config: p().prefault(!0),
687
687
  open_ai: J.optional(),
@@ -3,20 +3,20 @@ import { g as t, h as n, m as r, o as i } from "./button-BQRdqQi1.js";
3
3
  import { t as a } from "./react-sHtZdjeP.js";
4
4
  import { t as o } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { t as s } from "./tooltip-s4zZxwKe.js";
6
- import { C as c, a as l } from "./toDate-CDzoofJo.js";
6
+ import { C as c, a as l } from "./toDate-Cu2pIZcC.js";
7
7
  import { t as u } from "./jsx-runtime-3BD1kT0s.js";
8
8
  import { n as d } from "./error-banner-DxJ3s8YO.js";
9
9
  import { t as f } from "./debounce-C3jyATpc.js";
10
- import { A as p, n as ee } from "./useTheme-DMfQYmlG.js";
10
+ import { A as p, n as ee } from "./useTheme-CoEtr4mK.js";
11
11
  import { c as m, t as h } from "./arrays-R6JsImiT.js";
12
12
  import { t as te } from "./objects-DCmMvZtn.js";
13
- import { t as ne } from "./useAsyncData-Bn8SxUqM.js";
14
- import { a as g, i as re, n as ie, t as ae } from "./formats-DI7HEVv7.js";
13
+ import { t as ne } from "./useAsyncData-DEhLdRIu.js";
14
+ import { a as g, i as re, n as ie, t as ae } from "./formats-DbXnijtH.js";
15
15
  import { t as _ } from "./semaphore-89jsoHtS.js";
16
16
  import { D as oe, n as se } from "./vega-loader.browser-B9Pz0EGP.js";
17
17
  import { t as ce } from "./react-vega-C4T9Rb_w.js";
18
18
  import { a as le, n as v } from "./loader-DOVB0Syl.js";
19
- import { t as ue } from "./useDeepCompareMemoize-CC2tnPCk.js";
19
+ import { t as ue } from "./useDeepCompareMemoize-RjZGVhBV.js";
20
20
  import { t as de } from "./use-vega-container-remeasure-Bz4PZMtt.js";
21
21
  //#region src/plugins/impl/vega/fix-relative-url.ts
22
22
  var fe = /* @__PURE__ */ e(a(), 1), pe = o();
@@ -3,7 +3,7 @@ import { t } from "./src-CA_m4ONl.js";
3
3
  import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
4
4
  import { $ as r, H as i, K as a, U as o, a as s, b as c, c as l, et as u, f as d, nt as f, tt as p, v as m, w as h, y as g } from "./chunk-DU6HZSFF-BwiyaNpg.js";
5
5
  import { t as _ } from "./rough.esm-CtV9AnV1.js";
6
- import { p as v } from "./mermaid-CZ6FrClS.js";
6
+ import { p as v } from "./mermaid-DU9ZRmkG.js";
7
7
  //#region ../node_modules/.pnpm/khroma@2.1.0/node_modules/khroma/dist/methods/transparentize.js
8
8
  var y = (e, t) => p(e, "a", -t), b = 1e-10;
9
9
  function x(e, t) {
@@ -2,7 +2,7 @@ import { i as e } from "./chunk-75Z2AOVW-D7gxIs2Q.js";
2
2
  import { n as t } from "./src-CA_m4ONl.js";
3
3
  import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
4
4
  import { D as r, H as i, K as a, U as o, a as s, b as c, c as l, v as u, w as d, x as f, y as p } from "./chunk-DU6HZSFF-BwiyaNpg.js";
5
- import { p as m } from "./mermaid-CZ6FrClS.js";
5
+ import { p as m } from "./mermaid-DU9ZRmkG.js";
6
6
  import { n as h } from "./mermaid-parser.core-DxuSfYwJ.js";
7
7
  import { t as g } from "./chunk-JWPE2WC7-DJvv1t8U.js";
8
8
  //#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/wardleyDiagram-VM6X3IG4.mjs
@@ -9,7 +9,7 @@ import { n as s } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
9
9
  import { D as c, H as l, K as u, U as d, a as f, b as p, c as m, f as h, v as g, w as _, y as v, z as y } from "./chunk-DU6HZSFF-BwiyaNpg.js";
10
10
  import "./chunk-PWAF6VOD-CQw5qATZ.js";
11
11
  import { t as b } from "./chunk-GMAD6QVW-BhaJx0X3.js";
12
- import { p as x } from "./mermaid-CZ6FrClS.js";
12
+ import { p as x } from "./mermaid-DU9ZRmkG.js";
13
13
  //#region ../node_modules/.pnpm/d3-scale@4.0.2/node_modules/d3-scale/src/band.js
14
14
  function S() {
15
15
  var e = t().unknown(void 0), r = e.domain, i = e.range, o = 0, s = 1, c, l, u = !1, d = 0, f = 0, p = .5;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.25.3-dev189",
3
+ "version": "0.25.3-dev191",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,64 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import type { CopilotMode } from "@/core/config/config-schema";
6
+ import { NotebookAccessSelector } from "../notebook-access-selector";
7
+
8
+ Element.prototype.scrollIntoView = vi.fn();
9
+
10
+ describe("NotebookAccessSelector", () => {
11
+ it("shows legacy Manual as Read only and offers exactly three levels", async () => {
12
+ const onValueChange = vi.fn();
13
+ render(
14
+ <NotebookAccessSelector value="manual" onValueChange={onValueChange} />,
15
+ );
16
+
17
+ fireEvent.keyDown(
18
+ screen.getByRole("combobox", { name: "Notebook access: Read only" }),
19
+ { key: "ArrowDown" },
20
+ );
21
+ const options = await screen.findAllByRole("option");
22
+ expect(options.map((option) => option.getAttribute("data-state"))).toEqual([
23
+ "checked",
24
+ "unchecked",
25
+ "unchecked",
26
+ ]);
27
+ expect(options.map((option) => option.textContent)).toEqual([
28
+ "Read onlyRead notebook code, outputs, and variable previews.",
29
+ "Suggest editsRead the notebook and propose cell changes for review.",
30
+ "Full accessRead, edit, and run cells, with Python scratchpad access.",
31
+ ]);
32
+ expect(onValueChange).not.toHaveBeenCalled();
33
+ });
34
+
35
+ it.each<{ mode: CopilotMode; label: string }>([
36
+ { mode: "ask", label: "Read only" },
37
+ { mode: "agent", label: "Suggest edits" },
38
+ { mode: "code_mode", label: "Full access" },
39
+ ])("maps $label to the existing $mode setting", async ({ mode, label }) => {
40
+ const onValueChange = vi.fn();
41
+ const { rerender } = render(
42
+ <NotebookAccessSelector value={mode} onValueChange={onValueChange} />,
43
+ );
44
+
45
+ expect(
46
+ screen.getByRole("combobox", { name: `Notebook access: ${label}` }),
47
+ ).toHaveTextContent(label);
48
+ expect(onValueChange).not.toHaveBeenCalled();
49
+
50
+ rerender(
51
+ <NotebookAccessSelector
52
+ value={mode === "ask" ? "agent" : "ask"}
53
+ onValueChange={onValueChange}
54
+ />,
55
+ );
56
+ fireEvent.keyDown(screen.getByRole("combobox"), { key: "ArrowDown" });
57
+ const option = await screen.findByRole("option", {
58
+ name: new RegExp(`^${label} `),
59
+ });
60
+ fireEvent.keyDown(option, { key: "Enter" });
61
+
62
+ expect(onValueChange).toHaveBeenCalledExactlyOnceWith(mode);
63
+ });
64
+ });
@@ -13,28 +13,15 @@ import {
13
13
  import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
14
14
  import {
15
15
  BotMessageSquareIcon,
16
- HatGlasses,
17
16
  ArrowRightIcon,
18
17
  Loader2,
19
- type LucideIcon,
20
- MessageCircleIcon,
21
- NotebookText,
22
18
  PlusIcon,
23
19
  SparklesIcon,
24
20
  SettingsIcon,
25
- CodeIcon,
26
21
  } from "lucide-react";
27
22
  import { memo, useEffect, useRef, useState } from "react";
28
23
  import useEvent from "react-use-event-hook";
29
24
  import { Button } from "@/components/ui/button";
30
- import {
31
- Select,
32
- SelectContent,
33
- SelectGroup,
34
- SelectItem,
35
- SelectLabel,
36
- SelectTrigger,
37
- } from "@/components/ui/select";
38
25
  import { replaceMessagesInChat } from "@/core/ai/chat-utils";
39
26
  import { useAIConfigActions } from "@/core/ai/config";
40
27
  import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
@@ -49,10 +36,7 @@ import {
49
36
  pendingAiPromptAtom,
50
37
  } from "@/core/ai/state";
51
38
  import type { ToolNotebookContext } from "@/core/ai/tools/base";
52
- import {
53
- type CopilotMode,
54
- FRONTEND_TOOL_REGISTRY,
55
- } from "@/core/ai/tools/registry";
39
+ import { FRONTEND_TOOL_REGISTRY } from "@/core/ai/tools/registry";
56
40
  import { useCellActions } from "@/core/cells/actions";
57
41
  import { aiAtom, aiModelConfiguredAtom } from "@/core/config/config";
58
42
  import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
@@ -97,6 +81,7 @@ import {
97
81
  import { renderUIMessage } from "./chat-display";
98
82
  import { ChatHistoryPopover } from "./chat-history-popover";
99
83
  import { CapabilitiesPopover } from "./capabilities-popover";
84
+ import { NotebookAccessSelector } from "./notebook-access-selector";
100
85
  import {
101
86
  type ChatMessagePart,
102
87
  convertToFileUIPart,
@@ -117,7 +102,7 @@ import ScrollToBottomButton from "./acp/scroll-to-bottom-button";
117
102
  import { useChatControllerId } from "./use-chat-controller-id";
118
103
 
119
104
  // Default mode for the AI
120
- const DEFAULT_MODE = "manual";
105
+ const DEFAULT_MODE = "ask";
121
106
 
122
107
  const QueuedMessageDisplay = memo(
123
108
  ({ message }: { message: QueuedUserMessage }) => {
@@ -321,82 +306,17 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
321
306
 
322
307
  const { saveModeChange } = useAIConfigActions();
323
308
 
324
- const modeOptions: {
325
- value: CopilotMode;
326
- label: string;
327
- subtitle: string;
328
- Icon: LucideIcon;
329
- }[] = [
330
- {
331
- value: "manual",
332
- label: "Manual",
333
- subtitle: "Pure chat, no tool usage",
334
- Icon: MessageCircleIcon,
335
- },
336
- {
337
- value: "ask",
338
- label: "Ask",
339
- subtitle: "AI with access to read-only tools like documentation search",
340
- Icon: NotebookText,
341
- },
342
- {
343
- value: "agent",
344
- label: "Agent",
345
- subtitle: "AI with access to read and write tools",
346
- Icon: HatGlasses,
347
- },
348
- {
349
- value: "code_mode",
350
- label: "Code Mode (beta)",
351
- subtitle:
352
- "AI with access to the notebook's kernel. Potentially destructive.",
353
- Icon: CodeIcon,
354
- },
355
- ];
356
-
357
309
  const isAttachmentSupported =
358
310
  PROVIDERS_THAT_SUPPORT_ATTACHMENTS.has(currentProvider);
359
311
 
360
- const currentModeOption = modeOptions.find((o) => o.value === currentMode);
361
- const CurrentModeIcon = currentModeOption?.Icon;
362
- const CurrentModeLabel = currentModeOption?.label;
363
-
364
312
  return (
365
313
  <TooltipProvider>
366
314
  <div className="px-3 py-2 border-t border-border/20 flex flex-wrap items-center gap-1">
367
315
  <div className="flex flex-wrap items-center gap-1">
368
- <Select value={currentMode} onValueChange={saveModeChange}>
369
- <SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5 shrink-0">
370
- {CurrentModeIcon && <CurrentModeIcon className="h-3 w-3" />}
371
- <span className="text-nowrap">{CurrentModeLabel}</span>
372
- </SelectTrigger>
373
- <SelectContent>
374
- <SelectGroup>
375
- <SelectLabel className="text-xs uppercase tracking-wider text-muted-foreground/70 font-medium">
376
- AI Mode
377
- </SelectLabel>
378
- {modeOptions.map((option) => (
379
- <SelectItem
380
- key={option.value}
381
- value={option.value}
382
- className="text-xs py-1"
383
- >
384
- <div className="flex items-start gap-2.5">
385
- <span className="mt-1 text-muted-foreground">
386
- <option.Icon className="h-3 w-3" />
387
- </span>
388
- <div className="flex flex-col gap-0.5">
389
- <span className="font-semibold">{option.label}</span>
390
- <span className="text-muted-foreground">
391
- {option.subtitle}
392
- </span>
393
- </div>
394
- </div>
395
- </SelectItem>
396
- ))}
397
- </SelectGroup>
398
- </SelectContent>
399
- </Select>
316
+ <NotebookAccessSelector
317
+ value={currentMode}
318
+ onValueChange={saveModeChange}
319
+ />
400
320
  <AIModelDropdown
401
321
  placeholder="Model"
402
322
  triggerClassName="h-6 text-xs shadow-none! ring-0! bg-muted hover:bg-muted/30 rounded-sm max-w-[200px]"
@@ -573,7 +493,7 @@ const ChatPanelBody = () => {
573
493
  setAbortNotice(null);
574
494
  });
575
495
  const runtimeManager = useRuntimeManager();
576
- const { invokeAiTool, sendRun } = useRequestClient();
496
+ const { invokeAiTool } = useRequestClient();
577
497
  const { openModal, closeModal } = useImperativeModal();
578
498
 
579
499
  const activeChatId = activeChat?.id;
@@ -582,12 +502,10 @@ const ChatPanelBody = () => {
582
502
  const store = useStore();
583
503
 
584
504
  const { addStagedCell } = useStagedAICellsActions();
585
- const { prepareForRun, createCell } = useCellActions();
505
+ const { createCell } = useCellActions();
586
506
  const toolContext: ToolNotebookContext = {
587
507
  addStagedCell,
588
508
  createCell,
589
- prepareForRun,
590
- sendRun,
591
509
  store,
592
510
  };
593
511
 
@@ -0,0 +1,22 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { Meta, StoryObj } from "@storybook/react-vite";
4
+ import { useState } from "react";
5
+ import { NotebookAccessSelector } from "./notebook-access-selector";
6
+
7
+ const meta = {
8
+ title: "Chat/Notebook access",
9
+ component: NotebookAccessSelector,
10
+ args: { value: "ask", onValueChange: () => undefined },
11
+ render: function Render(args) {
12
+ const [value, setValue] = useState(args.value);
13
+ return <NotebookAccessSelector value={value} onValueChange={setValue} />;
14
+ },
15
+ } satisfies Meta<typeof NotebookAccessSelector>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const ReadOnly: Story = { args: { value: "ask" } };
21
+ export const SuggestEdits: Story = { args: { value: "agent" } };
22
+ export const FullAccess: Story = { args: { value: "code_mode" } };
@@ -0,0 +1,100 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ EyeIcon,
5
+ type LucideIcon,
6
+ PencilIcon,
7
+ TerminalIcon,
8
+ } from "lucide-react";
9
+ import {
10
+ Select,
11
+ SelectContent,
12
+ SelectGroup,
13
+ SelectItem,
14
+ SelectLabel,
15
+ SelectTrigger,
16
+ } from "@/components/ui/select";
17
+ import type { CopilotMode } from "@/core/config/config-schema";
18
+
19
+ const ACCESS_LEVELS = {
20
+ ask: {
21
+ label: "Read only",
22
+ description: "Read notebook code, outputs, and variable previews.",
23
+ Icon: EyeIcon,
24
+ },
25
+ agent: {
26
+ label: "Suggest edits",
27
+ description: "Read the notebook and propose cell changes for review.",
28
+ Icon: PencilIcon,
29
+ },
30
+ code_mode: {
31
+ label: "Full access",
32
+ description: "Read, edit, and run cells, with Python scratchpad access.",
33
+ Icon: TerminalIcon,
34
+ },
35
+ } satisfies Record<
36
+ Exclude<CopilotMode, "manual">,
37
+ { label: string; description: string; Icon: LucideIcon }
38
+ >;
39
+
40
+ const ACCESS_MODES = ["ask", "agent", "code_mode"] as const;
41
+
42
+ interface NotebookAccessSelectorProps {
43
+ value: CopilotMode;
44
+ onValueChange: (value: CopilotMode) => void;
45
+ }
46
+
47
+ export const NotebookAccessSelector = ({
48
+ value,
49
+ onValueChange,
50
+ }: NotebookAccessSelectorProps) => {
51
+ const mode = value === "manual" ? "ask" : value;
52
+ const selected = ACCESS_LEVELS[mode];
53
+
54
+ return (
55
+ <Select
56
+ value={mode}
57
+ onValueChange={(value) => {
58
+ const mode = ACCESS_MODES.find((mode) => mode === value);
59
+ if (mode) {
60
+ onValueChange(mode);
61
+ }
62
+ }}
63
+ >
64
+ <SelectTrigger
65
+ aria-label={`Notebook access: ${selected.label}`}
66
+ title={`Notebook access: ${selected.description}`}
67
+ className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5 shrink-0"
68
+ >
69
+ <selected.Icon className="h-3 w-3" />
70
+ <span className="text-nowrap">{selected.label}</span>
71
+ </SelectTrigger>
72
+ <SelectContent className="max-w-[calc(100vw-2rem)]">
73
+ <SelectGroup>
74
+ <SelectLabel className="text-xs uppercase tracking-wider text-muted-foreground/70 font-medium">
75
+ Notebook access
76
+ </SelectLabel>
77
+ {ACCESS_MODES.map((mode) => {
78
+ const { label, description, Icon } = ACCESS_LEVELS[mode];
79
+ return (
80
+ <SelectItem
81
+ key={mode}
82
+ value={mode}
83
+ textValue={label}
84
+ className="text-xs py-1"
85
+ >
86
+ <div className="flex items-start gap-2.5">
87
+ <Icon className="h-3 w-3 mt-1 shrink-0 text-muted-foreground" />
88
+ <div className="flex flex-col gap-0.5">
89
+ <span className="font-semibold">{label}</span>
90
+ <span className="text-muted-foreground">{description}</span>
91
+ </div>
92
+ </div>
93
+ </SelectItem>
94
+ );
95
+ })}
96
+ </SelectGroup>
97
+ </SelectContent>
98
+ </Select>
99
+ );
100
+ };
@@ -65,8 +65,6 @@ describe("EditNotebookTool", () => {
65
65
  let toolContext: {
66
66
  addStagedCell: ReturnType<typeof vi.fn>;
67
67
  createCell: ReturnType<typeof vi.fn>;
68
- prepareForRun: ReturnType<typeof vi.fn>;
69
- sendRun: ReturnType<typeof vi.fn>;
70
68
  store: ReturnType<typeof getDefaultStore>;
71
69
  };
72
70
 
@@ -82,8 +80,6 @@ describe("EditNotebookTool", () => {
82
80
  store.set(stagedAICellsAtom, new Map(current).set(cellId, edit));
83
81
  }),
84
82
  createCell: vi.fn(),
85
- prepareForRun: vi.fn(),
86
- sendRun: vi.fn().mockResolvedValue(null),
87
83
  store,
88
84
  };
89
85
  tool = new EditNotebookTool();
@@ -100,7 +96,7 @@ describe("EditNotebookTool", () => {
100
96
  it("should have correct metadata", () => {
101
97
  expect(tool.name).toBe("edit_notebook_tool");
102
98
  expect(tool.description).toBeDefined();
103
- expect(tool.description.baseDescription).toContain("editing operations");
99
+ expect(tool.description.baseDescription).toContain("Propose cell edits");
104
100
  expect(tool.schema).toBeDefined();
105
101
  expect(tool.outputSchema).toBeDefined();
106
102
  expect(tool.mode).toEqual(["agent"]);
@@ -10,6 +10,23 @@ import { TestFrontendTool } from "../sample-tool";
10
10
  const INVALID_KEYS_IN_SCHEMA_PARAMS = new Set(["anyOf", "oneOf"]);
11
11
 
12
12
  describe("FrontendToolRegistry", () => {
13
+ it("only offers cell proposals with Suggest edits access", () => {
14
+ expect(
15
+ Object.fromEntries(
16
+ COPILOT_MODES.map((mode) => [
17
+ mode,
18
+ FRONTEND_TOOL_REGISTRY.getToolSchemas(mode).map((tool) => tool.name),
19
+ ]),
20
+ ),
21
+ ).toEqual({
22
+ manual: [],
23
+ ask: [],
24
+ agent: ["edit_notebook_tool"],
25
+ code_mode: [],
26
+ });
27
+ expect(FRONTEND_TOOL_REGISTRY.has("run_stale_cells_tool")).toBe(false);
28
+ });
29
+
13
30
  it("registers tools via constructor and supports has()", () => {
14
31
  const registry = new FrontendToolRegistry([new TestFrontendTool()]);
15
32
  expect(registry.has("test_frontend_tool")).toBe(true);
@@ -3,7 +3,6 @@
3
3
  import { z } from "zod";
4
4
  import type { CreateCell } from "@/core/cells/actions";
5
5
  import type { CellId } from "@/core/cells/ids";
6
- import type { ExecuteCellsRequest } from "@/core/network/types";
7
6
  import type { JotaiStore } from "@/core/state/jotai";
8
7
  import type { Edit } from "../staged-cells";
9
8
  import type { CopilotMode } from "./registry";
@@ -67,9 +66,6 @@ export class ToolExecutionError extends Error {
67
66
  }
68
67
  }
69
68
 
70
- // Can be any input
71
- export type EmptyToolInput = unknown;
72
-
73
69
  /**
74
70
  * Base interface for tool output responses.
75
71
  * Mirrors the SuccessResult dataclass from marimo/_ai/_tools/types.py
@@ -114,8 +110,6 @@ export interface ToolNotebookContext {
114
110
  store: JotaiStore;
115
111
  addStagedCell: (payload: { cellId: CellId; edit: Edit }) => void;
116
112
  createCell: CreateCell;
117
- prepareForRun: (payload: { cellId: CellId }) => void;
118
- sendRun: (request: ExecuteCellsRequest) => Promise<null>;
119
113
  }
120
114
 
121
115
  export interface AiTool<TIn, TOut> {
@@ -21,7 +21,7 @@ import type { CopilotMode } from "./registry";
21
21
 
22
22
  const description: ToolDescription = {
23
23
  baseDescription:
24
- "Perform editing operations on the current notebook. You should prefer to create new cells unless you need to edit existing cells. Call this tool multiple times to perform multiple edits. Separate code into logical individual cells to take advantage of the notebook's reactive execution model.",
24
+ "Propose cell edits for the user to review in the current notebook. You should prefer to create new cells unless you need to edit existing cells. Call this tool multiple times to perform multiple edits. Separate code into logical individual cells to take advantage of the notebook's reactive execution model.",
25
25
  prerequisites: [
26
26
  "If you are updating existing cells, you need the cellIds. If they are not known, call the lightweight_cell_map_tool to find out.",
27
27
  ],
@@ -254,7 +254,6 @@ export class EditNotebookTool implements AiTool<
254
254
  next_steps: [
255
255
  "If you need to perform more edits, call this tool again.",
256
256
  "You should use the lint notebook tool to check for errors and lint issues. Fix them by editing the notebook.",
257
- "You should use the run stale cells tool to run the cells that have been edited or newly added. This allows you to see the output of the cells and fix any errors.",
258
257
  ],
259
258
  };
260
259
  };
@@ -9,7 +9,6 @@ import {
9
9
  type ToolNotebookContext,
10
10
  } from "./base";
11
11
  import { EditNotebookTool } from "./edit-notebook-tool";
12
- import { RunStaleCellsTool } from "./run-cells-tool";
13
12
  import { formatToolDescription } from "./utils";
14
13
 
15
14
  export type AnyZodObject = ZodObject<z.ZodRawShape>;
@@ -148,5 +147,4 @@ export class FrontendToolRegistry {
148
147
 
149
148
  export const FRONTEND_TOOL_REGISTRY = new FrontendToolRegistry([
150
149
  new EditNotebookTool(),
151
- new RunStaleCellsTool(),
152
150
  ]);
@@ -16,6 +16,11 @@ import {
16
16
  UserConfigSchema,
17
17
  } from "../config-schema";
18
18
 
19
+ test("legacy Manual config resolves to Read only", () => {
20
+ const config = UserConfigSchema.parse({ ai: { mode: "manual" } });
21
+ expect(config).toEqual(UserConfigSchema.parse({ ai: { mode: "ask" } }));
22
+ });
23
+
19
24
  test("default AppConfig", () => {
20
25
  const defaultConfig = AppConfigSchema.parse({});
21
26
  expect(defaultConfig).toMatchInlineSnapshot(`
@@ -51,7 +56,7 @@ test("default UserConfig - empty", () => {
51
56
  "custom_providers": {},
52
57
  "enabled": true,
53
58
  "inline_tooltip": false,
54
- "mode": "manual",
59
+ "mode": "ask",
55
60
  "models": {
56
61
  "custom_models": [],
57
62
  "displayed_models": [],
@@ -128,7 +133,7 @@ test("default UserConfig - one level", () => {
128
133
  "custom_providers": {},
129
134
  "enabled": true,
130
135
  "inline_tooltip": false,
131
- "mode": "manual",
136
+ "mode": "ask",
132
137
  "models": {
133
138
  "custom_models": [],
134
139
  "displayed_models": [],