@agent-native/dispatch 0.14.2 → 0.14.4

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 (138) hide show
  1. package/README.md +114 -122
  2. package/dist/actions/approve-dispatch-change.d.ts +10 -10
  3. package/dist/actions/list_apps.d.ts +1 -0
  4. package/dist/actions/list_apps.js +16 -8
  5. package/dist/actions/list_apps.js.map +1 -1
  6. package/dist/actions/provider-api-register.js +64 -2
  7. package/dist/actions/provider-api-register.js.map +1 -1
  8. package/dist/actions/reject-dispatch-change.d.ts +10 -10
  9. package/dist/actions/set-mcp-app-access.js +1 -4
  10. package/dist/actions/set-mcp-app-access.js.map +1 -1
  11. package/dist/components/action-query-error.d.ts +8 -0
  12. package/dist/components/action-query-error.d.ts.map +1 -0
  13. package/dist/components/action-query-error.js +10 -0
  14. package/dist/components/action-query-error.js.map +1 -0
  15. package/dist/components/app-keys-popover.d.ts +4 -0
  16. package/dist/components/app-keys-popover.d.ts.map +1 -1
  17. package/dist/components/app-keys-popover.js +26 -14
  18. package/dist/components/app-keys-popover.js.map +1 -1
  19. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  20. package/dist/components/dispatch-control-plane.js +31 -27
  21. package/dist/components/dispatch-control-plane.js.map +1 -1
  22. package/dist/components/index.d.ts +1 -0
  23. package/dist/components/index.d.ts.map +1 -1
  24. package/dist/components/index.js +1 -0
  25. package/dist/components/index.js.map +1 -1
  26. package/dist/components/layout/Layout.d.ts +1 -0
  27. package/dist/components/layout/Layout.d.ts.map +1 -1
  28. package/dist/components/layout/Layout.js +19 -21
  29. package/dist/components/layout/Layout.js.map +1 -1
  30. package/dist/components/ui/sheet.js +1 -1
  31. package/dist/components/ui/sheet.js.map +1 -1
  32. package/dist/components/ui/toast.js +1 -1
  33. package/dist/components/ui/toast.js.map +1 -1
  34. package/dist/components/workspace-app-card.d.ts.map +1 -1
  35. package/dist/components/workspace-app-card.js +8 -5
  36. package/dist/components/workspace-app-card.js.map +1 -1
  37. package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
  38. package/dist/components/workspace-resource-effective-stack.js +6 -1
  39. package/dist/components/workspace-resource-effective-stack.js.map +1 -1
  40. package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -1
  41. package/dist/components/workspace-resource-impact-preview.js +6 -1
  42. package/dist/components/workspace-resource-impact-preview.js.map +1 -1
  43. package/dist/routes/pages/$appId.d.ts.map +1 -1
  44. package/dist/routes/pages/$appId.js +6 -1
  45. package/dist/routes/pages/$appId.js.map +1 -1
  46. package/dist/routes/pages/agents.d.ts.map +1 -1
  47. package/dist/routes/pages/agents.js +5 -8
  48. package/dist/routes/pages/agents.js.map +1 -1
  49. package/dist/routes/pages/approval.d.ts.map +1 -1
  50. package/dist/routes/pages/approval.js +6 -1
  51. package/dist/routes/pages/approval.js.map +1 -1
  52. package/dist/routes/pages/approvals.d.ts.map +1 -1
  53. package/dist/routes/pages/approvals.js +8 -5
  54. package/dist/routes/pages/approvals.js.map +1 -1
  55. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  56. package/dist/routes/pages/apps.$appId.js +4 -2
  57. package/dist/routes/pages/apps.$appId.js.map +1 -1
  58. package/dist/routes/pages/apps.d.ts.map +1 -1
  59. package/dist/routes/pages/apps.js +9 -3
  60. package/dist/routes/pages/apps.js.map +1 -1
  61. package/dist/routes/pages/audit.d.ts.map +1 -1
  62. package/dist/routes/pages/audit.js +4 -2
  63. package/dist/routes/pages/audit.js.map +1 -1
  64. package/dist/routes/pages/chat.d.ts.map +1 -1
  65. package/dist/routes/pages/chat.js +16 -2
  66. package/dist/routes/pages/chat.js.map +1 -1
  67. package/dist/routes/pages/destinations.d.ts.map +1 -1
  68. package/dist/routes/pages/destinations.js +5 -3
  69. package/dist/routes/pages/destinations.js.map +1 -1
  70. package/dist/routes/pages/identities.d.ts.map +1 -1
  71. package/dist/routes/pages/identities.js +5 -3
  72. package/dist/routes/pages/identities.js.map +1 -1
  73. package/dist/routes/pages/integrations.d.ts.map +1 -1
  74. package/dist/routes/pages/integrations.js +9 -3
  75. package/dist/routes/pages/integrations.js.map +1 -1
  76. package/dist/routes/pages/thread-debug.d.ts.map +1 -1
  77. package/dist/routes/pages/thread-debug.js +7 -6
  78. package/dist/routes/pages/thread-debug.js.map +1 -1
  79. package/dist/routes/pages/vault.d.ts.map +1 -1
  80. package/dist/routes/pages/vault.js +23 -7
  81. package/dist/routes/pages/vault.js.map +1 -1
  82. package/dist/routes/pages/workspace.d.ts.map +1 -1
  83. package/dist/routes/pages/workspace.js +9 -3
  84. package/dist/routes/pages/workspace.js.map +1 -1
  85. package/dist/server/lib/dispatch-store.d.ts +24 -21
  86. package/dist/server/lib/dispatch-store.d.ts.map +1 -1
  87. package/dist/server/lib/dispatch-store.js +59 -23
  88. package/dist/server/lib/dispatch-store.js.map +1 -1
  89. package/dist/server/lib/mcp-access-store.d.ts.map +1 -1
  90. package/dist/server/lib/mcp-access-store.js +30 -2
  91. package/dist/server/lib/mcp-access-store.js.map +1 -1
  92. package/dist/server/lib/vault-store.d.ts.map +1 -1
  93. package/dist/server/lib/vault-store.js +102 -37
  94. package/dist/server/lib/vault-store.js.map +1 -1
  95. package/dist/server/plugins/agent-chat.d.ts +1 -1
  96. package/package.json +2 -3
  97. package/src/actions/list_apps.spec.ts +52 -0
  98. package/src/actions/list_apps.ts +16 -8
  99. package/src/actions/provider-api-register.spec.ts +193 -0
  100. package/src/actions/provider-api-register.ts +66 -1
  101. package/src/actions/set-mcp-app-access.spec.ts +73 -0
  102. package/src/actions/set-mcp-app-access.ts +1 -5
  103. package/src/components/action-query-error.spec.tsx +56 -0
  104. package/src/components/action-query-error.tsx +32 -0
  105. package/src/components/app-keys-popover.spec.tsx +85 -0
  106. package/src/components/app-keys-popover.tsx +76 -50
  107. package/src/components/dispatch-control-plane.spec.tsx +106 -0
  108. package/src/components/dispatch-control-plane.tsx +71 -115
  109. package/src/components/index.ts +1 -0
  110. package/src/components/layout/Layout.spec.tsx +42 -2
  111. package/src/components/layout/Layout.tsx +22 -27
  112. package/src/components/ui/sheet.tsx +1 -1
  113. package/src/components/ui/toast.tsx +1 -1
  114. package/src/components/workspace-app-card.spec.tsx +80 -0
  115. package/src/components/workspace-app-card.tsx +36 -25
  116. package/src/components/workspace-resource-effective-stack.tsx +13 -1
  117. package/src/components/workspace-resource-impact-preview.tsx +12 -1
  118. package/src/routes/pages/$appId.tsx +17 -1
  119. package/src/routes/pages/agents.tsx +24 -11
  120. package/src/routes/pages/approval.tsx +16 -4
  121. package/src/routes/pages/approvals.tsx +108 -89
  122. package/src/routes/pages/apps.$appId.tsx +9 -2
  123. package/src/routes/pages/apps.tsx +20 -7
  124. package/src/routes/pages/audit.tsx +29 -20
  125. package/src/routes/pages/chat.spec.tsx +94 -0
  126. package/src/routes/pages/chat.tsx +30 -10
  127. package/src/routes/pages/destinations.tsx +75 -62
  128. package/src/routes/pages/identities.tsx +74 -63
  129. package/src/routes/pages/integrations.tsx +17 -10
  130. package/src/routes/pages/thread-debug.tsx +18 -12
  131. package/src/routes/pages/vault.tsx +59 -26
  132. package/src/routes/pages/workspace.tsx +69 -55
  133. package/src/server/lib/approval-fencing.spec.ts +420 -0
  134. package/src/server/lib/dispatch-store.ts +94 -21
  135. package/src/server/lib/mcp-access-store.spec.ts +116 -4
  136. package/src/server/lib/mcp-access-store.ts +37 -2
  137. package/src/server/lib/mcp-gateway.spec.ts +79 -3
  138. package/src/server/lib/vault-store.ts +151 -45
