@agent-native/toolkit 0.218.0 → 0.219.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 (43) hide show
  1. package/dist/app/agentkit/react/styles.css +13 -6
  2. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  3. package/dist/app/chat/AgentKitAssistantChat.js +1 -1
  4. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  5. package/dist/app/chat/AgentPanel.js +6 -6
  6. package/dist/app/chat/AgentPanel.js.map +1 -1
  7. package/dist/app/chat/MultiTabAssistantChat.js +2 -2
  8. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  9. package/dist/app/chat/agentkit-chat/action-widget.d.ts.map +1 -1
  10. package/dist/app/chat/agentkit-chat/action-widget.js +1 -0
  11. package/dist/app/chat/agentkit-chat/action-widget.js.map +1 -1
  12. package/dist/app/chat/chat/tool-call-display.d.ts +3 -1
  13. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  14. package/dist/app/chat/chat/tool-call-display.js +5 -4
  15. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  16. package/dist/app/chat/chat/tool-render-registry.d.ts +2 -0
  17. package/dist/app/chat/chat/tool-render-registry.d.ts.map +1 -1
  18. package/dist/app/chat/chat/tool-render-registry.js.map +1 -1
  19. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  20. package/dist/app/chat/conversation/AgentConversation.js +6 -1
  21. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  22. package/dist/app/shared/CommandMenu.d.ts +2 -0
  23. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  24. package/dist/app/shared/CommandMenu.js +11 -6
  25. package/dist/app/shared/CommandMenu.js.map +1 -1
  26. package/dist/composer/TiptapComposer.js +1 -1
  27. package/dist/composer/TiptapComposer.js.map +1 -1
  28. package/package.json +3 -3
  29. package/src/app/agentkit/react/components.spec.tsx +7 -5
  30. package/src/app/agentkit/react/styles.css +13 -6
  31. package/src/app/chat/AgentKitAssistantChat.spec.tsx +201 -27
  32. package/src/app/chat/AgentKitAssistantChat.tsx +1 -0
  33. package/src/app/chat/AgentPanel.tsx +8 -8
  34. package/src/app/chat/MultiTabAssistantChat.tsx +2 -2
  35. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +62 -0
  36. package/src/app/chat/agentkit-chat/action-widget.tsx +1 -0
  37. package/src/app/chat/chat/tool-call-display.tsx +10 -1
  38. package/src/app/chat/chat/tool-render-registry.tsx +2 -0
  39. package/src/app/chat/conversation/AgentConversation.spec.tsx +51 -1
  40. package/src/app/chat/conversation/AgentConversation.tsx +11 -2
  41. package/src/app/shared/CommandMenu.spec.tsx +175 -6
  42. package/src/app/shared/CommandMenu.tsx +24 -7
  43. package/src/composer/TiptapComposer.tsx +1 -1
@@ -116,7 +116,9 @@ describe("AgentConversationMessageView", () => {
116
116
  id: "example-renderer",
117
117
  match: "example-tool",
118
118
  Component: ({ context }) => (
119
- <div data-testid="example-renderer">{context.resultText}</div>
119
+ <div data-testid="example-renderer">
120
+ Custom renderer: {context.resultText}
121
+ </div>
120
122
  ),
121
123
  });
