@agent-native/toolkit 0.15.1 → 0.16.0

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 (38) hide show
  1. package/README.md +26 -0
  2. package/agent-native.eject.json +16 -0
  3. package/dist/composer/PromptComposer.d.ts +7 -1
  4. package/dist/composer/PromptComposer.d.ts.map +1 -1
  5. package/dist/composer/PromptComposer.js +2 -2
  6. package/dist/composer/PromptComposer.js.map +1 -1
  7. package/dist/composer/TiptapComposer.d.ts +19 -1
  8. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  9. package/dist/composer/TiptapComposer.js +27 -4
  10. package/dist/composer/TiptapComposer.js.map +1 -1
  11. package/dist/data-grid/DataGrid.d.ts +92 -0
  12. package/dist/data-grid/DataGrid.d.ts.map +1 -0
  13. package/dist/data-grid/DataGrid.js +315 -0
  14. package/dist/data-grid/DataGrid.js.map +1 -0
  15. package/dist/data-grid/DataGrid.spec.d.ts +2 -0
  16. package/dist/data-grid/DataGrid.spec.d.ts.map +1 -0
  17. package/dist/data-grid/DataGrid.spec.js +134 -0
  18. package/dist/data-grid/DataGrid.spec.js.map +1 -0
  19. package/dist/data-grid/index.d.ts +2 -0
  20. package/dist/data-grid/index.d.ts.map +1 -0
  21. package/dist/data-grid/index.js +2 -0
  22. package/dist/data-grid/index.js.map +1 -0
  23. package/dist/sharing/SharePrimitives.d.ts +14 -3
  24. package/dist/sharing/SharePrimitives.d.ts.map +1 -1
  25. package/dist/sharing/SharePrimitives.js +11 -3
  26. package/dist/sharing/SharePrimitives.js.map +1 -1
  27. package/dist/sharing/index.d.ts +1 -1
  28. package/dist/sharing/index.d.ts.map +1 -1
  29. package/dist/sharing/index.js +1 -1
  30. package/dist/sharing/index.js.map +1 -1
  31. package/package.json +15 -1
  32. package/src/composer/PromptComposer.tsx +13 -0
  33. package/src/composer/TiptapComposer.tsx +107 -1
  34. package/src/data-grid/DataGrid.spec.tsx +243 -0
  35. package/src/data-grid/DataGrid.tsx +754 -0
  36. package/src/data-grid/index.ts +15 -0
  37. package/src/sharing/SharePrimitives.tsx +47 -8
  38. package/src/sharing/index.ts +4 -0
@@ -1,3 +1,3 @@
1
- export { ShareAgentsSection, ShareCopyRow, ShareTrigger, } from "./SharePrimitives.js";
1
+ export { ShareAgentsSection, ShareCopyRow, ShareDisclosureSection, SharePeopleSection, ShareTrigger, } from "./SharePrimitives.js";
2
2
  export { VisibilityBadge, } from "./VisibilityBadge.js";
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAElB,YAAY,EAEZ,YAAY,GAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,GAEhB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAElB,YAAY,EAEZ,sBAAsB,EAEtB,kBAAkB,EAElB,YAAY,GAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,GAEhB,MAAM,sBAAsB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/toolkit",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Reusable app-building UI and helpers for Agent-Native apps.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -75,6 +75,20 @@
75
75
  "import": "./dist/dashboard/*.js",
76
76
  "default": "./dist/dashboard/*.js"
77
77
  },
