@agent-native/dispatch 0.23.5 → 0.24.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 (99) hide show
  1. package/dist/actions/list-connected-agents.d.ts +1 -0
  2. package/dist/actions/list-connected-agents.js +5 -1
  3. package/dist/actions/list-connected-agents.js.map +1 -1
  4. package/dist/actions/list-dispatch-usage-metrics.d.ts +2 -0
  5. package/dist/actions/list-dispatch-usage-metrics.js +11 -1
  6. package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
  7. package/dist/actions/remix-workspace-template.js +4 -3
  8. package/dist/actions/remix-workspace-template.js.map +1 -1
  9. package/dist/actions/scaffold-workspace-app.js +3 -2
  10. package/dist/actions/scaffold-workspace-app.js.map +1 -1
  11. package/dist/actions/start-workspace-app-creation.js +3 -2
  12. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  13. package/dist/actions/view-screen.js +11 -1
  14. package/dist/actions/view-screen.js.map +1 -1
  15. package/dist/components/app-icon.js +1 -1
  16. package/dist/components/app-icon.js.map +1 -1
  17. package/dist/components/connected-app-card.d.ts.map +1 -1
  18. package/dist/components/connected-app-card.js +2 -1
  19. package/dist/components/connected-app-card.js.map +1 -1
  20. package/dist/components/layout/Layout.d.ts.map +1 -1
  21. package/dist/components/layout/Layout.js +6 -7
  22. package/dist/components/layout/Layout.js.map +1 -1
  23. package/dist/components/workspace-template-card.d.ts.map +1 -1
  24. package/dist/components/workspace-template-card.js +8 -14
  25. package/dist/components/workspace-template-card.js.map +1 -1
  26. package/dist/hooks/use-navigation-state.d.ts +2 -0
  27. package/dist/hooks/use-navigation-state.d.ts.map +1 -1
  28. package/dist/hooks/use-navigation-state.js +10 -0
  29. package/dist/hooks/use-navigation-state.js.map +1 -1
  30. package/dist/lib/embed-session-recovery.d.ts +3 -0
  31. package/dist/lib/embed-session-recovery.d.ts.map +1 -0
  32. package/dist/lib/embed-session-recovery.js +18 -0
  33. package/dist/lib/embed-session-recovery.js.map +1 -0
  34. package/dist/lib/other-apps.d.ts +2 -0
  35. package/dist/lib/other-apps.d.ts.map +1 -1
  36. package/dist/lib/other-apps.js.map +1 -1
  37. package/dist/lib/workspace-apps.d.ts +4 -0
  38. package/dist/lib/workspace-apps.d.ts.map +1 -1
  39. package/dist/lib/workspace-apps.js +7 -0
  40. package/dist/lib/workspace-apps.js.map +1 -1
  41. package/dist/lib/workspace-apps.test.d.ts +2 -0
  42. package/dist/lib/workspace-apps.test.d.ts.map +1 -0
  43. package/dist/lib/workspace-apps.test.js +16 -0
  44. package/dist/lib/workspace-apps.test.js.map +1 -0
  45. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  46. package/dist/routes/pages/apps.$appId.js +10 -11
  47. package/dist/routes/pages/apps.$appId.js.map +1 -1
  48. package/dist/routes/pages/chat.d.ts.map +1 -1
  49. package/dist/routes/pages/chat.js +7 -2
  50. package/dist/routes/pages/chat.js.map +1 -1
  51. package/dist/routes/pages/metrics.d.ts.map +1 -1
  52. package/dist/routes/pages/metrics.js +143 -17
  53. package/dist/routes/pages/metrics.js.map +1 -1
  54. package/dist/routes/pages/settings.d.ts.map +1 -1
  55. package/dist/routes/pages/settings.js +13 -1
  56. package/dist/routes/pages/settings.js.map +1 -1
  57. package/dist/server/lib/usage-metrics-store.d.ts +17 -0
  58. package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
  59. package/dist/server/lib/usage-metrics-store.js +187 -30
  60. package/dist/server/lib/usage-metrics-store.js.map +1 -1
  61. package/dist/server/plugins/agent-chat.js +2 -0
  62. package/dist/server/plugins/agent-chat.js.map +1 -1
  63. package/dist/server/plugins/integrations.d.ts.map +1 -1
  64. package/dist/server/plugins/integrations.js +1 -0
  65. package/dist/server/plugins/integrations.js.map +1 -1
  66. package/package.json +3 -3
  67. package/src/actions/list-connected-agents.spec.ts +73 -0
  68. package/src/actions/list-connected-agents.ts +6 -2
  69. package/src/actions/list-dispatch-usage-metrics.ts +11 -1
  70. package/src/actions/remix-workspace-template.spec.ts +29 -0
  71. package/src/actions/remix-workspace-template.ts +13 -6
  72. package/src/actions/scaffold-workspace-app.ts +12 -5
  73. package/src/actions/start-workspace-app-creation.ts +8 -2
  74. package/src/actions/view-screen.ts +13 -1
  75. package/src/components/app-icon.spec.tsx +34 -0
  76. package/src/components/app-icon.tsx +1 -1
  77. package/src/components/connected-app-card.tsx +3 -1
  78. package/src/components/dispatch-control-plane.spec.tsx +8 -1
  79. package/src/components/layout/Layout.spec.tsx +3 -0
  80. package/src/components/layout/Layout.tsx +13 -9
  81. package/src/components/workspace-template-card.spec.tsx +1 -1
  82. package/src/components/workspace-template-card.tsx +9 -16
  83. package/src/hooks/use-navigation-state.ts +12 -0
  84. package/src/lib/embed-session-recovery.spec.ts +63 -0
  85. package/src/lib/embed-session-recovery.ts +26 -0
  86. package/src/lib/other-apps.ts +2 -0
  87. package/src/lib/workspace-apps.test.ts +20 -0
  88. package/src/lib/workspace-apps.ts +10 -0
  89. package/src/routes/pages/apps.$appId.tsx +10 -11
  90. package/src/routes/pages/chat.spec.tsx +10 -0
  91. package/src/routes/pages/chat.tsx +8 -0
  92. package/src/routes/pages/metrics.tsx +452 -102
  93. package/src/routes/pages/settings.tsx +13 -1
  94. package/src/server/lib/usage-metrics-store.spec.ts +117 -0
  95. package/src/server/lib/usage-metrics-store.ts +249 -30
  96. package/src/server/plugins/agent-chat.spec.ts +4 -0
  97. package/src/server/plugins/agent-chat.ts +2 -0
  98. package/src/server/plugins/integrations.ts +1 -0
  99. package/src/styles/dispatch.css +9 -0
