@agent-native/core 0.107.2 → 0.108.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 (228) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/actions.mdx +48 -0
  4. package/corpus/core/docs/content/locales/ar-SA/actions.mdx +38 -0
  5. package/corpus/core/docs/content/locales/de-DE/actions.mdx +38 -0
  6. package/corpus/core/docs/content/locales/es-ES/actions.mdx +38 -0
  7. package/corpus/core/docs/content/locales/fr-FR/actions.mdx +38 -0
  8. package/corpus/core/docs/content/locales/hi-IN/actions.mdx +38 -0
  9. package/corpus/core/docs/content/locales/ja-JP/actions.mdx +38 -0
  10. package/corpus/core/docs/content/locales/ko-KR/actions.mdx +38 -0
  11. package/corpus/core/docs/content/locales/pt-BR/actions.mdx +38 -0
  12. package/corpus/core/docs/content/locales/zh-CN/actions.mdx +38 -0
  13. package/corpus/core/docs/content/locales/zh-TW/actions.mdx +38 -0
  14. package/corpus/core/package.json +5 -1
  15. package/corpus/core/src/a2a-claims.ts +52 -0
  16. package/corpus/core/src/action.ts +6 -2
  17. package/corpus/core/src/audit/record.ts +6 -0
  18. package/corpus/core/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
  19. package/corpus/core/src/client/feature-flags/helpers.ts +77 -0
  20. package/corpus/core/src/client/feature-flags/index.ts +13 -0
  21. package/corpus/core/src/client/feature-flags/types.ts +29 -0
  22. package/corpus/core/src/client/feature-flags/use-feature-flag.ts +26 -0
  23. package/corpus/core/src/client/index.ts +11 -0
  24. package/corpus/core/src/extensions/routes.ts +1 -1
  25. package/corpus/core/src/feature-flags/a2a-action-route.ts +56 -0
  26. package/corpus/core/src/feature-flags/actions/get-feature-flags.ts +24 -0
  27. package/corpus/core/src/feature-flags/actions/list-feature-flags.ts +69 -0
  28. package/corpus/core/src/feature-flags/actions/set-feature-flag.ts +89 -0
  29. package/corpus/core/src/feature-flags/index.ts +21 -0
  30. package/corpus/core/src/feature-flags/permissions.ts +53 -0
  31. package/corpus/core/src/feature-flags/plugin.ts +44 -0
  32. package/corpus/core/src/feature-flags/registry.ts +96 -0
  33. package/corpus/core/src/feature-flags/store.ts +176 -0
  34. package/corpus/core/src/index.ts +15 -0
  35. package/corpus/core/src/localization/default-messages.ts +47 -0
  36. package/corpus/core/src/server/action-discovery.ts +12 -0
  37. package/corpus/core/src/server/action-routes.ts +47 -5
  38. package/corpus/core/src/server/index.ts +1 -0
  39. package/corpus/core/src/settings/org-settings.ts +13 -0
  40. package/corpus/core/src/settings/store.ts +57 -0
  41. package/corpus/core/src/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
  42. package/corpus/core/src/templates/default/AGENTS.md +1 -0
  43. package/corpus/core/src/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
  44. package/corpus/core/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
  45. package/corpus/core/src/templates/workspace-core/AGENTS.md +1 -1
  46. package/corpus/core/src/templates/workspace-root/AGENTS.md +1 -1
  47. package/corpus/core/src/vite/action-types-plugin.ts +12 -0
  48. package/corpus/templates/analytics/.agents/skills/feature-flags/SKILL.md +169 -0
  49. package/corpus/templates/analytics/AGENTS.md +15 -3
  50. package/corpus/templates/analytics/actions/list-workspace-feature-flags.ts +15 -0
  51. package/corpus/templates/analytics/actions/navigate.ts +2 -2
  52. package/corpus/templates/analytics/actions/set-workspace-feature-flag.ts +45 -0
  53. package/corpus/templates/analytics/actions/view-screen.ts +13 -0
  54. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +243 -0
  55. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +11 -3
  56. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +3 -1
  57. package/corpus/templates/analytics/app/i18n/zh-TW.ts +17 -0
  58. package/corpus/templates/analytics/app/i18n-data.ts +251 -0
  59. package/corpus/templates/analytics/app/pages/Agents.tsx +22 -2
  60. package/corpus/templates/analytics/changelog/2026-07-17-manage-feature-flag-rollouts-across-your-organization-s-apps.md +6 -0
  61. package/corpus/templates/analytics/server/lib/db-admin-connections.ts +13 -5
  62. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +347 -0
  63. package/corpus/templates/assets/.agents/skills/feature-flags/SKILL.md +169 -0
  64. package/corpus/templates/brain/.agents/skills/feature-flags/SKILL.md +169 -0
  65. package/corpus/templates/calendar/.agents/skills/feature-flags/SKILL.md +169 -0
  66. package/corpus/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
  67. package/corpus/templates/clips/.agents/skills/feature-flags/SKILL.md +169 -0
  68. package/corpus/templates/clips/server/plugins/feature-flags.ts +11 -0
  69. package/corpus/templates/clips/shared/feature-flags.ts +30 -37
  70. package/corpus/templates/content/.agents/skills/feature-flags/SKILL.md +169 -0
  71. package/corpus/templates/design/.agents/skills/feature-flags/SKILL.md +169 -0
  72. package/corpus/templates/design/AGENTS.md +5 -3
  73. package/corpus/templates/design/actions/add-fusion-screens.ts +4 -6
  74. package/corpus/templates/design/actions/apply-fusion-edits.ts +4 -6
  75. package/corpus/templates/design/actions/create-fusion-app.ts +5 -4
  76. package/corpus/templates/design/actions/deploy-fusion-app.ts +4 -3
  77. package/corpus/templates/design/actions/get-fusion-deploy-status.ts +4 -6
  78. package/corpus/templates/design/actions/list-fusion-edits.ts +4 -3
  79. package/corpus/templates/design/actions/push-fusion-app.ts +4 -6
  80. package/corpus/templates/design/actions/queue-fusion-edit.ts +4 -3
  81. package/corpus/templates/design/actions/send-fusion-message.ts +4 -6
  82. package/corpus/templates/design/actions/sync-fusion-app.ts +4 -3
  83. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  84. package/corpus/templates/design/app/pages/Index.tsx +8 -6
  85. package/corpus/templates/design/server/plugins/feature-flags.ts +5 -0
  86. package/corpus/templates/design/shared/full-app.ts +10 -7
  87. package/corpus/templates/dispatch/.agents/skills/feature-flags/SKILL.md +169 -0
  88. package/corpus/templates/forms/.agents/skills/feature-flags/SKILL.md +169 -0
  89. package/corpus/templates/macros/.agents/skills/feature-flags/SKILL.md +169 -0
  90. package/corpus/templates/mail/.agents/skills/feature-flags/SKILL.md +169 -0
  91. package/corpus/templates/plan/.agents/skills/feature-flags/SKILL.md +169 -0
  92. package/corpus/templates/slides/.agents/skills/feature-flags/SKILL.md +169 -0
  93. package/corpus/templates/tasks/.agents/skills/feature-flags/SKILL.md +169 -0
  94. package/dist/a2a-claims.d.ts +10 -0
  95. package/dist/a2a-claims.d.ts.map +1 -0
  96. package/dist/a2a-claims.js +41 -0
  97. package/dist/a2a-claims.js.map +1 -0
  98. package/dist/action.d.ts +6 -2
  99. package/dist/action.d.ts.map +1 -1
  100. package/dist/action.js.map +1 -1
  101. package/dist/audit/record.d.ts +3 -0
  102. package/dist/audit/record.d.ts.map +1 -1
  103. package/dist/audit/record.js +3 -0
  104. package/dist/audit/record.js.map +1 -1
  105. package/dist/client/feature-flags/FeatureFlagsPanel.d.ts +10 -0
  106. package/dist/client/feature-flags/FeatureFlagsPanel.d.ts.map +1 -0
  107. package/dist/client/feature-flags/FeatureFlagsPanel.js +143 -0
  108. package/dist/client/feature-flags/FeatureFlagsPanel.js.map +1 -0
  109. package/dist/client/feature-flags/helpers.d.ts +16 -0
  110. package/dist/client/feature-flags/helpers.d.ts.map +1 -0
  111. package/dist/client/feature-flags/helpers.js +50 -0
  112. package/dist/client/feature-flags/helpers.js.map +1 -0
  113. package/dist/client/feature-flags/index.d.ts +5 -0
  114. package/dist/client/feature-flags/index.d.ts.map +1 -0
  115. package/dist/client/feature-flags/index.js +4 -0
  116. package/dist/client/feature-flags/index.js.map +1 -0
  117. package/dist/client/feature-flags/types.d.ts +27 -0
  118. package/dist/client/feature-flags/types.d.ts.map +1 -0
  119. package/dist/client/feature-flags/types.js +2 -0
  120. package/dist/client/feature-flags/types.js.map +1 -0
  121. package/dist/client/feature-flags/use-feature-flag.d.ts +8 -0
  122. package/dist/client/feature-flags/use-feature-flag.d.ts.map +1 -0
  123. package/dist/client/feature-flags/use-feature-flag.js +15 -0
  124. package/dist/client/feature-flags/use-feature-flag.js.map +1 -0
  125. package/dist/client/index.d.ts +1 -0
  126. package/dist/client/index.d.ts.map +1 -1
  127. package/dist/client/index.js +1 -0
  128. package/dist/client/index.js.map +1 -1
  129. package/dist/collab/routes.d.ts +1 -1
  130. package/dist/extensions/routes.d.ts.map +1 -1
  131. package/dist/extensions/routes.js +1 -1
  132. package/dist/extensions/routes.js.map +1 -1
  133. package/dist/feature-flags/a2a-action-route.d.ts +14 -0
  134. package/dist/feature-flags/a2a-action-route.d.ts.map +1 -0
  135. package/dist/feature-flags/a2a-action-route.js +50 -0
  136. package/dist/feature-flags/a2a-action-route.js.map +1 -0
  137. package/dist/feature-flags/actions/get-feature-flags.d.ts +3 -0
  138. package/dist/feature-flags/actions/get-feature-flags.d.ts.map +1 -0
  139. package/dist/feature-flags/actions/get-feature-flags.js +18 -0
  140. package/dist/feature-flags/actions/get-feature-flags.js.map +1 -0
  141. package/dist/feature-flags/actions/list-feature-flags.d.ts +28 -0
  142. package/dist/feature-flags/actions/list-feature-flags.d.ts.map +1 -0
  143. package/dist/feature-flags/actions/list-feature-flags.js +60 -0
  144. package/dist/feature-flags/actions/list-feature-flags.js.map +1 -0
  145. package/dist/feature-flags/actions/set-feature-flag.d.ts +26 -0
  146. package/dist/feature-flags/actions/set-feature-flag.d.ts.map +1 -0
  147. package/dist/feature-flags/actions/set-feature-flag.js +78 -0
  148. package/dist/feature-flags/actions/set-feature-flag.js.map +1 -0
  149. package/dist/feature-flags/index.d.ts +5 -0
  150. package/dist/feature-flags/index.d.ts.map +1 -0
  151. package/dist/feature-flags/index.js +5 -0
  152. package/dist/feature-flags/index.js.map +1 -0
  153. package/dist/feature-flags/permissions.d.ts +13 -0
  154. package/dist/feature-flags/permissions.d.ts.map +1 -0
  155. package/dist/feature-flags/permissions.js +37 -0
  156. package/dist/feature-flags/permissions.js.map +1 -0
  157. package/dist/feature-flags/plugin.d.ts +17 -0
  158. package/dist/feature-flags/plugin.d.ts.map +1 -0
  159. package/dist/feature-flags/plugin.js +29 -0
  160. package/dist/feature-flags/plugin.js.map +1 -0
  161. package/dist/feature-flags/registry.d.ts +23 -0
  162. package/dist/feature-flags/registry.d.ts.map +1 -0
  163. package/dist/feature-flags/registry.js +59 -0
  164. package/dist/feature-flags/registry.js.map +1 -0
  165. package/dist/feature-flags/store.d.ts +31 -0
  166. package/dist/feature-flags/store.d.ts.map +1 -0
  167. package/dist/feature-flags/store.js +124 -0
  168. package/dist/feature-flags/store.js.map +1 -0
  169. package/dist/index.d.ts +1 -0
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +1 -0
  172. package/dist/index.js.map +1 -1
  173. package/dist/localization/default-messages.d.ts +42 -0
  174. package/dist/localization/default-messages.d.ts.map +1 -1
  175. package/dist/localization/default-messages.js +42 -0
  176. package/dist/localization/default-messages.js.map +1 -1
  177. package/dist/observability/routes.d.ts +1 -1
  178. package/dist/progress/routes.d.ts +1 -1
  179. package/dist/resources/handlers.d.ts +1 -1
  180. package/dist/server/action-discovery.d.ts.map +1 -1
  181. package/dist/server/action-discovery.js +12 -0
  182. package/dist/server/action-discovery.js.map +1 -1
  183. package/dist/server/action-routes.d.ts +3 -0
  184. package/dist/server/action-routes.d.ts.map +1 -1
  185. package/dist/server/action-routes.js +45 -5
  186. package/dist/server/action-routes.js.map +1 -1
  187. package/dist/server/index.d.ts +1 -0
  188. package/dist/server/index.d.ts.map +1 -1
  189. package/dist/server/index.js +1 -0
  190. package/dist/server/index.js.map +1 -1
  191. package/dist/settings/org-settings.d.ts +2 -0
  192. package/dist/settings/org-settings.d.ts.map +1 -1
  193. package/dist/settings/org-settings.js +5 -1
  194. package/dist/settings/org-settings.js.map +1 -1
  195. package/dist/settings/store.d.ts +8 -0
  196. package/dist/settings/store.d.ts.map +1 -1
  197. package/dist/settings/store.js +48 -0
  198. package/dist/settings/store.js.map +1 -1
  199. package/dist/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
  200. package/dist/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
  201. package/dist/templates/default/AGENTS.md +1 -0
  202. package/dist/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
  203. package/dist/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
  204. package/dist/templates/workspace-core/AGENTS.md +1 -1
  205. package/dist/templates/workspace-root/AGENTS.md +1 -1
  206. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  207. package/dist/vite/action-types-plugin.js +12 -0
  208. package/dist/vite/action-types-plugin.js.map +1 -1
  209. package/docs/content/actions.mdx +48 -0
  210. package/docs/content/locales/ar-SA/actions.mdx +38 -0
  211. package/docs/content/locales/de-DE/actions.mdx +38 -0
  212. package/docs/content/locales/es-ES/actions.mdx +38 -0
  213. package/docs/content/locales/fr-FR/actions.mdx +38 -0
  214. package/docs/content/locales/hi-IN/actions.mdx +38 -0
  215. package/docs/content/locales/ja-JP/actions.mdx +38 -0
  216. package/docs/content/locales/ko-KR/actions.mdx +38 -0
  217. package/docs/content/locales/pt-BR/actions.mdx +38 -0
  218. package/docs/content/locales/zh-CN/actions.mdx +38 -0
  219. package/docs/content/locales/zh-TW/actions.mdx +38 -0
  220. package/package.json +5 -1
  221. package/src/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
  222. package/src/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
  223. package/src/templates/default/AGENTS.md +1 -0
  224. package/src/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
  225. package/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
  226. package/src/templates/workspace-core/AGENTS.md +1 -1
  227. package/src/templates/workspace-root/AGENTS.md +1 -1
  228. package/corpus/templates/clips/actions/get-feature-flags.ts +0 -30