@@ -6,6 +6,7 @@ import {
6
6
  import { useState } from "react";
7
7
  import { toast } from "sonner";
8
8
 
9
+ import { ActionQueryError } from "../../components/action-query-error";
9
10
  import { DispatchShell } from "../../components/dispatch-shell";
10
11
  import { TaskQueueHealth } from "../../components/task-queue-health";
11
12
  import {
@@ -78,7 +79,8 @@ function QuickSendRow({
78
79
 
79
80
  export default function DestinationsRoute() {
80
81
  const t = useT();
81
- const { data } = useActionQuery("list-destinations", {});
82
+ const destinationsQuery = useActionQuery("list-destinations", {});
83
+ const { data } = destinationsQuery;
82
84
  const [form, setForm] = useState({
83
85
  name: "",
84
86
  platform: "slack",
@@ -115,70 +117,81 @@ export default function DestinationsRoute() {
115
117
  <h2 className="text-lg font-semibold text-foreground">
116
118
  {t("dispatch.pages.savedDestinations")}
117
119
  </h2>
118
- <div className="mt-4 space-y-3">
119
- {(data || []).map((destination: any) => (
120
- <div
121
- key={destination.id}
122
- className="rounded-xl border bg-muted/30 p-4"
123
- >
124
- <div className="flex items-start justify-between gap-3">
125
- <div>
126
- <div className="text-sm font-medium text-foreground">
127
- {destination.name}
128
- </div>
129
- <div className="mt-1 text-xs text-muted-foreground">
130
- {destination.platform} · {destination.destination}
131
- {destination.threadRef
132
- ? ` · thread ${destination.threadRef}`
133
- : ""}
120
+ {destinationsQuery.isError ? (
121
+ <ActionQueryError
122
+ className="mt-4"
123
+ error={destinationsQuery.error}
124
+ onRetry={() => void destinationsQuery.refetch()}
125
+ />
126
+ ) : (
127
+ <div className="mt-4 space-y-3">
128
+ {(data || []).map((destination: any) => (
129
+ <div
130
+ key={destination.id}
131
+ className="rounded-xl border bg-muted/30 p-4"
132
+ >
133
+ <div className="flex items-start justify-between gap-3">
134
+ <div>
135
+ <div className="text-sm font-medium text-foreground">
136
+ {destination.name}
137
+ </div>
138
+ <div className="mt-1 text-xs text-muted-foreground">
139
+ {destination.platform} · {destination.destination}
140
+ {destination.threadRef
141
+ ? ` · thread ${destination.threadRef}`
142
+ : ""}
143
+ </div>
144
+ {destination.notes && (
145
+ <p className="mt-2 text-sm text-muted-foreground">
146
+ {destination.notes}
147
+ </p>
148
+ )}
134
149
  </div>
135
- {destination.notes && (
136
- <p className="mt-2 text-sm text-muted-foreground">
137
- {destination.notes}
138
- </p>
139
- )}
140
- </div>
141
- <AlertDialog>
142
- <AlertDialogTrigger asChild>
143
- <Button variant="outline" size="sm">
144
- {t("dispatch.pages.delete")}
145
- </Button>
146
- </AlertDialogTrigger>
147
- <AlertDialogContent>
148
- <AlertDialogHeader>
149
- <AlertDialogTitle>
150
- {t("dispatch.pages.deleteDestinationTitle")}
151
- </AlertDialogTitle>
152
- <AlertDialogDescription>
153
- {t("dispatch.pages.deleteDestinationDescription", {
154
- name: destination.name,
155
- })}
156
- </AlertDialogDescription>
157
- </AlertDialogHeader>
158
- <AlertDialogFooter>
159
- <AlertDialogCancel>
160
- {t("dispatch.pages.cancel")}
161
- </AlertDialogCancel>
162
- <AlertDialogAction
163
- onClick={() =>
164
- remove.mutate({ id: destination.id })
165
- }
166
- >
150
+ <AlertDialog>
151
+ <AlertDialogTrigger asChild>
152
+ <Button variant="outline" size="sm">
167
153
  {t("dispatch.pages.delete")}
168
- </AlertDialogAction>
169
- </AlertDialogFooter>
170
- </AlertDialogContent>
171
- </AlertDialog>
154
+ </Button>
155
+ </AlertDialogTrigger>
156
+ <AlertDialogContent>
157
+ <AlertDialogHeader>
158
+ <AlertDialogTitle>
159
+ {t("dispatch.pages.deleteDestinationTitle")}
160
+ </AlertDialogTitle>
161
+ <AlertDialogDescription>
162
+ {t(
163
+ "dispatch.pages.deleteDestinationDescription",
164
+ {
165
+ name: destination.name,
166
+ },
167
+ )}
168
+ </AlertDialogDescription>
169
+ </AlertDialogHeader>
170
+ <AlertDialogFooter>
171
+ <AlertDialogCancel>
172
+ {t("dispatch.pages.cancel")}
173
+ </AlertDialogCancel>
174
+ <AlertDialogAction
175
+ onClick={() =>
176
+ remove.mutate({ id: destination.id })
177
+ }
178
+ >
179
+ {t("dispatch.pages.delete")}
180
+ </AlertDialogAction>
181
+ </AlertDialogFooter>
182
+ </AlertDialogContent>
183
+ </AlertDialog>
184
+ </div>
185
+ <QuickSendRow destination={destination} />
172
186
  </div>
173
- <QuickSendRow destination={destination} />
174
- </div>
175
- ))}
176
- {(data?.length || 0) === 0 && (
177
- <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
178
- {t("dispatch.pages.noDestinations")}
179
- </div>
180
- )}
181
- </div>
187
+ ))}
188
+ {(data?.length || 0) === 0 && (
189
+ <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
190
+ {t("dispatch.pages.noDestinations")}
191
+ </div>
192
+ )}
193
+ </div>
194
+ )}
182
195
  </section>
183
196
 
184
197
  <section className="rounded-2xl border bg-card p-5">
@@ -1,6 +1,7 @@
1
1
  import { useActionMutation, useActionQuery } from "@agent-native/core/client";
2
2
  import { toast } from "sonner";
3
3
 
4
+ import { ActionQueryError } from "../../components/action-query-error";
4
5
  import { DispatchShell } from "../../components/dispatch-shell";
5
6
  import { Button } from "../../components/ui/button";
6
7
 
@@ -9,7 +10,8 @@ export function meta() {
9
10
  }
10
11
 
11
12
  export default function IdentitiesRoute() {
12
- const { data } = useActionQuery("list-linked-identities", {});
13
+ const query = useActionQuery("list-linked-identities", {});
14
+ const { data } = query;
13
15
  const createToken = useActionMutation("create-link-token", {
14
16
  onSuccess: () => toast.success("Link token created"),
15
17
  });
@@ -19,74 +21,83 @@ export default function IdentitiesRoute() {
19
21
  title="Identities"
20
22
  description="Link external senders to workspace users."
21
23
  >
22
- <div className="grid gap-4 xl:grid-cols-2">
23
- <section className="rounded-2xl border bg-card p-5">
24
- <div className="flex flex-wrap items-center justify-between gap-2">
25
- <h2 className="text-lg font-semibold text-foreground">
26
- Active links
27
- </h2>
28
- <div className="flex shrink-0 gap-2">
29
- <Button
30
- variant="outline"
31
- onClick={() => createToken.mutate({ platform: "slack" })}
32
- >
33
- New Slack token
34
- </Button>
35
- <Button
36
- onClick={() => createToken.mutate({ platform: "telegram" })}
37
- >
38
- New Telegram token
39
- </Button>
24
+ {query.isError ? (
25
+ <ActionQueryError
26
+ error={query.error}
27
+ onRetry={() => void query.refetch()}
28
+ />
29
+ ) : (
30
+ <div className="grid gap-4 xl:grid-cols-2">
31
+ <section className="rounded-2xl border bg-card p-5">
32
+ <div className="flex flex-wrap items-center justify-between gap-2">
33
+ <h2 className="text-lg font-semibold text-foreground">
34
+ Active links
35
+ </h2>
36
+ <div className="flex shrink-0 gap-2">
37
+ <Button
38
+ variant="outline"
39
+ onClick={() => createToken.mutate({ platform: "slack" })}
40
+ >
41
+ New Slack token
42
+ </Button>
43
+ <Button
44
+ onClick={() => createToken.mutate({ platform: "telegram" })}
45
+ >
46
+ New Telegram token
47
+ </Button>
48
+ </div>
40
49
  </div>
41
- </div>
42
- <div className="mt-4 space-y-3">
43
- {(data?.links || []).map((link: any) => (
44
- <div
45
- key={link.id}
46
- className="rounded-xl border bg-muted/30 px-4 py-3"
47
- >
48
- <div className="text-sm font-medium text-foreground">
49
- {link.externalUserName || link.externalUserId}
50
+ <div className="mt-4 space-y-3">
51
+ {(data?.links || []).map((link: any) => (
52
+ <div
53
+ key={link.id}
54
+ className="rounded-xl border bg-muted/30 px-4 py-3"
55
+ >
56
+ <div className="text-sm font-medium text-foreground">
57
+ {link.externalUserName || link.externalUserId}
58
+ </div>
59
+ <div className="mt-1 text-xs text-muted-foreground">
60
+ {link.platform} → {link.ownerEmail}
61
+ </div>
50
62
  </div>
51
- <div className="mt-1 text-xs text-muted-foreground">
52
- {link.platform} {link.ownerEmail}
63
+ ))}
64
+ {(data?.links?.length || 0) === 0 && (
65
+ <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
66
+ No linked identities yet. Generate a token and ask the user to
67
+ send <code>/link TOKEN</code> from Slack or Telegram.
53
68
  </div>
54
- </div>
55
- ))}
56
- {(data?.links?.length || 0) === 0 && (
57
- <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
58
- No linked identities yet. Generate a token and ask the user to
59
- send <code>/link TOKEN</code> from Slack or Telegram.
60
- </div>
61
- )}
62
- </div>
63
- </section>
69
+ )}
70
+ </div>
71
+ </section>
64
72
 
65
- <section className="rounded-2xl border bg-card p-5">
66
- <h2 className="text-lg font-semibold text-foreground">Link tokens</h2>
67
- <div className="mt-4 space-y-3">
68
- {(data?.tokens || []).map((token: any) => (
69
- <div key={token.id} className="rounded-xl border px-4 py-3">
70
- <div className="text-sm font-medium text-foreground">
71
- /link {token.token}
73
+ <section className="rounded-2xl border bg-card p-5">
74
+ <h2 className="text-lg font-semibold text-foreground">
75
+ Link tokens
76
+ </h2>
77
+ <div className="mt-4 space-y-3">
78
+ {(data?.tokens || []).map((token: any) => (
79
+ <div key={token.id} className="rounded-xl border px-4 py-3">
80
+ <div className="text-sm font-medium text-foreground">
81
+ /link {token.token}
82
+ </div>
83
+ <div className="mt-1 text-xs text-muted-foreground">
84
+ {token.platform} · expires{" "}
85
+ {new Date(token.expiresAt).toLocaleString()}
86
+ {token.claimedAt
87
+ ? ` · claimed by ${token.claimedByExternalUserName || token.claimedByExternalUserId}`
88
+ : " · waiting to be claimed"}
89
+ </div>
72
90
  </div>
73
- <div className="mt-1 text-xs text-muted-foreground">
74
- {token.platform} · expires{" "}
75
- {new Date(token.expiresAt).toLocaleString()}
76
- {token.claimedAt
77
- ? ` · claimed by ${token.claimedByExternalUserName || token.claimedByExternalUserId}`
78
- : " · waiting to be claimed"}
91
+ ))}
92
+ {(data?.tokens?.length || 0) === 0 && (
93
+ <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
94
+ No active link tokens.
79
95
  </div>
80
- </div>
81
- ))}
82
- {(data?.tokens?.length || 0) === 0 && (
83
- <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
84
- No active link tokens.
85
- </div>
86
- )}
87
- </div>
88
- </section>
89
- </div>
96
+ )}
97
+ </div>
98
+ </section>
99
+ </div>
100
+ )}
90
101
  </DispatchShell>
91
102
  );
92
103
  }
@@ -11,6 +11,7 @@ import { useQueryClient } from "@tanstack/react-query";
11
11
  import { useMemo, useState } from "react";
12
12
  import { toast } from "sonner";
13
13
 
14
+ import { ActionQueryError } from "../../components/action-query-error";
14
15
  import { DispatchShell } from "../../components/dispatch-shell";
15
16
  import { Badge } from "../../components/ui/badge";
16
17
  import { Button } from "../../components/ui/button";
@@ -319,14 +320,10 @@ function PerAppDetailRow({ app }: { app: CatalogApp }) {
319
320
  }
320
321
 
321
322
  export default function ConnectionsRoute() {
322
- const { data: catalog, isLoading } = useActionQuery(
323
- "list-integrations-catalog",
324
- {},
325
- );
326
- const { data: accessSettings } = useActionQuery(
327
- "get-vault-access-settings",
328
- {},
329
- );
323
+ const catalogQuery = useActionQuery("list-integrations-catalog", {});
324
+ const accessQuery = useActionQuery("get-vault-access-settings", {});
325
+ const { data: catalog, isLoading } = catalogQuery;
326
+ const { data: accessSettings } = accessQuery;
330
327
  const apps = (catalog as CatalogApp[]) || [];
331
328
  const accessMode =
332
329
  (accessSettings as any)?.mode === "manual" ? "manual" : "all-apps";
@@ -365,13 +362,23 @@ export default function ConnectionsRoute() {
365
362
  title="Connections"
366
363
  description="Connect services once. Apps that need them pick up the key automatically."
367
364
  >
368
- {isLoading && services.length === 0 && (
365
+ {catalogQuery.isError || accessQuery.isError ? (
366
+ <ActionQueryError
367
+ error={catalogQuery.error ?? accessQuery.error}
368
+ onRetry={() => {
369
+ void catalogQuery.refetch();
370
+ void accessQuery.refetch();
371
+ }}
372
+ />
373
+ ) : null}
374
+
375
+ {!catalogQuery.isError && isLoading && services.length === 0 && (
369
376
  <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
370
377
  Discovering apps and credentials…
371
378
  </div>
372
379
  )}
373
380
 
374
- {!isLoading && services.length === 0 && (
381
+ {!catalogQuery.isError && !isLoading && services.length === 0 && (
375
382
  <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
376
383
  No apps with declared integrations are reachable yet.
377
384
  </div>
@@ -7,8 +7,8 @@ import {
7
7
  } from "@tabler/icons-react";
8
8
  import { useEffect, useMemo, useState } from "react";
9
9
 
10
+ import { ActionQueryError } from "../../components/action-query-error";
10
11
  import { DispatchShell } from "../../components/dispatch-shell";
11
- import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert";
12
12
  import { Badge } from "../../components/ui/badge";
13
13
  import { Button } from "../../components/ui/button";
14
14
  import { Input } from "../../components/ui/input";
@@ -430,7 +430,7 @@ export default function ThreadDebugRoute() {
430
430
  ownerEmail?: string;
431
431
  } | null>(null);
432
432
 
433
- const { data: sourcesData, isLoading: sourcesLoading } = useActionQuery<{
433
+ const sourcesQuery = useActionQuery<{
434
434
  access: {
435
435
  viewerEmail: string;
436
436
  orgId: string | null;
@@ -441,6 +441,7 @@ export default function ThreadDebugRoute() {
441
441
  };
442
442
  sources: ThreadDebugSource[];
443
443
  }>("list-agent-thread-sources", {});
444
+ const { data: sourcesData, isLoading: sourcesLoading } = sourcesQuery;
444
445
 
445
446
  const sources: ThreadDebugSource[] = sourcesData?.sources ?? [];
446
447
  const searchParams = useMemo(
@@ -480,6 +481,7 @@ export default function ThreadDebugRoute() {
480
481
  data: detail,
481
482
  isLoading: detailLoading,
482
483
  error: detailError,
484
+ refetch: refetchDetail,
483
485
  } = useActionQuery<ThreadDebugResponse>(
484
486
  "get-agent-thread-debug",
485
487
  detailParams,
@@ -515,6 +517,12 @@ export default function ThreadDebugRoute() {
515
517
  description="Inspect persisted agent chat threads, run events, and AI internals."
516
518
  >
517
519
  <div className="space-y-4">
520
+ {sourcesQuery.isError ? (
521
+ <ActionQueryError
522
+ error={sourcesQuery.error}
523
+ onRetry={() => void sourcesQuery.refetch()}
524
+ />
525
+ ) : null}
518
526
  <section className="rounded-lg border bg-card p-4">
519
527
  <div className="grid gap-3 lg:grid-cols-[220px_1fr_260px_auto]">
520
528
  <Select value={sourceId} onValueChange={setSourceId}>
@@ -600,10 +608,10 @@ export default function ThreadDebugRoute() {
600
608
  </section>
601
609
 
602
610
  {searchError ? (
603
- <Alert variant="destructive">
604
- <AlertTitle>Search failed</AlertTitle>
605
- <AlertDescription>{String(searchError.message)}</AlertDescription>
606
- </Alert>
611
+ <ActionQueryError
612
+ error={searchError}
613
+ onRetry={() => void refetchSearch()}
614
+ />
607
615
  ) : null}
608
616
 
609
617
  <div className="grid gap-4 xl:grid-cols-[380px_1fr]">
@@ -661,12 +669,10 @@ export default function ThreadDebugRoute() {
661
669
 
662
670
  <section className="min-w-0">
663
671
  {detailError ? (
664
- <Alert variant="destructive">
665
- <AlertTitle>Thread lookup failed</AlertTitle>
666
- <AlertDescription>
667
- {String(detailError.message)}
668
- </AlertDescription>
669
- </Alert>
672
+ <ActionQueryError
673
+ error={detailError}
674
+ onRetry={() => void refetchDetail()}
675
+ />
670
676
  ) : null}
671
677
  {detailLoading ? (
672
678
  <div className="rounded-lg border bg-card p-4">
@@ -14,6 +14,7 @@ import {
14
14
  import { useState } from "react";
15
15
  import { toast } from "sonner";
16
16
 
17
+ import { ActionQueryError } from "../../components/action-query-error";
17
18
  import { DispatchShell } from "../../components/dispatch-shell";
18
19
  import {
19
20
  AlertDialog,
@@ -736,17 +737,16 @@ function RequestRow({ request }: { request: any }) {
736
737
  }
737
738
 
738
739
  export default function VaultRoute() {
739
- const { data: secrets, isLoading: secretsLoading } = useActionQuery(
740
- "list-vault-secrets",
741
- {},
742
- );
743
- const { data: grants } = useActionQuery("list-vault-grants", {});
744
- const { data: requests } = useActionQuery("list-vault-requests", {});
745
- const { data: audit } = useActionQuery("list-vault-audit", { limit: 20 });
746
- const { data: accessSettings } = useActionQuery(
747
- "get-vault-access-settings",
748
- {},
749
- );
740
+ const secretsQuery = useActionQuery("list-vault-secrets", {});
741
+ const grantsQuery = useActionQuery("list-vault-grants", {});
742
+ const requestsQuery = useActionQuery("list-vault-requests", {});
743
+ const auditQuery = useActionQuery("list-vault-audit", { limit: 20 });
744
+ const accessQuery = useActionQuery("get-vault-access-settings", {});
745
+ const { data: secrets, isLoading: secretsLoading } = secretsQuery;
746
+ const { data: grants } = grantsQuery;
747
+ const { data: requests } = requestsQuery;
748
+ const { data: audit } = auditQuery;
749
+ const { data: accessSettings } = accessQuery;
750
750
  const accessMode: VaultAccessMode =
751
751
  (accessSettings as any)?.mode === "manual" ? "manual" : "all-apps";
752
752
 
@@ -788,6 +788,20 @@ export default function VaultRoute() {
788
788
  </TabsList>
789
789
 
790
790
  <TabsContent value="secrets" className="mt-4 space-y-3">
791
+ {secretsQuery.isError ||
792
+ grantsQuery.isError ||
793
+ accessQuery.isError ? (
794
+ <ActionQueryError
795
+ error={
796
+ secretsQuery.error ?? grantsQuery.error ?? accessQuery.error
797
+ }
798
+ onRetry={() => {
799
+ void secretsQuery.refetch();
800
+ void grantsQuery.refetch();
801
+ void accessQuery.refetch();
802
+ }}
803
+ />
804
+ ) : null}
791
805
  <VaultAccessSettingsCard mode={accessMode} />
792
806
 
793
807
  <div className="flex items-center justify-between">
@@ -804,7 +818,9 @@ export default function VaultRoute() {
804
818
  <AddSecretDialog />
805
819
  </div>
806
820
 
807
- {secretsLoading && (secrets ?? []).length === 0
821
+ {!secretsQuery.isError &&
822
+ secretsLoading &&
823
+ (secrets ?? []).length === 0
808
824
  ? Array.from({ length: 3 }).map((_, index) => (
809
825
  <div
810
826
  key={index}
@@ -823,25 +839,36 @@ export default function VaultRoute() {
823
839
  />
824
840
  ))}
825
841
 
826
- {!secretsLoading && (secrets?.length || 0) === 0 && (
827
- <div className="rounded-2xl border border-dashed px-6 py-12 text-center">
828
- <IconKey size={32} className="mx-auto text-muted-foreground/50" />
829
- <h3 className="mt-3 text-sm font-medium text-foreground">
830
- No secrets yet
831
- </h3>
832
- <p className="mt-1 text-sm text-muted-foreground">
833
- Add your first secret to start sharing credentials across
834
- workspace apps.
835
- </p>
836
- </div>
837
- )}
842
+ {!secretsQuery.isError &&
843
+ !secretsLoading &&
844
+ (secrets?.length || 0) === 0 && (
845
+ <div className="rounded-2xl border border-dashed px-6 py-12 text-center">
846
+ <IconKey
847
+ size={32}
848
+ className="mx-auto text-muted-foreground/50"
849
+ />
850
+ <h3 className="mt-3 text-sm font-medium text-foreground">
851
+ No secrets yet
852
+ </h3>
853
+ <p className="mt-1 text-sm text-muted-foreground">
854
+ Add your first secret to start sharing credentials across
855
+ workspace apps.
856
+ </p>
857
+ </div>
858
+ )}
838
859
  </TabsContent>
839
860
 
840
861
  <TabsContent value="requests" className="mt-4 space-y-3">
862
+ {requestsQuery.isError ? (
863
+ <ActionQueryError
864
+ error={requestsQuery.error}
865
+ onRetry={() => void requestsQuery.refetch()}
866
+ />
867
+ ) : null}
841
868
  {(requests || []).map((request: any) => (
842
869
  <RequestRow key={request.id} request={request} />
843
870
  ))}
844
- {(requests?.length || 0) === 0 && (
871
+ {!requestsQuery.isError && (requests?.length || 0) === 0 && (
845
872
  <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
846
873
  No secret requests yet.
847
874
  </div>
@@ -849,6 +876,12 @@ export default function VaultRoute() {
849
876
  </TabsContent>
850
877
 
851
878
  <TabsContent value="audit" className="mt-4 space-y-2">
879
+ {auditQuery.isError ? (
880
+ <ActionQueryError
881
+ error={auditQuery.error}
882
+ onRetry={() => void auditQuery.refetch()}
883
+ />
884
+ ) : null}
852
885
  {(audit || []).map((event: any) => (
853
886
  <div
854
887
  key={event.id}
@@ -862,7 +895,7 @@ export default function VaultRoute() {
862
895
  </div>
863
896
  </div>
864
897
  ))}
865
- {(audit?.length || 0) === 0 && (
898
+ {!auditQuery.isError && (audit?.length || 0) === 0 && (
866
899
  <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
867
900
  No vault activity yet.
868
901
  </div>