@@ -5,6 +5,8 @@ export interface ConnectedAppSummary {
5
5
  name: string;
6
6
  description?: string;
7
7
  url: string;
8
+ /** Canonical app-home URL used for launchers; `url` remains the A2A endpoint. */
9
+ homeUrl?: string;
8
10
  color?: string;
9
11
  source?: "builtin" | "custom" | "workspace";
10
12
  }
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { workspaceAppEmbedTarget } from "./workspace-apps";
4
+
5
+ describe("workspaceAppEmbedTarget", () => {
6
+ it("uses the app URL as the embed root when a mount path is also present", () => {
7
+ expect(
8
+ workspaceAppEmbedTarget({
9
+ path: "/content",
10
+ url: "https://workspace.example.test/content",
11
+ }),
12
+ ).toEqual({ url: "https://workspace.example.test/content" });
13
+ });
14
+
15
+ it("falls back to the mounted path when no app URL is available", () => {
16
+ expect(workspaceAppEmbedTarget({ path: "/content", url: null })).toEqual({
17
+ path: "/content",
18
+ });
19
+ });
20
+ });
@@ -70,6 +70,16 @@ export function workspaceAppHref(app: WorkspaceAppSummary): string | null {
70
70
  return app.path || app.url || null;
71
71
  }
72
72
 
73
+ export function workspaceAppEmbedTarget(
74
+ app: Pick<WorkspaceAppSummary, "path" | "url">,
75
+ ): { path?: string; url?: string } {
76
+ const url = app.url?.trim();
77
+ if (url) return { url };
78
+
79
+ const path = app.path.trim();
80
+ return path.startsWith("/") ? { path } : path ? { url: path } : {};
81
+ }
82
+
73
83
  export function isPendingBuilderHref(app: WorkspaceAppSummary): boolean {
74
84
  return app.status === "pending" && !!app.builderUrl;
75
85
  }
@@ -13,7 +13,9 @@ import { Badge } from "../../components/ui/badge";
13
13
  import { Button } from "../../components/ui/button";
14
14
  import { Skeleton } from "../../components/ui/skeleton";
15
15
  import { Spinner } from "../../components/ui/spinner";
16
+ import { isEmbedSessionExpiredMessage } from "../../lib/embed-session-recovery";
16
17
  import {
18
+ workspaceAppEmbedTarget,
17
19
  workspaceAppHref,
18
20
  type WorkspaceAppSummary,
19
21
  } from "../../lib/workspace-apps";
@@ -61,12 +63,12 @@ export default function WorkspaceAppRoute() {
61
63
  });
62
64
  const embedInput = useMemo<EmbedSessionInput | null>(() => {
63
65
  if (!app || !href) return null;
64
- const path = app.path.trim();
65
- if (path.startsWith("/")) {
66
- return { app: app.id, path, chrome: "minimal" };
67
- }
68
- return { app: app.id, url: href, chrome: "minimal" };
69
- }, [app?.id, app?.path, href]);
66
+ return {
67
+ app: app.id,
68
+ ...workspaceAppEmbedTarget(app),
69
+ chrome: "minimal",
70
+ };
71
+ }, [app?.id, app?.path, app?.url, href]);
70
72
 