122
124
  act(() =>
@@ -144,11 +146,59 @@ describe("AgentConversationMessageView", () => {
144
146
  expect(container.querySelector('[data-testid="example-renderer"]')).toBe(
145
147
  null,
146
148
  );
149
+ expect(container.textContent).not.toContain("Custom renderer:");
150
+ expect(container.querySelector("pre span")?.textContent).toBe(
151
+ "Example Document failed.",
152
+ );
147
153
  expect(
148
154
  container.querySelector("pre span")?.hasAttribute("data-an-mask"),
149
155
  ).toBe(true);
150
156
  });
151
157
 
158
+ it.each([
159
+ ["running", "running"],
160
+ ["activity", "running"],
161
+ ["completed", "completed"],
162
+ ] as const)(
163
+ "passes %s status to conversation custom renderers as %s",
164
+ (state, status) => {
165
+ registerToolRenderer({
166
+ id: "example-status-renderer",
167
+ match: "example-status-tool",
168
+ Component: ({ context }) => (
169
+ <div data-testid="example-tool-status" data-status={context.status} />
170
+ ),
171
+ });
172
+ act(() =>
173
+ root.render(
174
+ <AgentConversationMessageView
175
+ message={{
176
+ id: "message-status",
177
+ role: "assistant",
178
+ parts: [
179
+ {
180
+ id: "tool-status",
181
+ type: "tool",
182
+ tool: {
183
+ id: "tool-status",
184
+ name: "example-status-tool",
185
+ state,
186
+ },
187
+ },
188
+ ],
189
+ }}
190
+ />,
191
+ ),
192
+ );
193
+
194
+ expect(
195
+ container
196
+ .querySelector('[data-testid="example-tool-status"]')
197
+ ?.getAttribute("data-status"),
198
+ ).toBe(status);
199
+ },
200
+ );
201
+
152
202
  it.each(["errored", "completed"] as const)(
153
203
  "blocks only an errored tool's MCP App from replays (%s)",
154
204
  (state) => {
@@ -17,7 +17,10 @@ import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
17
17
  import remarkGfm from "remark-gfm";
18
18
 
19
19
  import { ActionChatUiSurface } from "../chat/action-chat-ui-surface.js";
20
- import { resolveToolRenderer } from "../chat/tool-render-registry.js";
20
+ import {
21
+ resolveToolRenderer,
22
+ type ToolRendererContext,
23
+ } from "../chat/tool-render-registry.js";
21
24
  import {
22
25
  isBuiltinConnectRequiredResult,
23
26
  resolveBuiltinActionChatRenderer,
@@ -407,11 +410,17 @@ function parseJsonObject(value: string | undefined): Record<string, unknown> {
407
410
  function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
408
411
  const t = useT();
409
412
  const resultJson = parseJsonText(tool.result);
410
- const nativeToolContext = {
413
+ const nativeToolContext: ToolRendererContext = {
411
414
  toolName: tool.name,
412
415
  args: tool.args ?? parseJsonObject(tool.input),
413
416
  resultText: tool.result,
414
417
  resultJson: tool.resultJson ?? resultJson,
418
+ status:
419
+ tool.state === "errored"
420
+ ? undefined
421
+ : tool.state === "activity"
422
+ ? "running"
423
+ : tool.state,
415
424
  isRunning: tool.state === "running" || tool.state === "activity",
416
425
  chatUI: tool.chatUI,
417
426
  };
@@ -9,13 +9,19 @@ const agentEngineStatusMock = vi.hoisted(() => ({
9
9
  state: "missing" as "unknown" | "configured" | "missing" | "unavailable",
10
10
  }));
11
11
 
12
- vi.mock("@agent-native/core/client/use-agent-engine-configured", () => ({
13
- useAgentEngineConfigured: () => ({
14
- canChat: agentEngineStatusMock.state === "configured",
15
- missing: agentEngineStatusMock.state === "missing",
16
- state: agentEngineStatusMock.state,
12
+ vi.mock(
13
+ "@agent-native/core/client/use-agent-engine-configured",
14
+ async (importOriginal) => ({
15
+ ...(await importOriginal<
16
+ typeof import("@agent-native/core/client/use-agent-engine-configured")
17
+ >()),
18
+ useAgentEngineConfigured: () => ({
19
+ canChat: agentEngineStatusMock.state === "configured",
20
+ missing: agentEngineStatusMock.state === "missing",
21
+ state: agentEngineStatusMock.state,
22
+ }),
17
23
  }),
18
- }));
24
+ );
19
25
 
20
26
  vi.mock("../chat/chat/run-recovery.js", async () => {
21
27
  const { createElement } = await import("react");
@@ -44,6 +50,7 @@ import {
44
50
  openAgentSettings,
45
51
  useCommandMenuNestedDialog,
46
52
  useCommandMenuShortcut,
53
+ type AppCommandContext,
47
54
  type AppCommandGroup,
48
55
  type CommandMenuItemMatchContext,
49
56
  type CommandMenuItemMatcher,
@@ -1836,6 +1843,168 @@ describe("CommandMenu", () => {
1836
1843
  ).toContain("Selected preview");
1837
1844
  });
1838
1845
 
1846
+ it("renders opt-in app command previews in only the selected row", () => {
1847
+ renderAppCommands({
1848
+ groups: [
1849
+ {
1850
+ id: "results",
1851
+ order: 1,
1852
+ commands: [
1853
+ {
1854
+ id: "record",
1855
+ order: 1,
1856
+ label: "Meeting notes",
1857
+ inlinePreview: <span>First row preview</span>,
1858
+ onSelect() {},
1859
+ },
1860
+ {
1861
+ id: "meeting-agenda",
1862
+ order: 2,
1863
+ label: "Meeting agenda",
1864
+ inlinePreview: <span>Second row preview</span>,
1865
+ onSelect() {},
1866
+ },
1867
+ ],
1868
+ },
1869
+ ],
1870
+ });
1871
+
1872
+ const firstRow = document.querySelector(
1873
+ '[cmdk-item][data-value="app-command:results:record"]',
1874
+ );
1875
+ const secondRow = document.querySelector(
1876
+ '[cmdk-item][data-value="app-command:results:meeting-agenda"]',
1877
+ );
1878
+ expect(
1879
+ firstRow?.querySelector(
1880
+ '[data-testid="command-menu-app-command-preview"]',
1881
+ )?.textContent,
1882
+ ).toBe("First row preview");
1883
+ expect(
1884
+ secondRow?.querySelector(
1885
+ '[data-testid="command-menu-app-command-preview"]',
1886
+ ),
1887
+ ).toBeNull();
1888
+
1889
+ const input = document.querySelector<HTMLInputElement>("[cmdk-input]");
1890
+ expect(input).toBeTruthy();
1891
+ act(() =>
1892
+ input!.dispatchEvent(
1893
+ new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
1894
+ ),
1895
+ );
1896
+
1897
+ expect(
1898
+ firstRow?.querySelector(
1899
+ '[data-testid="command-menu-app-command-preview"]',
1900
+ ),
1901
+ ).toBeNull();
1902
+ expect(
1903
+ secondRow?.querySelector(
1904
+ '[data-testid="command-menu-app-command-preview"]',
1905
+ )?.textContent,
1906
+ ).toBe("Second row preview");
1907
+ });
1908
+
1909
+ it("keeps the selected app preview when search retains the highlighted row", () => {
1910
+ renderAppCommands({
1911
+ groups: [
1912
+ {
1913
+ id: "results",
1914
+ order: 1,
1915
+ commands: [
1916
+ {
1917
+ id: "meeting-notes",
1918
+ order: 1,
1919
+ label: "Meeting notes",
1920
+ inlinePreview: <span>Meeting preview</span>,
1921
+ onSelect() {},
1922
+ },
1923
+ {
1924
+ id: "other-task",
1925
+ order: 2,
1926
+ label: "Other task",
1927
+ inlinePreview: <span>Other preview</span>,
1928
+ onSelect() {},
1929
+ },
1930
+ ],
1931
+ },
1932
+ ],
1933
+ });
1934
+
1935
+ const selectedPreview = () =>
1936
+ document.querySelector(
1937
+ '[cmdk-item][data-value="app-command:results:meeting-notes"] [data-testid="command-menu-app-command-preview"]',
1938
+ )?.textContent;
1939
+
1940
+ expect(selectedPreview()).toBe("Meeting preview");
1941
+ search("Meeting");
1942
+ expect(selectedPreview()).toBe("Meeting preview");
1943
+ });
1944
+
1945
+ it("passes app context and search to an app command row preview", () => {
1946
+ const renderPreview = vi.fn(
1947
+ ({
1948
+ search: currentSearch,
1949
+ appContext,
1950
+ }: AppCommandContext<{ workspace: string }>) => (
1951
+ <span>{`${currentSearch}|${appContext?.workspace}`}</span>
1952
+ ),
1953
+ );
1954
+ renderAppCommands({
1955
+ initialSelection: "none",
1956
+ context: { workspace: "alpha" },
1957
+ groups: [
1958
+ {
1959
+ id: "results",
1960
+ order: 1,
1961
+ commands: [
1962
+ {
1963
+ id: "workspace",
1964
+ order: 1,
1965
+ label: "Open workspace",
1966
+ inlinePreview: renderPreview,
1967
+ onSelect() {},
1968
+ },
1969
+ ],
1970
+ },
1971
+ ],
1972
+ });
1973
+
1974
+ const input = document.querySelector<HTMLInputElement>("[cmdk-input]");
1975
+ expect(input).toBeTruthy();
1976
+ const moveDown = () =>
1977
+ act(() =>
1978
+ input!.dispatchEvent(
1979
+ new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
1980
+ ),
1981
+ );
1982
+ moveDown();
1983
+
1984
+ expect(renderPreview).toHaveBeenCalledWith(
1985
+ expect.objectContaining({
1986
+ search: "",
1987
+ appContext: { workspace: "alpha" },
1988
+ }),
1989
+ );
1990
+ expect(
1991
+ document.querySelector('[data-testid="command-menu-app-command-preview"]')
1992
+ ?.textContent,
1993
+ ).toBe("|alpha");
1994
+
1995
+ search("workspace");
1996
+ moveDown();
1997
+ expect(
1998
+ document.querySelector(
1999
+ '[cmdk-item][data-value="app-command:results:workspace"]',
2000
+ ),
2001
+ ).toBeTruthy();
2002
+ expect(
2003
+ document.querySelector('[data-testid="command-menu-app-command-preview"]')
2004
+ ?.textContent,
2005
+ ).toBe("workspace|alpha");
2006
+ });
2007
+
1839
2008
  it("hides the app preview when a non-app result is selected", () => {
1840
2009
  renderAppCommands({
1841
2010
  groups: [
@@ -304,6 +304,10 @@ interface AppCommandBase<TAppContext> {
304
304
  preview?:
305
305
  | ReactNode
306
306
  | ((context: AppCommandContext<TAppContext>) => ReactNode);
307
+ /** Content rendered at the end of this command's selected row. */
308
+ inlinePreview?:
309
+ | ReactNode
310
+ | ((context: AppCommandContext<TAppContext>) => ReactNode);
307
311
  visibleWhen?: (context: AppCommandContext<TAppContext>) => boolean;
308
312
  enabledWhen?: (context: AppCommandContext<TAppContext>) => boolean;
309
313
  deferSelect?: boolean;
@@ -877,9 +881,10 @@ export function CommandMenu<TAppContext = unknown>({
877
881
  firstEnabledAppCommand?.value ??
878
882
  NO_COMMAND_SELECTION)
879
883
  : undefined;
884
+ const selectedAppCommandValue = commandMenuValue ?? selectedCommandValue;
880
885
  const previewAppCommand =
881
886
  appCommandEntries.find(
882
- (entry) => entry.value === (commandMenuValue ?? selectedCommandValue),
887
+ (entry) => entry.value === selectedAppCommandValue,
883
888
  ) ?? selectedAppCommand;
884
889
  const appPreview = previewAppCommand?.command.preview;
885
890
  const renderedAppPreview =
@@ -973,6 +978,12 @@ export function CommandMenu<TAppContext = unknown>({
973
978
  {commands.map((command) => {
974
979
  const value = `app-command:${group.id}:${command.id}`;
975
980
  const enabled = command.enabledWhen?.(appCommandContext) ?? true;
981
+ const inlinePreview =
982
+ value === selectedAppCommandValue
983
+ ? typeof command.inlinePreview === "function"
984
+ ? command.inlinePreview(appCommandContext)
985
+ : command.inlinePreview
986
+ : null;
976
987
  return (
977
988
  <CommandItemPrimitive
978
989
  key={command.id}
@@ -1003,6 +1014,14 @@ export function CommandMenu<TAppContext = unknown>({
1003
1014
  {command.shortcutHint}
1004
1015
  </CommandShortcutPrimitive>
1005
1016
  ) : null}
1017
+ {inlinePreview != null ? (
1018
+ <div
1019
+ data-testid="command-menu-app-command-preview"
1020
+ className="ms-auto min-w-0"
1021
+ >
1022
+ {inlinePreview}
1023
+ </div>
1024
+ ) : null}
1006
1025
  </CommandItemPrimitive>
1007
1026
  );
1008
1027
  })}
@@ -1299,12 +1318,10 @@ export function CommandMenu<TAppContext = unknown>({
1299
1318
  onValueChange={(value) => {
1300
1319
  updateSearch(value);
1301
1320
  selectionEnabledRef.current = false;
1302
- if (initialSelection === "none") setSelectionEnabled(false);
1303
- setSelectedCommandValue(
1304
- initialSelection === "none"
1305
- ? nextEmptySelectionValue()
1306
- : "",
1307
- );
1321
+ if (initialSelection === "none") {
1322
+ setSelectionEnabled(false);
1323
+ setSelectedCommandValue(nextEmptySelectionValue());
1324
+ }
1308
1325
  }}
1309
1326
  onKeyDown={(event) => {
1310
1327
  if (
@@ -2207,7 +2207,7 @@ function ModelSelector({
2207
2207
  ? `. Agent: ${selectedAgentLabel}`
2208
2208
  : ""
2209
2209
  }`}
2210
- className="agent-composer-model-button flex min-w-0 max-w-none shrink-0 items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground"
2210
+ className="agent-composer-model-button flex min-w-0 max-w-none shrink items-center gap-1 overflow-hidden rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground"
2211
2211
  >
2212
2212
  <span className="min-w-0 truncate">
2213
2213
  {selectedAgentOption?.icon ? (