78
+ "./data-grid": {
79
+ "types": "./dist/data-grid/index.d.ts",
80
+ "browser": "./dist/data-grid/index.js",
81
+ "development": "./dist/data-grid/index.js",
82
+ "import": "./dist/data-grid/index.js",
83
+ "default": "./dist/data-grid/index.js"
84
+ },
85
+ "./data-grid/*": {
86
+ "types": "./dist/data-grid/*.d.ts",
87
+ "browser": "./dist/data-grid/*.js",
88
+ "development": "./dist/data-grid/*.js",
89
+ "import": "./dist/data-grid/*.js",
90
+ "default": "./dist/data-grid/*.js"
91
+ },
78
92
  "./chat-history": {
79
93
  "types": "./dist/chat-history/index.d.ts",
80
94
  "browser": "./dist/chat-history/index.js",
@@ -47,6 +47,7 @@ import {
47
47
  import {
48
48
  DEFAULT_VOICE_DICTATION_ENABLED,
49
49
  TiptapComposer,
50
+ type ComposerAgentOption,
50
51
  type ComposerSubmitIntent,
51
52
  type TiptapComposerHandle,
52
53
  type TiptapComposerSubmitOptions,
@@ -149,6 +150,12 @@ export interface PromptComposerProps {
149
150
  selectedEffort?: ReasoningEffort;
150
151
  onModelChange?: (model: string, engine: string) => void;
151
152
  onEffortChange?: (effort: ReasoningEffort) => void;
153
+ /** Local or hosted agent runtimes shown above the model list. */
154
+ availableAgents?: ComposerAgentOption[];
155
+ /** Selected agent runtime identifier. */
156
+ selectedAgent?: string;
157
+ /** Callback when the user picks an agent runtime. */
158
+ onAgentChange?: (agent: string) => void;
152
159
  /**
153
160
  * Enable server-backed model/provider status checks. Defaults off when the
154
161
  * host supplies model state and callbacks, otherwise on.
@@ -515,6 +522,9 @@ function PromptComposerInner({
515
522
  selectedEffort,
516
523
  onModelChange,
517
524
  onEffortChange,
525
+ availableAgents,
526
+ selectedAgent,
527
+ onAgentChange,
518
528
  modelStatusChecksEnabled,
519
529
  onTextChange,
520
530
  onConnectProvider,
@@ -689,10 +699,13 @@ function PromptComposerInner({
689
699
  selectedModel={composerModel}
690
700
  selectedEffort={composerEffort}
691
701
  availableModels={composerModelGroups}
702
+ availableAgents={availableAgents}
703
+ selectedAgent={selectedAgent}
692
704
  showAutoModelOption={showAutoModelOption}
693
705
  modelListLoading={composerModelListLoading}
694
706
  onModelChange={handleModelChange}
695
707
  onEffortChange={handleEffortChange}
708
+ onAgentChange={onAgentChange}
696
709
  providerConnectStatusEnabled={resolvedModelStatusChecksEnabled}
697
710
  onConnectProvider={onConnectProvider}
698
711
  onConnectLocalRuntime={onConnectLocalRuntime}
@@ -652,6 +652,19 @@ function ComposerModeChip({
652
652
 
653
653
  type ExecMode = "build" | "plan";
654
654
 
655
+ export interface ComposerAgentOption {
656
+ /** Stable host-defined identifier for the agent runtime. */
657
+ id: string;
658
+ /** Human-readable runtime name shown in the picker. */
659
+ label: string;
660
+ /** Optional short detail shown below the runtime name. */
661
+ description?: string;
662
+ /** Whether this runtime can be selected right now. */
663
+ configured?: boolean;
664
+ /** Optional status text such as "Installed" or "Sign in". */
665
+ statusLabel?: string;
666
+ }
667
+
655
668
  export interface TiptapComposerProps {
656
669
  placeholder?: string;
657
670
  disabled?: boolean;
@@ -744,6 +757,12 @@ export interface TiptapComposerProps {
744
757
  onModelChange?: (model: string, engine: string) => void;
745
758
  /** Callback when user picks an effort */
746
759
  onEffortChange?: (effort: ReasoningEffort) => void;
760
+ /** Local or hosted agent runtimes shown above the model list. */
761
+ availableAgents?: ComposerAgentOption[];
762
+ /** Selected agent runtime identifier. Defaults to the built-in agent. */
763
+ selectedAgent?: string;
764
+ /** Callback when the user picks an agent runtime. */
765
+ onAgentChange?: (agent: string) => void;
747
766
  /**
748
767
  * Disable Builder/provider status polling for hosts that supply provider
749
768
  * state through another channel, such as Electron IPC.
@@ -1225,10 +1244,13 @@ function ModelSelector({
1225
1244
  model,
1226
1245
  effort,
1227
1246
  engines,
1247
+ agents,
1248
+ selectedAgent,
1228
1249
  showAutoModelOption = true,
1229
1250
  modelListLoading = false,
1230
1251
  onChange,
1231
1252
  onEffortChange,
1253
+ onAgentChange,
1232
1254
  providerConnectStatusEnabled = true,
1233
1255
  onConnectProvider,
1234
1256
  onConnectLocalRuntime,
@@ -1236,6 +1258,8 @@ function ModelSelector({
1236
1258
  }: {
1237
1259
  model: string;
1238
1260
  effort?: ReasoningEffort;
1261
+ agents?: ComposerAgentOption[];
1262
+ selectedAgent?: string;
1239
1263
  engines: Array<{
1240
1264
  engine: string;
1241
1265
  label: string;
@@ -1248,6 +1272,7 @@ function ModelSelector({
1248
1272
  modelListLoading?: boolean;
1249
1273
  onChange: (model: string, engine: string) => void;
1250
1274
  onEffortChange?: (effort: ReasoningEffort) => void;
1275
+ onAgentChange?: (agent: string) => void;
1251
1276
  providerConnectStatusEnabled?: boolean;
1252
1277
  onConnectProvider?: () => void;
1253
1278
  onConnectLocalRuntime?: (engine: string) => void;
@@ -1286,6 +1311,10 @@ function ModelSelector({
1286
1311
  ),
1287
1312
  [reasoning?.label, t],
1288
1313
  );
1314
+ const selectedAgentOption = agents?.find(
1315
+ (agent) => agent.id === (selectedAgent ?? "default"),
1316
+ );
1317
+ const selectedAgentLabel = selectedAgentOption?.label ?? "Default";
1289
1318
 
1290
1319
  // Collapse non-selected families by default. The family containing the
1291
1320
  // currently-selected model stays expanded so the user sees their pick at
@@ -1391,11 +1420,17 @@ function ModelSelector({
1391
1420
  defaultValue: "Reasoning",
1392
1421
  })}: ${effortLabel(selectedEffort)}`
1393
1422
  : ""
1423
+ }${
1424
+ selectedAgentOption && selectedAgentOption.id !== "default"
1425
+ ? `. Agent: ${selectedAgentLabel}`
1426
+ : ""
1394
1427
  }`}
1395
1428
  className="agent-composer-model-button flex min-w-0 max-w-[10.5rem] shrink items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground"
1396
1429
  >
1397
1430
  <span className="min-w-0 truncate">
1398
- {compactComposerModelName(model, t)}
1431
+ {selectedAgentOption && selectedAgentOption.id !== "default"
1432
+ ? selectedAgentLabel
1433
+ : compactComposerModelName(model, t)}
1399
1434
  </span>
1400
1435
  {effortOptions.length > 0 && (
1401
1436
  <span className="agent-composer-model-effort min-w-0 shrink truncate text-muted-foreground/70">
@@ -1414,6 +1449,71 @@ function ModelSelector({
1414
1449
  className="z-[260] box-border w-72 overflow-y-auto rounded-lg border-border p-0 py-1 shadow-lg"
1415
1450
  style={MODEL_SELECTOR_POPOVER_STYLE}
1416
1451
  >
1452
+ {agents && agents.length > 0 && (
1453
+ <>
1454
+ <div className="px-3 pb-1 pt-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1455
+ Agents
1456
+ </div>
1457
+ {agents.map((agent) => {
1458
+ const isSelected = agent.id === (selectedAgent ?? "default");
1459
+ const isConfigured = agent.configured !== false;
1460
+ const canConnect =
1461
+ !isConfigured && Boolean(onConnectLocalRuntime);
1462
+ const statusLabel =
1463
+ agent.statusLabel ??
1464
+ (!isConfigured ? "Unavailable" : undefined);
1465
+ return (
1466
+ <div
1467
+ key={agent.id}
1468
+ className="flex items-center hover:bg-accent/30"
1469
+ >
1470
+ <button
1471
+ type="button"
1472
+ disabled={!isConfigured || !onAgentChange}
1473
+ aria-current={isSelected ? "true" : undefined}
1474
+ onClick={() => {
1475
+ if (!isConfigured) return;
1476
+ onAgentChange?.(agent.id);
1477
+ setOpen(false);
1478
+ }}
1479
+ className={`flex min-w-0 flex-1 items-center gap-3 px-3 py-1.5 text-start ${
1480
+ isConfigured
1481
+ ? "hover:bg-accent/50"
1482
+ : "cursor-default opacity-50"
1483
+ }`}
1484
+ >
1485
+ <span className="min-w-0 flex-1">
1486
+ <span className="block truncate text-[13px] text-foreground">
1487
+ {agent.label}
1488
+ </span>
1489
+ {agent.description && (
1490
+ <span className="block truncate text-[11px] text-muted-foreground">
1491
+ {agent.description}
1492
+ </span>
1493
+ )}
1494
+ </span>
1495
+ {isSelected && isConfigured && (
1496
+ <IconCheck className="h-3.5 w-3.5 shrink-0 text-primary" />
1497
+ )}
1498
+ </button>
1499
+ {!isConfigured && statusLabel && (
1500
+ <button
1501
+ type="button"
1502
+ disabled={!canConnect}
1503
+ className="ms-auto max-w-[6rem] shrink-0 truncate px-3 py-1.5 text-end text-[10px] text-muted-foreground/70 hover:text-foreground disabled:cursor-default"
1504
+ onClick={() => {
1505
+ if (canConnect) connectLocalRuntime(agent.id);
1506
+ }}
1507
+ >
1508
+ {statusLabel}
1509
+ </button>
1510
+ )}
1511
+ </div>
1512
+ );
1513
+ })}
1514
+ <div className="my-1 border-t border-border" />
1515
+ </>
1516
+ )}
1417
1517
  {showBuilderCta && (
1418
1518
  <>
1419
1519
  <button
@@ -1771,6 +1871,9 @@ export function TiptapComposer({
1771
1871
  modelListLoading,
1772
1872
  onModelChange,
1773
1873
  onEffortChange,
1874
+ availableAgents,
1875
+ selectedAgent,
1876
+ onAgentChange,
1774
1877
  providerConnectStatusEnabled,
1775
1878
  onConnectProvider,
1776
1879
  onConnectLocalRuntime,
@@ -3276,10 +3379,13 @@ export function TiptapComposer({
3276
3379
  model={selectedModel}
3277
3380
  effort={selectedEffort}
3278
3381
  engines={availableModels}
3382
+ agents={availableAgents}
3383
+ selectedAgent={selectedAgent}
3279
3384
  showAutoModelOption={showAutoModelOption}
3280
3385
  modelListLoading={modelListLoading}
3281
3386
  onChange={onModelChange}
3282
3387
  onEffortChange={onEffortChange}
3388
+ onAgentChange={onAgentChange}
3283
3389
  providerConnectStatusEnabled={providerConnectStatusEnabled}
3284
3390
  onConnectProvider={onConnectProvider}
3285
3391
  onConnectLocalRuntime={onConnectLocalRuntime}
@@ -0,0 +1,243 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import {
8
+ DataGrid,
9
+ dataGridColumnTemplate,
10
+ type DataGridColumn,
11
+ } from "./index.js";
12
+
13
+ interface Row {
14
+ id: string;
15
+ name: string;
16
+ status: string;
17
+ }
18
+
19
+ const columns: readonly DataGridColumn<Row>[] = [
20
+ {
21
+ id: "name",
22
+ label: "Name",
23
+ width: 180,
24
+ getValue: (row) => row.name,
25
+ renderCell: ({ value }) => String(value ?? ""),
26
+ },
27
+ {
28
+ id: "status",
29
+ label: "Status",
30
+ width: 120,
31
+ getValue: (row) => row.status,
32
+ renderCell: ({ value }) => String(value ?? ""),
33
+ renderEditor: ({ value, onCommit }) => (
34
+ <input
35
+ aria-label="Status editor"
36
+ defaultValue={String(value ?? "")}
37
+ onKeyDown={(event) => {
38
+ if (event.key === "Enter") onCommit(event.currentTarget.value);
39
+ }}
40
+ />
41
+ ),
42
+ },
43
+ ];
44
+
45
+ const rows: readonly Row[] = [
46
+ { id: "row-1", name: "First", status: "Open" },
47
+ { id: "row-2", name: "Second", status: "Closed" },
48
+ ];
49
+
50
+ describe("data-grid", () => {
51
+ it("builds a bounded template and includes the selection column only when requested", () => {
52
+ expect(dataGridColumnTemplate(columns, { name: 40 }, "none")).toBe(
53
+ "96px 120px",
54
+ );
55
+ expect(dataGridColumnTemplate(columns, { name: 220 }, "multiple")).toBe(
56
+ "40px 220px 120px",
57
+ );
58
+ });
59
+
60
+ describe("DataGrid", () => {
61
+ let container: HTMLDivElement;
62
+ let root: Root;
63
+
64
+ beforeEach(() => {
65
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
66
+ container = document.createElement("div");
67
+ document.body.appendChild(container);
68
+ root = createRoot(container);
69
+ });
70
+
71
+ afterEach(() => {
72
+ act(() => root.unmount());
73
+ container.remove();
74
+ vi.restoreAllMocks();
75
+ vi.unstubAllGlobals();
76
+ });
77
+
78
+ it("moves the active cell with arrows and commits an editor value", () => {
79
+ const onActiveCellChange = vi.fn();
80
+ const onCellCommit = vi.fn();
81
+
82
+ act(() => {
83
+ root.render(
84
+ <DataGrid
85
+ rows={rows}
86
+ columns={columns}
87
+ getRowId={(row) => row.id}
88
+ onActiveCellChange={onActiveCellChange}
89
+ onCellCommit={onCellCommit}
90
+ />,
91
+ );
92
+ });
93
+
94
+ const firstNameCell = container.querySelector<HTMLElement>(
95
+ '[data-data-grid-row-id="row-1"][data-data-grid-column-id="name"]',
96
+ );
97
+ expect(firstNameCell?.getAttribute("tabindex")).toBe("0");
98
+
99
+ act(() => {
100
+ firstNameCell?.dispatchEvent(
101
+ new KeyboardEvent("keydown", { bubbles: true, key: "ArrowRight" }),
102
+ );
103
+ });
104
+ expect(onActiveCellChange).toHaveBeenCalledWith({
105
+ rowId: "row-1",
106
+ columnId: "status",
107
+ editing: false,
108
+ });
109
+
110
+ const statusCell = container.querySelector<HTMLElement>(
111
+ '[data-data-grid-row-id="row-1"][data-data-grid-column-id="status"]',
112
+ );
113
+ act(() => {
114
+ statusCell?.dispatchEvent(
115
+ new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }),
116
+ );
117
+ });
118
+ expect(
119
+ container.querySelector('input[aria-label="Status editor"]'),
120
+ ).not.toBeNull();
121
+
122
+ const input = container.querySelector<HTMLInputElement>(
123
+ 'input[aria-label="Status editor"]',
124
+ );
125
+ expect(input).not.toBeNull();
126
+ if (input) {
127
+ input.value = "In progress";
128
+ act(() => {
129
+ input.dispatchEvent(
130
+ new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }),
131
+ );
132
+ });
133
+ }
134
+ expect(onCellCommit).toHaveBeenCalledWith({
135
+ row: rows[0],
136
+ rowIndex: 0,
137
+ column: columns[1],
138
+ previousValue: "Open",
139
+ value: "In progress",
140
+ });
141
+ });
142
+
143
+ it("supports controlled multi-row selection", () => {
144
+ const onSelectedRowIdsChange = vi.fn();
145
+
146
+ act(() => {
147
+ root.render(
148
+ <DataGrid
149
+ rows={rows}
150
+ columns={columns}
151
+ getRowId={(row) => row.id}
152
+ selection="multiple"
153
+ onSelectedRowIdsChange={onSelectedRowIdsChange}
154
+ />,
155
+ );
156
+ });
157
+
158
+ const rowCheckbox = container.querySelector<HTMLInputElement>(
159
+ '[aria-label="Select row row-1"]',
160
+ );
161
+ act(() => rowCheckbox?.click());
162
+ expect(onSelectedRowIdsChange).toHaveBeenCalledTimes(1);
163
+ expect(Array.from(onSelectedRowIdsChange.mock.calls[0][0])).toEqual([
164
+ "row-1",
165
+ ]);
166
+ });
167
+
168
+ it("reports pointer-based column resizing through the app callback", () => {
169
+ const onColumnWidthsChange = vi.fn();
170
+
171
+ act(() => {
172
+ root.render(
173
+ <DataGrid
174
+ rows={rows}
175
+ columns={columns}
176
+ getRowId={(row) => row.id}
177
+ onColumnWidthsChange={onColumnWidthsChange}
178
+ />,
179
+ );
180
+ });
181
+
182
+ const resizeHandle = container.querySelector<HTMLButtonElement>(
183
+ '[aria-label="Resize name column"]',
184
+ );
185
+ expect(resizeHandle).not.toBeNull();
186
+
187
+ act(() => {
188
+ resizeHandle?.dispatchEvent(
189
+ new PointerEvent("pointerdown", {
190
+ bubbles: true,
191
+ button: 0,
192
+ clientX: 100,
193
+ }),
194
+ );
195
+ document.dispatchEvent(
196
+ new PointerEvent("pointermove", {
197
+ bubbles: true,
198
+ clientX: 180,
199
+ }),
200
+ );
201
+ document.dispatchEvent(
202
+ new PointerEvent("pointerup", { bubbles: true, clientX: 180 }),
203
+ );
204
+ });
205
+
206
+ expect(onColumnWidthsChange).toHaveBeenLastCalledWith({ name: 260 });
207
+ });
208
+
209
+ it("lets an app own the outer header and body while sharing the grid template", () => {
210
+ const renderHeader = vi.fn(({ gridTemplateColumns }) => (
211
+ <div data-testid="custom-header" style={{ gridTemplateColumns }} />
212
+ ));
213
+ const renderBody = vi.fn(({ gridTemplateColumns }) => (
214
+ <div data-testid="custom-body" style={{ gridTemplateColumns }} />
215
+ ));
216
+
217
+ act(() => {
218
+ root.render(
219
+ <DataGrid
220
+ rows={rows}
221
+ columns={columns}
222
+ getRowId={(row) => row.id}
223
+ renderHeader={renderHeader}
224
+ renderBody={renderBody}
225
+ />,
226
+ );
227
+ });
228
+
229
+ expect(
230
+ container.querySelector("[data-testid=custom-header]"),
231
+ ).not.toBeNull();
232
+ expect(
233
+ container.querySelector("[data-testid=custom-body]"),
234
+ ).not.toBeNull();
235
+ expect(renderHeader).toHaveBeenCalledWith(
236
+ expect.objectContaining({ gridTemplateColumns: "180px 120px" }),
237
+ );
238
+ expect(renderBody).toHaveBeenCalledWith(
239
+ expect.objectContaining({ rows, columns }),
240
+ );
241
+ });
242
+ });
243
+ });