@@ -0,0 +1,243 @@
1
+ import {
2
+ FeatureFlagsEditor,
3
+ useActionMutation,
4
+ useActionQuery,
5
+ useT,
6
+ type FeatureFlagMetadata,
7
+ type SetFeatureFlagInput,
8
+ } from "@agent-native/core/client";
9
+ import { IconRefresh } from "@tabler/icons-react";
10
+ import { useQueryClient } from "@tanstack/react-query";
11
+
12
+ import { Badge } from "@/components/ui/badge";
13
+ import { Button } from "@/components/ui/button";
14
+
15
+ export interface FeatureFlag {
16
+ key: string;
17
+ displayName?: string | null;
18
+ description?: string | null;
19
+ defaultValue?: boolean;
20
+ rules?: FeatureFlagMetadata["rules"];
21
+ }
22
+
23
+ export interface FlagApp {
24
+ appId?: string;
25
+ appName?: string;
26
+ appOrigin?: string;
27
+ state?: string;
28
+ id?: string;
29
+ name?: string;
30
+ url?: string;
31
+ status?: string;
32
+ reason?: string;
33
+ flags?: FeatureFlag[];
34
+ }
35
+
36
+ export interface FlagDirectory {
37
+ directoryStatus?: string;
38
+ apps?: FlagApp[];
39
+ }
40
+
41
+ function asDirectory(value: unknown): FlagDirectory {
42
+ return value && typeof value === "object" ? (value as FlagDirectory) : {};
43
+ }
44
+
45
+ function isReady(app: FlagApp) {
46
+ return app.state === "ready" || app.status === "ready";
47
+ }
48
+
49
+ function statusLabel(app: FlagApp) {
50
+ return app.state || app.status || "unknown-legacy";
51
+ }
52
+
53
+ function appId(app: FlagApp) {
54
+ return app.appId || app.id || "";
55
+ }
56
+
57
+ function appName(app: FlagApp) {
58
+ return app.appName || app.name || appId(app);
59
+ }
60
+
61
+ export function qualifyFleetMutation(
62
+ appId: string,
63
+ input: SetFeatureFlagInput,
64
+ ) {
65
+ return { appId, ...input };
66
+ }
67
+
68
+ export function FeatureFlagsFleetPanel() {
69
+ const t = useT();
70
+ const client = useQueryClient();
71
+ const flags = useActionQuery<unknown>(
72
+ "list-workspace-feature-flags",
73
+ undefined,
74
+ { retry: false },
75
+ );
76
+ const mutation = useActionMutation<
77
+ unknown,
78
+ { appId: string } & SetFeatureFlagInput
79
+ >("set-workspace-feature-flag", {
80
+ onMutate: async (input) => {
81
+ await client.cancelQueries({
82
+ queryKey: ["action", "list-workspace-feature-flags"],
83
+ });
84
+ const key = ["action", "list-workspace-feature-flags", undefined];
85
+ const previous = client.getQueryData(key);
86
+ client.setQueryData(key, (old: unknown) => {
87
+ const data = asDirectory(old);
88
+ return {
89
+ ...data,
90
+ apps: data.apps?.map((app) =>
91
+ appId(app) !== input.appId
92
+ ? app
93
+ : {
94
+ ...app,
95
+ flags: app.flags?.map((flag) =>
96
+ flag.key !== input.key
97
+ ? flag
98
+ : {
99
+ ...flag,
100
+ rules:
101
+ input.operation === "replace-rules" && input.rules
102
+ ? input.rules
103
+ : input.operation === "off"
104
+ ? {
105
+ ...flag.rules!,
106
+ mode: "off",
107
+ emails: [],
108
+ orgIds: [],
109
+ percentage: 0,
110
+ }
111
+ : flag.rules,
112
+ },
113
+ ),
114
+ },
115
+ ),
116
+ };
117
+ });
118
+ return { previous, key };
119
+ },
120
+ onError: (_error, _input, context: any) => {
121
+ if (context) client.setQueryData(context.key, context.previous);
122
+ },
123
+ onSettled: () =>
124
+ void client.invalidateQueries({
125
+ queryKey: ["action", "list-workspace-feature-flags"],
126
+ }),
127
+ });
128
+ const directory = asDirectory(flags.data);
129
+ const apps = directory.apps ?? [];
130
+ if (flags.isLoading) return <PanelLoading />;
131
+ if (flags.error || directory.directoryStatus === "unavailable")
132
+ return (
133
+ <StatusState
134
+ title={t("agents.flagsUnavailable")}
135
+ detail={flags.error?.message || t("agents.flagsUnreachable")}
136
+ onRetry={() => void flags.refetch()}
137
+ />
138
+ );
139
+ if (apps.length === 0)
140
+ return (
141
+ <StatusState
142
+ title={t("agents.flagsEmpty")}
143
+ detail={t("agents.flagsEmptyDetail")}
144
+ onRetry={() => void flags.refetch()}
145
+ />
146
+ );
147
+ return (
148
+ <div className="flex flex-col gap-4">
149
+ <div className="flex items-start justify-between gap-3">
150
+ <div>
151
+ <h2 className="text-sm font-semibold">{t("agents.featureFlags")}</h2>
152
+ <p className="mt-1 text-sm text-muted-foreground">
153
+ {t("agents.featureFlagsDescription")}
154
+ </p>
155
+ </div>
156
+ <Button
157
+ variant="outline"
158
+ size="sm"
159
+ onClick={() => void flags.refetch()}
160
+ >
161
+ <IconRefresh className="me-2 size-4" />
162
+ {t("agents.reloadFlags")}
163
+ </Button>
164
+ </div>
165
+ {apps.map((app) => (
166
+ <section key={appId(app)} className="overflow-hidden rounded-lg border">
167
+ <div className="flex items-center justify-between gap-3 border-b bg-muted/30 px-4 py-3">
168
+ <div>
169
+ <h3 className="text-sm font-medium">{appName(app)}</h3>
170
+ {app.reason ? (
171
+ <p className="text-xs text-muted-foreground">{app.reason}</p>
172
+ ) : null}
173
+ </div>
174
+ {!isReady(app) ? (
175
+ <Badge variant="outline">{statusLabel(app)}</Badge>
176
+ ) : null}
177
+ </div>
178
+ {isReady(app) ? (
179
+ <div className="p-4">
180
+ <FeatureFlagsEditor
181
+ flags={(app.flags ?? []).filter(
182
+ (flag): flag is FeatureFlagMetadata =>
183
+ !!flag.rules && typeof flag.defaultValue === "boolean",
184
+ )}
185
+ isPending={mutation.isPending}
186
+ error={
187
+ mutation.variables?.appId === appId(app)
188
+ ? mutation.error
189
+ : null
190
+ }
191
+ errorFlagKey={
192
+ mutation.variables?.appId === appId(app)
193
+ ? mutation.variables.key
194
+ : null
195
+ }
196
+ showHeader={false}
197
+ onMutate={(input) =>
198
+ mutation.mutate(qualifyFleetMutation(appId(app), input))
199
+ }
200
+ />
201
+ </div>
202
+ ) : (
203
+ <div className="p-4 text-sm text-muted-foreground">
204
+ {statusLabel(app) === "no-definitions"
205
+ ? t("agents.noFlagDefinitions")
206
+ : t("agents.flagsNotReady", { status: statusLabel(app) })}
207
+ </div>
208
+ )}
209
+ </section>
210
+ ))}
211
+ </div>
212
+ );
213
+ }
214
+
215
+ function PanelLoading() {
216
+ const t = useT();
217
+ return (
218
+ <div className="flex min-h-52 items-center justify-center rounded-lg border text-sm text-muted-foreground">
219
+ {t("agents.loading")}
220
+ </div>
221
+ );
222
+ }
223
+
224
+ function StatusState({
225
+ title,
226
+ detail,
227
+ onRetry,
228
+ }: {
229
+ title: string;
230
+ detail: string;
231
+ onRetry: () => void;
232
+ }) {
233
+ const t = useT();
234
+ return (
235
+ <div className="rounded-lg border p-6">
236
+ <h2 className="text-sm font-semibold">{title}</h2>
237
+ <p className="mt-1 text-sm text-muted-foreground">{detail}</p>
238
+ <Button className="mt-4" size="sm" variant="outline" onClick={onRetry}>
239
+ {t("sidebar.retry")}
240
+ </Button>
241
+ </div>
242
+ );
243
+ }
@@ -95,16 +95,20 @@ const defaultTools = [
95
95
  "agent monitoring",
96
96
  "observability",
97
97
  "evals",
98
- "experiments",
99
98
  "feedback",
100
99
  "database",
101
100
  "db admin",
102
101
  "dashboard usage",
103
102
  "dashboard audit",
104
- "ab testing",
105
103
  "llm",
106
104
  ],
107
105
  },
106
+ {
107
+ id: "feature-flags",
108
+ nameKey: "agents.featureFlags",
109
+ href: "/agents?view=flags",
110
+ keywords: ["flags", "rollout", "release", "targeting"],
111
+ },
108
112
  {
109
113
  id: "explorer",
110
114
  nameKey: "commandPalette.toolExplorer",
@@ -565,7 +569,11 @@ export function CommandPalette() {
565
569
 
566
570
  <CommandGroup key="tools" heading={t("commandPalette.groupTools")}>
567
571
  {defaultTools
568
- .filter((tool) => tool.id !== "agents" || canManageOrg)
572
+ .filter((tool) =>
573
+ ["agents", "feature-flags"].includes(tool.id)
574
+ ? canManageOrg
575
+ : true,
576
+ )
569
577
  .map((tool) => (
570
578
  <CommandItem
571
579
  key={`tool-${tool.id}`}
@@ -129,7 +129,9 @@ export function useNavigationState() {
129
129
  if (cmd.view === "sessions") return "/sessions";
130
130
  if (
131
131
  cmd.view === "agents" &&
132
- (cmd.agentsView === "database" || cmd.agentsView === "dashboards")
132
+ (cmd.agentsView === "database" ||
133
+ cmd.agentsView === "dashboards" ||
134
+ cmd.agentsView === "flags")
133
135
  ) {
134
136
  const params = new URLSearchParams({ view: cmd.agentsView });
135
137
  if (cmd.agentsView === "database" && cmd.dbAdminConnectionId) {
@@ -698,6 +698,23 @@ const messages = {
698
698
  deleteConnectionTitle: "刪除資料庫連線?",
699
699
  deleteConnectionDescription:
700
700
  "這會從 Analytics 移除 {{name}} 並刪除儲存的連線密鑰。目標應用程式資料庫不會被修改。",
701
+ featureFlags: "功能旗標",
702
+ featureFlagsDescription: "檢視已註冊應用程式旗標,並進行可回復的發布調整。",
703
+ reloadFlags: "重新載入功能旗標",
704
+ loading: "載入中",
705
+ flagsUnavailable: "功能旗標無法使用",
706
+ flagsUnreachable: "無法連線到工作區目錄。連線可用後請重試。",
707
+ flagsEmpty: "沒有應用程式回報功能旗標",
708
+ flagsEmptyDetail: "連線的應用程式準備就緒後,已註冊旗標會顯示在這裡。",
709
+ flagsNotReady: "此應用程式為 {{status}},不會假定旗標已關閉。",
710
+ noFlagDefinitions: "此應用程式沒有已註冊的旗標定義。",
711
+ noFlagDescription: "未提供說明。",
712
+ selected: "已選取",
713
+ openFlag: "開啟功能旗標",
714
+ confirm: "確認",
715
+ name: "名稱",
716
+ appId: "應用程式 ID",
717
+ flagKey: "功能旗標",
701
718
  },
702
719
  providerCorpusNotifier: {
703
720
  completed: "提供者語料庫作業已完成",
@@ -459,6 +459,27 @@ const enUS = {
459
459
  deleteConnectionTitle: "Delete database connection?",
460
460
  deleteConnectionDescription:
461
461
  "This removes {{name}} from Analytics and deletes its stored connection secrets. The target app database is not modified.",
462
+ featureFlags: "Feature flags",
463
+ featureFlagsDescription:
464
+ "Review registered app flags and make deliberate, reversible rollout changes.",
465
+ reloadFlags: "Reload flags",
466
+ loading: "Loading",
467
+ flagsUnavailable: "Feature flags are unavailable",
468
+ flagsUnreachable:
469
+ "The workspace directory could not be reached. Retry when its connection is available.",
470
+ flagsEmpty: "No workspace apps reported flags",
471
+ flagsEmptyDetail:
472
+ "Registered flags will appear here when connected apps are ready.",
473
+ flagsNotReady:
474
+ "This app is {{status}}. Its flag state is not assumed to be off.",
475
+ noFlagDefinitions: "This ready app has no registered flag definitions.",
476
+ noFlagDescription: "No description supplied.",
477
+ selected: "Selected",
478
+ openFlag: "Open feature flag",
479
+ confirm: "Confirm",
480
+ name: "Name",
481
+ appId: "App ID",
482
+ flagKey: "Feature flag",
462
483
  },
463
484
  providerCorpusNotifier: {
464
485
  completed: "Provider corpus job completed",
@@ -10801,6 +10822,224 @@ for (const [locale, overrides] of Object.entries(
10801
10822
  }
10802
10823
  }
10803
10824
 
10825
+ const translatedFeatureFlagAdminTranslations = {
10826
+ "zh-CN": {
10827
+ agents: {
10828
+ featureFlags: "功能开关",
10829
+ featureFlagsDescription: "查看已注册应用开关,并进行可逆的发布调整。",
10830
+ reloadFlags: "重新加载开关",
10831
+ flagsUnavailable: "功能开关不可用",
10832
+ flagsUnreachable: "无法连接工作区目录。连接恢复后重试。",
10833
+ flagsEmpty: "没有应用报告功能开关",
10834
+ flagsEmptyDetail: "连接的应用准备就绪后,已注册开关会显示在这里。",
10835
+ flagsNotReady: "此应用状态为 {{status}},不会将其开关状态假定为关闭。",
10836
+ noFlagDefinitions: "此应用没有注册功能开关定义。",
10837
+ noFlagDescription: "未提供说明。",
10838
+ selected: "已选择",
10839
+ openFlag: "打开功能开关",
10840
+ confirm: "确认",
10841
+ name: "名称",
10842
+ appId: "应用 ID",
10843
+ flagKey: "功能开关",
10844
+ },
10845
+ },
10846
+ "es-ES": {
10847
+ agents: {
10848
+ featureFlags: "Indicadores de funciones",
10849
+ featureFlagsDescription:
10850
+ "Revisa los indicadores de las aplicaciones registradas y aplica cambios de lanzamiento reversibles.",
10851
+ reloadFlags: "Volver a cargar indicadores",
10852
+ flagsUnavailable: "Los indicadores de funciones no están disponibles",
10853
+ flagsUnreachable:
10854
+ "No se pudo acceder al directorio del espacio de trabajo. Inténtalo de nuevo cuando esté disponible.",
10855
+ flagsEmpty: "Ninguna aplicación informó indicadores",
10856
+ flagsEmptyDetail:
10857
+ "Los indicadores registrados aparecerán aquí cuando las aplicaciones conectadas estén listas.",
10858
+ flagsNotReady:
10859
+ "Esta aplicación está {{status}}; su estado no se supone desactivado.",
10860
+ noFlagDefinitions:
10861
+ "Esta aplicación lista no tiene definiciones de indicadores.",
10862
+ noFlagDescription: "No se proporcionó descripción.",
10863
+ selected: "Seleccionado",
10864
+ openFlag: "Abrir indicador",
10865
+ confirm: "Confirmar",
10866
+ name: "Nombre",
10867
+ appId: "ID de aplicación",
10868
+ flagKey: "Indicador de función",
10869
+ },
10870
+ },
10871
+ "fr-FR": {
10872
+ agents: {
10873
+ featureFlags: "Indicateurs de fonctionnalités",
10874
+ featureFlagsDescription:
10875
+ "Examinez les indicateurs d'applications enregistrées et appliquez des changements de déploiement réversibles.",
10876
+ reloadFlags: "Recharger les indicateurs",
10877
+ flagsUnavailable: "Les indicateurs de fonctionnalités sont indisponibles",
10878
+ flagsUnreachable:
10879
+ "Le répertoire de l'espace de travail est inaccessible. Réessayez lorsqu'il sera disponible.",
10880
+ flagsEmpty: "Aucune application n'a signalé d'indicateur",
10881
+ flagsEmptyDetail:
10882
+ "Les indicateurs enregistrés apparaîtront ici lorsque les applications connectées seront prêtes.",
10883
+ flagsNotReady:
10884
+ "Cette application est {{status}} ; son état n'est pas supposé désactivé.",
10885
+ noFlagDefinitions:
10886
+ "Cette application prête n'a aucune définition d'indicateur.",
10887
+ noFlagDescription: "Aucune description fournie.",
10888
+ selected: "Sélectionné",
10889
+ openFlag: "Ouvrir l'indicateur",
10890
+ confirm: "Confirmer",
10891
+ name: "Nom",
10892
+ appId: "ID d'application",
10893
+ flagKey: "Indicateur de fonctionnalité",
10894
+ },
10895
+ },
10896
+ "de-DE": {
10897
+ agents: {
10898
+ featureFlags: "Funktionsschalter",
10899
+ featureFlagsDescription:
10900
+ "Prüfe registrierte App-Schalter und nimm bewusste, rückgängig machbare Rollout-Änderungen vor.",
10901
+ reloadFlags: "Funktionsschalter neu laden",
10902
+ flagsUnavailable: "Funktionsschalter sind nicht verfügbar",
10903
+ flagsUnreachable:
10904
+ "Das Arbeitsbereichsverzeichnis konnte nicht erreicht werden. Versuche es erneut, wenn die Verbindung verfügbar ist.",
10905
+ flagsEmpty: "Keine Arbeitsbereichs-App meldet Funktionsschalter",
10906
+ flagsEmptyDetail:
10907
+ "Registrierte Schalter erscheinen hier, sobald verbundene Apps bereit sind.",
10908
+ flagsNotReady:
10909
+ "Diese App ist {{status}}; ihr Schalterzustand wird nicht als aus angenommen.",
10910
+ noFlagDefinitions:
10911
+ "Diese bereite App hat keine registrierten Schalterdefinitionen.",
10912
+ noFlagDescription: "Keine Beschreibung vorhanden.",
10913
+ selected: "Ausgewählt",
10914
+ openFlag: "Funktionsschalter öffnen",
10915
+ confirm: "Bestätigen",
10916
+ name: "Name",
10917
+ appId: "App-ID",
10918
+ flagKey: "Funktionsschalter",
10919
+ },
10920
+ },
10921
+ "pt-BR": {
10922
+ agents: {
10923
+ featureFlags: "Sinalizadores de recursos",
10924
+ featureFlagsDescription:
10925
+ "Revise sinalizadores de apps registrados e faça mudanças de lançamento reversíveis.",
10926
+ reloadFlags: "Recarregar sinalizadores",
10927
+ flagsUnavailable: "Sinalizadores de recursos indisponíveis",
10928
+ flagsUnreachable:
10929
+ "Não foi possível acessar o diretório do espaço de trabalho. Tente novamente quando estiver disponível.",
10930
+ flagsEmpty: "Nenhum app informou sinalizadores",
10931
+ flagsEmptyDetail:
10932
+ "Sinalizadores registrados aparecerão aqui quando os apps conectados estiverem prontos.",
10933
+ flagsNotReady:
10934
+ "Este app está {{status}}; seu estado não é considerado desligado.",
10935
+ noFlagDefinitions:
10936
+ "Este app pronto não tem definições de sinalizador registradas.",
10937
+ noFlagDescription: "Nenhuma descrição fornecida.",
10938
+ selected: "Selecionado",
10939
+ openFlag: "Abrir sinalizador",
10940
+ confirm: "Confirmar",
10941
+ name: "Nome",
10942
+ appId: "ID do app",
10943
+ flagKey: "Sinalizador de recurso",
10944
+ },
10945
+ },
10946
+ "ja-JP": {
10947
+ agents: {
10948
+ featureFlags: "機能フラグ",
10949
+ featureFlagsDescription:
10950
+ "登録済みアプリのフラグを確認し、元に戻せるロールアウト変更を行います。",
10951
+ reloadFlags: "機能フラグを再読み込み",
10952
+ flagsUnavailable: "機能フラグを利用できません",
10953
+ flagsUnreachable:
10954
+ "ワークスペースディレクトリに接続できません。接続可能になったら再試行してください。",
10955
+ flagsEmpty: "フラグを報告したワークスペースアプリはありません",
10956
+ flagsEmptyDetail:
10957
+ "接続済みアプリの準備ができると、登録済みフラグがここに表示されます。",
10958
+ flagsNotReady:
10959
+ "このアプリは {{status}} です。フラグ状態をオフとは見なしません。",
10960
+ noFlagDefinitions:
10961
+ "この準備済みアプリには登録済みフラグ定義がありません。",
10962
+ noFlagDescription: "説明はありません。",
10963
+ selected: "選択済み",
10964
+ openFlag: "機能フラグを開く",
10965
+ confirm: "確認",
10966
+ name: "名前",
10967
+ appId: "アプリ ID",
10968
+ flagKey: "機能フラグ",
10969
+ },
10970
+ },
10971
+ "ko-KR": {
10972
+ agents: {
10973
+ featureFlags: "기능 플래그",
10974
+ featureFlagsDescription:
10975
+ "등록된 앱 플래그를 검토하고 되돌릴 수 있는 출시 변경을 적용합니다.",
10976
+ reloadFlags: "기능 플래그 다시 불러오기",
10977
+ flagsUnavailable: "기능 플래그를 사용할 수 없습니다",
10978
+ flagsUnreachable:
10979
+ "작업 공간 디렉터리에 연결할 수 없습니다. 연결되면 다시 시도하세요.",
10980
+ flagsEmpty: "플래그를 보고한 작업 공간 앱이 없습니다",
10981
+ flagsEmptyDetail:
10982
+ "연결된 앱이 준비되면 등록된 플래그가 여기에 표시됩니다.",
10983
+ flagsNotReady:
10984
+ "이 앱은 {{status}} 상태이며 플래그가 꺼진 것으로 가정하지 않습니다.",
10985
+ noFlagDefinitions: "준비된 이 앱에는 등록된 플래그 정의가 없습니다.",
10986
+ noFlagDescription: "설명이 제공되지 않았습니다.",
10987
+ selected: "선택됨",
10988
+ openFlag: "기능 플래그 열기",
10989
+ confirm: "확인",
10990
+ name: "이름",
10991
+ appId: "앱 ID",
10992
+ flagKey: "기능 플래그",
10993
+ },
10994
+ },
10995
+ "hi-IN": {
10996
+ agents: {
10997
+ featureFlags: "फ़ीचर फ़्लैग",
10998
+ featureFlagsDescription:
10999
+ "पंजीकृत ऐप फ़्लैग देखें और वापस किए जा सकने वाले रोलआउट बदलाव करें।",
11000
+ reloadFlags: "फ़ीचर फ़्लैग फिर से लोड करें",
11001
+ flagsUnavailable: "फ़ीचर फ़्लैग उपलब्ध नहीं हैं",
11002
+ flagsUnreachable:
11003
+ "वर्कस्पेस डायरेक्टरी तक नहीं पहुँचा जा सका। कनेक्शन उपलब्ध होने पर फिर कोशिश करें।",
11004
+ flagsEmpty: "किसी वर्कस्पेस ऐप ने फ़्लैग की सूचना नहीं दी",
11005
+ flagsEmptyDetail: "कनेक्टेड ऐप तैयार होने पर पंजीकृत फ़्लैग यहाँ दिखाई देंगे।",
11006
+ flagsNotReady: "यह ऐप {{status}} है; इसकी फ़्लैग स्थिति को बंद नहीं माना जाता।",
11007
+ noFlagDefinitions: "इस तैयार ऐप में पंजीकृत फ़्लैग परिभाषाएँ नहीं हैं।",
11008
+ noFlagDescription: "कोई विवरण नहीं दिया गया।",
11009
+ selected: "चयनित",
11010
+ openFlag: "फ़ीचर फ़्लैग खोलें",
11011
+ confirm: "पुष्टि करें",
11012
+ name: "नाम",
11013
+ appId: "ऐप ID",
11014
+ flagKey: "फ़ीचर फ़्लैग",
11015
+ },
11016
+ },
11017
+ "ar-SA": {
11018
+ agents: {
11019
+ featureFlags: "علامات الميزات",
11020
+ featureFlagsDescription:
11021
+ "راجع علامات التطبيقات المسجلة ونفّذ تغييرات طرح قابلة للعكس.",
11022
+ reloadFlags: "إعادة تحميل علامات الميزات",
11023
+ flagsUnavailable: "علامات الميزات غير متاحة",
11024
+ flagsUnreachable:
11025
+ "تعذر الوصول إلى دليل مساحة العمل. أعد المحاولة عند توفر الاتصال.",
11026
+ flagsEmpty: "لم يبلغ أي تطبيق في مساحة العمل عن علامات",
11027
+ flagsEmptyDetail:
11028
+ "ستظهر العلامات المسجلة هنا عندما تصبح التطبيقات المتصلة جاهزة.",
11029
+ flagsNotReady:
11030
+ "حالة هذا التطبيق هي {{status}}؛ ولا يُفترض أن علاماته متوقفة.",
11031
+ noFlagDefinitions: "لا يحتوي هذا التطبيق الجاهز على تعريفات علامات مسجلة.",
11032
+ noFlagDescription: "لم يُقدَّم وصف.",
11033
+ selected: "محدد",
11034
+ openFlag: "فتح علامة الميزة",
11035
+ confirm: "تأكيد",
11036
+ name: "الاسم",
11037
+ appId: "معرّف التطبيق",
11038
+ flagKey: "علامة ميزة",
11039
+ },
11040
+ },
11041
+ } satisfies Partial<Record<LocaleCode, AnalyticsPartialMessages>>;
11042
+
10804
11043
  const translatedDashboardAdminTranslations = {
10805
11044
  "zh-CN": {
10806
11045
  navigation: {
@@ -11161,6 +11400,18 @@ const translatedDashboardAdminTranslations = {
11161
11400
  },
11162
11401
  } satisfies Partial<Record<LocaleCode, AnalyticsPartialMessages>>;
11163
11402
 
11403
+ for (const [locale, overrides] of Object.entries(
11404
+ translatedFeatureFlagAdminTranslations,
11405
+ ) as Array<[LocaleCode, AnalyticsPartialMessages]>) {
11406
+ const messages = messagesByLocale[locale];
11407
+ if (!messages) continue;
11408
+ for (const [section, sectionOverrides] of Object.entries(overrides) as Array<
11409
+ [Section, Partial<Messages[Section]>]
11410
+ >) {
11411
+ Object.assign(messages[section], sectionOverrides);
11412
+ }
11413
+ }
11414
+
11164
11415
  for (const [locale, overrides] of Object.entries(
11165
11416
  translatedDashboardAdminTranslations,
11166
11417
  ) as Array<[LocaleCode, AnalyticsPartialMessages]>) {
@@ -18,11 +18,13 @@ import {
18
18
  IconLoader2,
19
19
  IconMouse,
20
20
  IconPlus,
21
+ IconToggleRight,
21
22
  IconTrash,
22
23
  } from "@tabler/icons-react";
23
24
  import { useEffect, useMemo, useState, type FormEvent } from "react";
24
25
  import { Link, useSearchParams } from "react-router";
25
26
 
27
+ import { FeatureFlagsFleetPanel } from "@/components/agents/FeatureFlagsFleetPanel";
26
28
  import {
27
29
  AlertDialog,
28
30
  AlertDialogAction,
@@ -70,7 +72,7 @@ import {
70
72
  } from "@/components/ui/table";
71
73
  import { cn } from "@/lib/utils";
72
74
 
73
- type AgentAdminView = "monitoring" | "dashboards" | "database";
75
+ type AgentAdminView = "monitoring" | "dashboards" | "database" | "flags";
74
76
 
75
77
  interface DbAdminConnection {
76
78
  id: string;
@@ -117,6 +119,7 @@ const AGENT_ADMIN_VIEWS: AgentAdminView[] = [
117
119
  "monitoring",
118
120
  "dashboards",
119
121
  "database",
122
+ "flags",
120
123
  ];
121
124
 
122
125
  function parseView(value: string | null): AgentAdminView {
@@ -383,7 +386,7 @@ export default function AgentsPage() {
383
386
  const [searchParams, setSearchParams] = useSearchParams();
384
387
  const view = parseView(searchParams.get("view"));
385
388
  const selectedConnectionId = searchParams.get("db");
386
- const isAdminView = view === "dashboards" || view === "database";
389
+ const isAdminView = view !== "monitoring";
387
390
 
388
391
  function setView(next: AgentAdminView) {
389
392
  const params = new URLSearchParams(searchParams);
@@ -493,6 +496,21 @@ export default function AgentsPage() {
493
496
  {t("agents.dashboardUsage")}
494
497
  </button>
495
498
  ) : null}
499
+ {canManageOrg ? (
500
+ <button
501
+ type="button"
502
+ onClick={() => setView("flags")}
503
+ className={cn(
504
+ "inline-flex h-8 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors",
505
+ view === "flags"
506
+ ? "bg-foreground text-background"
507
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
508
+ )}
509
+ >
510
+ <IconToggleRight className="size-4" />
511
+ {t("agents.featureFlags")}
512
+ </button>
513
+ ) : null}
496
514
  {canManageOrg && view === "database" && (
497
515
  <button
498
516
  type="button"
@@ -512,6 +530,8 @@ export default function AgentsPage() {
512
530
  />
513
531
  ) : view === "dashboards" ? (
514
532
  <DashboardUsageAdminPanel />
533
+ ) : view === "flags" ? (
534
+ <FeatureFlagsFleetPanel />
515
535
  ) : (
516
536
  <div className="min-w-0">
517
537
  <div className="mb-4 max-w-3xl text-sm leading-6 text-muted-foreground">
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-17
4
+ ---
5
+
6
+ Manage feature flag rollouts across your organization’s apps from one Analytics control panel.