71
73
  useEffect(() => {
72
74
  if (!app || app.status === "pending" || !embedInput) return;
@@ -99,12 +101,8 @@ export default function WorkspaceAppRoute() {
99
101
 
100
102
  useEffect(() => {
101
103
  const handleEmbedSessionExpired = (event: MessageEvent) => {
102
- if (
103
- event.data?.type !== "agentNative.embedSessionExpired" ||
104
- event.source !== embedFrameRef.current?.contentWindow
105
- ) {
104
+ if (!isEmbedSessionExpiredMessage(event, embedFrameRef.current, null))
106
105
  return;
107
- }
108
106
  setEmbedAttempt((attempt) => attempt + 1);
109
107
  };
110
108
 
@@ -222,6 +220,7 @@ export default function WorkspaceAppRoute() {
222
220
  <div className="min-h-0 flex-1 bg-muted/20">
223
221
  {embedUrl ? (
224
222
  <iframe
223
+ key={`${embedUrl}:${embedAttempt}`}
225
224
  data-dispatch-workspace-app-frame
226
225
  src={embedUrl}
227
226
  title={app.name}
@@ -16,6 +16,7 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
16
16
  return <>{props.composerSlot as ReactNode}</>;
17
17
  },
18
18
  markAgentChatHomeHandoff: vi.fn(),
19
+ readChatFirstMode: () => true,
19
20
  navigateWithAgentChatViewTransition: (
20
21
  navigate: (path: string) => void,
21
22
  path: string,
@@ -23,6 +24,10 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
23
24
  sendToAgentChat: vi.fn(),
24
25
  }));
25
26
 
27
+ vi.mock("../../components/layout/Layout", () => ({
28
+ useDispatchExtensions: () => undefined,
29
+ }));
30
+
26
31
  vi.mock("@agent-native/core/client/api-path", () => ({
27
32
  agentNativePath: (path: string) => path,
28
33
  appApiPath: (path: string) => path,
@@ -74,6 +79,7 @@ describe("Dispatch ChatRoute", () => {
74
79
  centerComposerWhenEmpty: true,
75
80
  composerLayoutVariant: "hero",
76
81
  composerPlaceholder: "Ask Dispatch...",
82
+ suppressInlineOpenApp: true,
77
83
  });
78
84
  expect(container.textContent).toContain("Chat across your apps");
79
85
  });
@@ -106,6 +112,10 @@ describe("Dispatch ChatRoute", () => {
106
112
  expect(clientState.surfaceProps).not.toHaveProperty(
107
113
  "composerLayoutVariant",
108
114
  );
115
+ expect(clientState.surfaceProps).toHaveProperty(
116
+ "suppressInlineOpenApp",
117
+ true,
118
+ );
109
119
  expect(container.textContent).not.toContain("Chat across your apps");
110
120
  });
111
121
  });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AgentChatSurface,
3
3
  markAgentChatHomeHandoff,
4
+ readChatFirstMode,
4
5
  } from "@agent-native/core/client/agent-chat";
5
6
  import { appBasePath, appPath } from "@agent-native/core/client/api-path";
6
7
  import { useT } from "@agent-native/core/client/i18n";
@@ -13,6 +14,7 @@ import {
13
14
  } from "react";
14
15
  import { useLocation, useNavigate } from "react-router";
15
16
 
17
+ import { useDispatchExtensions } from "../../components/layout/Layout";
16
18
  import { submitOverviewPrompt } from "../../lib/overview-chat";
17
19
 
18
20
  function chatThreadPath(threadId: string | null): string {
@@ -93,6 +95,11 @@ export default function ChatRoute() {
93
95
  const navigate = useNavigate();
94
96
  const routeThreadId = threadIdFromPath(location.pathname);
95
97
  const handledStateIds = useRef(new Set<string>());
98
+ const extensions = useDispatchExtensions();
99
+ const suppressInlineOpenApp =
100
+ extensions?.chatFirst === true ||
101
+ readChatFirstMode() ||
102
+ new URLSearchParams(location.search).get("chatFirst") === "1";
96
103
 
97
104
  const navigateThreadUrl = useCallback(
98
105
  (path: string, options?: { replace?: boolean }) =>
@@ -181,6 +188,7 @@ export default function ChatRoute() {
181
188
  showHeader={false}
182
189
  showTabBar={false}
183
190
  dynamicSuggestions={false}
191
+ suppressInlineOpenApp={suppressInlineOpenApp}
184
192
  suggestions={[]}
185
193
  emptyStateText={t("dispatch.pages.chatAcrossAppsDescription", {
186
194
  defaultValue: