@data-club/ai-hub 0.0.3 → 0.0.5

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 (39) hide show
  1. package/dist/auth/locales/cs.d.ts.map +1 -1
  2. package/dist/auth/locales/cs.js +17 -0
  3. package/dist/auth/locales/cs.js.map +1 -1
  4. package/dist/auth/locales/sk.d.ts.map +1 -1
  5. package/dist/auth/locales/sk.js +17 -0
  6. package/dist/auth/locales/sk.js.map +1 -1
  7. package/dist/auth/texts.d.ts +26 -0
  8. package/dist/auth/texts.d.ts.map +1 -1
  9. package/dist/auth/texts.js +17 -0
  10. package/dist/auth/texts.js.map +1 -1
  11. package/dist/client/AIHubClient.d.ts +13 -0
  12. package/dist/client/AIHubClient.d.ts.map +1 -1
  13. package/dist/client/AIHubClient.js +10 -0
  14. package/dist/client/AIHubClient.js.map +1 -1
  15. package/dist/components/ChatShell/index.d.ts.map +1 -1
  16. package/dist/components/ChatShell/index.js +35 -2
  17. package/dist/components/ChatShell/index.js.map +1 -1
  18. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -1
  19. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +56 -1
  20. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
  21. package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -1
  22. package/dist/components/TurnStatusIndicator/index.js +2 -1
  23. package/dist/components/TurnStatusIndicator/index.js.map +1 -1
  24. package/dist/state/affordances.d.ts +13 -1
  25. package/dist/state/affordances.d.ts.map +1 -1
  26. package/dist/state/affordances.js +11 -9
  27. package/dist/state/affordances.js.map +1 -1
  28. package/dist/types.d.ts +11 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/auth/locales/cs.ts +19 -0
  32. package/src/auth/locales/sk.ts +19 -0
  33. package/src/auth/texts.ts +49 -0
  34. package/src/client/AIHubClient.ts +23 -0
  35. package/src/components/ChatShell/index.tsx +34 -2
  36. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +145 -1
  37. package/src/components/TurnStatusIndicator/index.tsx +2 -1
  38. package/src/state/affordances.ts +34 -10
  39. package/src/types.ts +11 -0
package/src/auth/texts.ts CHANGED
@@ -304,6 +304,26 @@ export interface AIHubChatTexts {
304
304
  /* ── Language switcher (header, beside the user avatar) ─────────── */
305
305
  /** `aria-label` / tooltip on the language-switcher button. */
306
306
  languageSwitcherAriaLabel: string;
307
+
308
+ /* ── Turn-status indicator (§10.4 — `●` dot + label) ───────────── */
309
+ /** Idle / ready (no turn in flight). */
310
+ turnStatusReady: string;
311
+ /** `running` + `model_text` (or unknown phase) — assistant text streaming. */
312
+ turnStatusGenerating: string;
313
+ /** `running` + `model_reasoning`. */
314
+ turnStatusReasoning: string;
315
+ /** `running` + `model_tool_input` — assembling a tool call. */
316
+ turnStatusPreparingTool: string;
317
+ /** `running` + `tool_executing`. */
318
+ turnStatusRunningTool: string;
319
+ /** `running` + `waiting_provider` — rate-limited / recovering. */
320
+ turnStatusWaitingRateLimit: string;
321
+ /** Terminal `failed`. */
322
+ turnStatusFailed: string;
323
+ /** Terminal `cancelled` (before the auto-clear to ready). */
324
+ turnStatusCancelled: string;
325
+ /** Terminal `done` (the brief flash before settling to ready). */
326
+ turnStatusDone: string;
307
327
  }
308
328
 
309
329
  /**
@@ -794,6 +814,16 @@ export interface AIHubAdminTexts {
794
814
  settingsLanguageLockedHelper: string;
795
815
  settingsSaveLanguageButton: string;
796
816
 
817
+ /* Agents section. */
818
+ settingsAgentsSectionHeading: string;
819
+ settingsAgentsDefaultLabel: string;
820
+ /** First `<option>` — no default agent (show the picker). */
821
+ settingsAgentsDefaultNone: string;
822
+ settingsAgentsDefaultHelper: string;
823
+ settingsAgentsSwitcherEnabledLabel: string;
824
+ settingsAgentsSwitcherEnabledHelper: string;
825
+ settingsSaveAgentsButton: string;
826
+
797
827
  /* Feature toggles section. */
798
828
  settingsTogglesSectionHeading: string;
799
829
  settingsTogglesGroupSessions: string;
@@ -973,6 +1003,16 @@ export const defaultAIHubTexts: AIHubTexts = Object.freeze({
973
1003
  openLettaAdeMenuItem: "Open Letta ADE",
974
1004
  /* Language switcher. */
975
1005
  languageSwitcherAriaLabel: "Change language",
1006
+ /* Turn-status indicator. */
1007
+ turnStatusReady: "Ready",
1008
+ turnStatusGenerating: "Generating…",
1009
+ turnStatusReasoning: "Reasoning…",
1010
+ turnStatusPreparingTool: "Preparing tool call…",
1011
+ turnStatusRunningTool: "Running tool…",
1012
+ turnStatusWaitingRateLimit: "Waiting (rate limit)…",
1013
+ turnStatusFailed: "Failed",
1014
+ turnStatusCancelled: "Cancelled",
1015
+ turnStatusDone: "Done",
976
1016
  }),
977
1017
  admin: Object.freeze({
978
1018
  /* Shell. */
@@ -1250,6 +1290,15 @@ export const defaultAIHubTexts: AIHubTexts = Object.freeze({
1250
1290
  settingsLanguageLockedHelper:
1251
1291
  "When on, the language switcher is hidden and every user sees the default language. When off, users can pick their own language and their choice is remembered on their device.",
1252
1292
  settingsSaveLanguageButton: "Save language",
1293
+ settingsAgentsSectionHeading: "Agents",
1294
+ settingsAgentsDefaultLabel: "Default agent",
1295
+ settingsAgentsDefaultNone: "No default — show the agent list",
1296
+ settingsAgentsDefaultHelper:
1297
+ "When set, users open straight into this agent instead of the picker. A deployment with a single agent always opens that one regardless of this setting.",
1298
+ settingsAgentsSwitcherEnabledLabel: "Allow users to switch agents",
1299
+ settingsAgentsSwitcherEnabledHelper:
1300
+ "When off, the “← Agents” back link is hidden so users stay on their agent. It is also hidden whenever only one agent exists.",
1301
+ settingsSaveAgentsButton: "Save agents",
1253
1302
  settingsTogglesSectionHeading: "UI feature toggles",
1254
1303
  settingsTogglesGroupSessions: "Sessions panel",
1255
1304
  settingsTogglesGroupInput: "Input",
@@ -583,6 +583,13 @@ export interface AdminSettingsView {
583
583
  default: AIHubLanguage;
584
584
  user_locked: boolean;
585
585
  };
586
+ /** Agent-picker settings, stored as separate columns server-side. */
587
+ agents: {
588
+ /** Default ai-hub agent id auto-opened on load, or `null` for the picker. */
589
+ default_id: string | null;
590
+ /** Whether users may switch agents (the "← Agents" back link). */
591
+ switcher_enabled: boolean;
592
+ };
586
593
  /** The flat §8.4 feature-toggles blob. */
587
594
  feature_toggles: AIHubFeatureToggles;
588
595
  /** ISO-8601 of the last signing-key rotation. `null` until first rotation. */
@@ -611,6 +618,12 @@ export interface PatchAdminSettingsBody {
611
618
  default?: AIHubLanguage;
612
619
  userLocked?: boolean;
613
620
  };
621
+ /** Agent-picker partial — either or both fields. */
622
+ agents?: {
623
+ /** Agent id to default to; `null` / `""` clears (→ show the picker). */
624
+ defaultId?: string | null;
625
+ switcherEnabled?: boolean;
626
+ };
614
627
  }
615
628
 
616
629
  /** Response from `rotateAdminMetricsToken` — the new token returned **once**. */
@@ -1505,6 +1518,16 @@ export class AIHubClient {
1505
1518
  }
1506
1519
  wire.language = languageWire;
1507
1520
  }
1521
+ if (body.agents !== undefined) {
1522
+ const agentsWire: Record<string, unknown> = {};
1523
+ if (body.agents.defaultId !== undefined) {
1524
+ agentsWire.default_id = body.agents.defaultId;
1525
+ }
1526
+ if (body.agents.switcherEnabled !== undefined) {
1527
+ agentsWire.switcher_enabled = body.agents.switcherEnabled;
1528
+ }
1529
+ wire.agents = agentsWire;
1530
+ }
1508
1531
  return this.requestJson<AdminSettingsView>("PATCH", "/admin/settings", {
1509
1532
  body: wire,
1510
1533
  });
@@ -13,7 +13,7 @@
13
13
  * @see ui-sketches/00-shell.md, ui-sketches/03-chat.md
14
14
  */
15
15
 
16
- import { useEffect, useMemo, type ReactNode } from "react";
16
+ import { useEffect, useMemo, useRef, type ReactNode } from "react";
17
17
 
18
18
  import {
19
19
  type AIHubTexts,
@@ -21,6 +21,7 @@ import {
21
21
  } from "../../auth/texts.js";
22
22
  import { useResolvedTexts } from "../../context/useAIHubTexts.js";
23
23
  import { useAIHub } from "../../context/useAIHub.js";
24
+ import { useAIHubAuth } from "../../context/useAIHubAuth.js";
24
25
  import {
25
26
  resolveAffordanceFlags,
26
27
  type AIHubAffordanceFlags,
@@ -171,9 +172,13 @@ export function ChatShell(props: ChatShellProps): ReactNode {
171
172
  setActiveSession,
172
173
  featureToggles,
173
174
  } = useAIHub();
175
+ const { deployment } = useAIHubAuth();
174
176
  const texts = useResolvedTexts(props.texts);
175
177
  const productName = props.productName ?? "ai-hub";
176
178
  const showAgentsPicker = props.showAgentsPicker ?? true;
179
+ // §7.x agent-picker deployment config: when switching is off the "← Agents"
180
+ // back link is hidden; a single-agent deployment hides it too (see below).
181
+ const agentSwitcherEnabled = deployment?.agent_switcher_enabled ?? true;
177
182
  const sessionsPanelPosition = props.sessionsPanelPosition ?? "left";
178
183
  const sessionsPanelWidthPercent = props.sessionsPanelWidthPercent ?? 25;
179
184
 
@@ -214,6 +219,31 @@ export function ChatShell(props: ChatShellProps): ReactNode {
214
219
  }
215
220
  }, [autoSelectOnlyAgent, agents, setActiveAgent]);
216
221
 
222
+ // Deployment default agent (multi-agent deployments): open it once on first
223
+ // agents-load instead of showing the picker. A deep-link `initialAgentId`
224
+ // wins (handled by its own effect above). Guarded by a ref so that, when
225
+ // switching is enabled, clicking "← Agents" genuinely returns to the picker
226
+ // rather than snapping straight back to the default.
227
+ const appliedDefaultAgentRef = useRef(false);
228
+ useEffect(() => {
229
+ if (appliedDefaultAgentRef.current) return;
230
+ if (props.initialAgentId) return;
231
+ if (agentsLoading || agents.length <= 1) return;
232
+ appliedDefaultAgentRef.current = true;
233
+ if (activeAgentId) return;
234
+ const defaultId = deployment?.default_agent_id ?? null;
235
+ if (defaultId && agents.some((a) => a.id === defaultId)) {
236
+ setActiveAgent(defaultId);
237
+ }
238
+ }, [
239
+ agents,
240
+ agentsLoading,
241
+ activeAgentId,
242
+ props.initialAgentId,
243
+ deployment,
244
+ setActiveAgent,
245
+ ]);
246
+
217
247
  const inPicker = showAgentsPicker && !activeAgentId;
218
248
 
219
249
  const rootClass = props.className
@@ -259,7 +289,9 @@ export function ChatShell(props: ChatShellProps): ReactNode {
259
289
  />
260
290
  ) : null}
261
291
  <MainColumn
262
- showBackToAgents={showAgentsPicker}
292
+ showBackToAgents={
293
+ showAgentsPicker && agentSwitcherEnabled && agents.length > 1
294
+ }
263
295
  showTurnStatus={flags.turnStatusVisible}
264
296
  {...(props.texts !== undefined ? { texts: props.texts } : {})}
265
297
  {...(props.customHeaderContent !== undefined
@@ -27,7 +27,10 @@
27
27
  import { useEffect, useState, type ReactNode } from "react";
28
28
 
29
29
  import type { AIHubAdminTexts, AIHubLanguage } from "../../auth/texts.js";
30
- import type { AdminSettingsView } from "../../client/AIHubClient.js";
30
+ import type {
31
+ AdminAgentView,
32
+ AdminSettingsView,
33
+ } from "../../client/AIHubClient.js";
31
34
  import { useAIHub } from "../../context/useAIHub.js";
32
35
  import { useAIHubAuth } from "../../context/useAIHubAuth.js";
33
36
  import {
@@ -188,6 +191,11 @@ export function AdminSettingsPage(props: AdminSettingsPageProps): ReactNode {
188
191
  texts={props.texts}
189
192
  onSaved={handleSectionSaved}
190
193
  />
194
+ <AgentsSection
195
+ settings={settings}
196
+ texts={props.texts}
197
+ onSaved={handleSectionSaved}
198
+ />
191
199
  <FeatureTogglesSection
192
200
  settings={settings}
193
201
  texts={props.texts}
@@ -646,6 +654,142 @@ function LanguageSection(props: LanguageSectionProps): ReactNode {
646
654
  );
647
655
  }
648
656
 
657
+ /* ────────────────────────────────────────────────────────────────────────── */
658
+ /* Section: Agents */
659
+ /* ────────────────────────────────────────────────────────────────────────── */
660
+
661
+ interface AgentsSectionProps extends SectionProps {
662
+ onSaved: (next: AdminSettingsView) => void;
663
+ }
664
+
665
+ function AgentsSection(props: AgentsSectionProps): ReactNode {
666
+ const { client } = useAIHub();
667
+ const [defaultAgentId, setDefaultAgentId] = useState<string>(
668
+ props.settings.agents.default_id ?? "",
669
+ );
670
+ const [switcherEnabled, setSwitcherEnabled] = useState(
671
+ props.settings.agents.switcher_enabled,
672
+ );
673
+ // Non-archived agents to populate the default-agent <select>.
674
+ const [agents, setAgents] = useState<AdminAgentView[] | null>(null);
675
+ const [saving, setSaving] = useState(false);
676
+ const [flash, setFlash] = useState<{ tone: "success" | "error"; text: string } | null>(
677
+ null,
678
+ );
679
+
680
+ useEffect(() => {
681
+ let cancelled = false;
682
+ void (async () => {
683
+ try {
684
+ const rows = await client.listAdminAgents();
685
+ if (cancelled) return;
686
+ setAgents(rows.filter((a) => !a.is_archived));
687
+ } catch {
688
+ if (!cancelled) setAgents([]);
689
+ }
690
+ })();
691
+ return () => {
692
+ cancelled = true;
693
+ };
694
+ }, [client]);
695
+
696
+ async function handleSave(event: React.FormEvent): Promise<void> {
697
+ event.preventDefault();
698
+ setSaving(true);
699
+ setFlash(null);
700
+ try {
701
+ const next = await client.patchAdminSettings({
702
+ agents: { defaultId: defaultAgentId, switcherEnabled },
703
+ });
704
+ props.onSaved(next);
705
+ setFlash({ tone: "success", text: props.texts.settingsSaveSuccessFlash });
706
+ } catch (err) {
707
+ setFlash({
708
+ tone: "error",
709
+ text:
710
+ err instanceof Error && err.message.length > 0
711
+ ? err.message
712
+ : props.texts.settingsSaveGenericError,
713
+ });
714
+ } finally {
715
+ setSaving(false);
716
+ }
717
+ }
718
+
719
+ return (
720
+ <section
721
+ className="data-club-ai-hub-admin__settings-section"
722
+ data-testid="admin-settings-agents-section"
723
+ >
724
+ <h2 className="data-club-ai-hub-admin__settings-section-heading">
725
+ {props.texts.settingsAgentsSectionHeading}
726
+ </h2>
727
+ <form onSubmit={(event) => void handleSave(event)} noValidate>
728
+ <label
729
+ className="data-club-ai-hub-admin__form-label"
730
+ htmlFor="admin-settings-default-agent"
731
+ >
732
+ {props.texts.settingsAgentsDefaultLabel}
733
+ </label>
734
+ <select
735
+ id="admin-settings-default-agent"
736
+ className="data-club-ai-hub-admin__form-input"
737
+ data-testid="admin-settings-default-agent"
738
+ value={defaultAgentId}
739
+ onChange={(event) => setDefaultAgentId(event.target.value)}
740
+ disabled={saving || agents === null}
741
+ >
742
+ <option value="">{props.texts.settingsAgentsDefaultNone}</option>
743
+ {(agents ?? []).map((agent) => (
744
+ <option key={agent.id} value={agent.id}>
745
+ {agent.display_name}
746
+ </option>
747
+ ))}
748
+ </select>
749
+ <p className="data-club-ai-hub-admin__form-helper">
750
+ {props.texts.settingsAgentsDefaultHelper}
751
+ </p>
752
+ <label className="data-club-ai-hub-admin__checkbox">
753
+ <input
754
+ type="checkbox"
755
+ data-testid="admin-settings-agent-switcher-enabled"
756
+ checked={switcherEnabled}
757
+ onChange={(event) => setSwitcherEnabled(event.target.checked)}
758
+ disabled={saving}
759
+ />
760
+ <span>{props.texts.settingsAgentsSwitcherEnabledLabel}</span>
761
+ </label>
762
+ <p className="data-club-ai-hub-admin__form-helper">
763
+ {props.texts.settingsAgentsSwitcherEnabledHelper}
764
+ </p>
765
+ {flash !== null ? (
766
+ <div
767
+ className={
768
+ flash.tone === "success"
769
+ ? "data-club-ai-hub-admin__flash data-club-ai-hub-admin__flash--success"
770
+ : "data-club-ai-hub-admin__flash data-club-ai-hub-admin__flash--error"
771
+ }
772
+ data-testid="admin-settings-agents-flash"
773
+ role={flash.tone === "error" ? "alert" : "status"}
774
+ >
775
+ {flash.text}
776
+ </div>
777
+ ) : null}
778
+ <button
779
+ type="submit"
780
+ className="data-club-ai-hub-admin__button data-club-ai-hub-admin__button--primary"
781
+ data-testid="admin-settings-agents-save"
782
+ disabled={saving}
783
+ >
784
+ {saving
785
+ ? props.texts.settingsSaveSavingButton
786
+ : props.texts.settingsSaveAgentsButton}
787
+ </button>
788
+ </form>
789
+ </section>
790
+ );
791
+ }
792
+
649
793
  /* ────────────────────────────────────────────────────────────────────────── */
650
794
  /* Section: Feature toggles */
651
795
  /* ────────────────────────────────────────────────────────────────────────── */
@@ -173,6 +173,7 @@ export function TurnStatusIndicator(
173
173
  }, [state.status, state.phase]);
174
174
 
175
175
  const visual = turnStatusVisual(effective.status, effective.phase);
176
+ const label = ctx.texts.chat[visual.labelKey];
176
177
  const rootClass = props.className
177
178
  ? `${cls.root} ${props.className}`
178
179
  : cls.root;
@@ -196,7 +197,7 @@ export function TurnStatusIndicator(
196
197
  >
197
198
  <span className={dotClass} aria-hidden="true" />
198
199
  <span className={cls.label} data-testid="turn-status-label">
199
- {visual.label}
200
+ {label}
200
201
  </span>
201
202
  </div>
202
203
  );
@@ -254,14 +254,36 @@ export function chatAffordances(state: AIHubState): AIHubChatAffordances {
254
254
  * `useEffect` + a short timer.
255
255
  */
256
256
  export interface AIHubTurnStatusVisual {
257
- /** Short human-readable label rendered next to the dot. */
257
+ /**
258
+ * Short human-readable label rendered next to the dot. **English** — this
259
+ * is the fallback for consumers that read `turnStatusVisual` directly. The
260
+ * `<TurnStatusIndicator />` primitive renders the localized
261
+ * `texts.chat[labelKey]` instead.
262
+ */
258
263
  label: string;
264
+ /**
265
+ * The `AIHubChatTexts` key for the localized label. Each value must name an
266
+ * existing `turnStatus*` key in the chat texts.
267
+ */
268
+ labelKey: TurnStatusLabelKey;
259
269
  /** Dot variant — drives the BEM modifier on the dot element. */
260
270
  dot: "idle" | "running" | "warning" | "error";
261
271
  /** Whether the dot pulses (suppressed under `prefers-reduced-motion`). */
262
272
  pulse: boolean;
263
273
  }
264
274
 
275
+ /** The `turnStatus*` keys in `AIHubChatTexts` a status can resolve to. */
276
+ export type TurnStatusLabelKey =
277
+ | "turnStatusReady"
278
+ | "turnStatusGenerating"
279
+ | "turnStatusReasoning"
280
+ | "turnStatusPreparingTool"
281
+ | "turnStatusRunningTool"
282
+ | "turnStatusWaitingRateLimit"
283
+ | "turnStatusFailed"
284
+ | "turnStatusCancelled"
285
+ | "turnStatusDone";
286
+
265
287
  /**
266
288
  * Computes the §10.4 turn-status indicator's `(label, dot, pulse)` from
267
289
  * a `(status, phase)` pair.
@@ -281,20 +303,22 @@ export function turnStatusVisual(
281
303
  if (status === "running") {
282
304
  switch (phase) {
283
305
  case "model_text":
284
- return { label: "Generating…", dot: "running", pulse: true };
306
+ return { label: "Generating…", labelKey: "turnStatusGenerating", dot: "running", pulse: true };
285
307
  case "model_reasoning":
286
- return { label: "Reasoning…", dot: "running", pulse: true };
308
+ return { label: "Reasoning…", labelKey: "turnStatusReasoning", dot: "running", pulse: true };
287
309
  case "model_tool_input":
288
310
  return {
289
311
  label: "Preparing tool call…",
312
+ labelKey: "turnStatusPreparingTool",
290
313
  dot: "running",
291
314
  pulse: true,
292
315
  };
293
316
  case "tool_executing":
294
- return { label: "Running tool…", dot: "running", pulse: true };
317
+ return { label: "Running tool…", labelKey: "turnStatusRunningTool", dot: "running", pulse: true };
295
318
  case "waiting_provider":
296
319
  return {
297
320
  label: "Waiting (rate limit)…",
321
+ labelKey: "turnStatusWaitingRateLimit",
298
322
  dot: "warning",
299
323
  pulse: true,
300
324
  };
@@ -302,20 +326,20 @@ export function turnStatusVisual(
302
326
  // Not exercised by the predicate table (the spec doesn't surface
303
327
  // a label for awaiting_user); fall back to "Ready" so the dot
304
328
  // doesn't strobe.
305
- return { label: "Ready", dot: "idle", pulse: false };
329
+ return { label: "Ready", labelKey: "turnStatusReady", dot: "idle", pulse: false };
306
330
  case null:
307
- return { label: "Generating…", dot: "running", pulse: true };
331
+ return { label: "Generating…", labelKey: "turnStatusGenerating", dot: "running", pulse: true };
308
332
  }
309
333
  }
310
334
  if (status === "failed") {
311
- return { label: "Failed", dot: "error", pulse: false };
335
+ return { label: "Failed", labelKey: "turnStatusFailed", dot: "error", pulse: false };
312
336
  }
313
337
  if (status === "cancelled") {
314
- return { label: "Cancelled", dot: "idle", pulse: false };
338
+ return { label: "Cancelled", labelKey: "turnStatusCancelled", dot: "idle", pulse: false };
315
339
  }
316
340
  if (status === "done") {
317
- return { label: "Done", dot: "idle", pulse: false };
341
+ return { label: "Done", labelKey: "turnStatusDone", dot: "idle", pulse: false };
318
342
  }
319
343
  // idle
320
- return { label: "Ready", dot: "idle", pulse: false };
344
+ return { label: "Ready", labelKey: "turnStatusReady", dot: "idle", pulse: false };
321
345
  }
package/src/types.ts CHANGED
@@ -112,6 +112,17 @@ export interface AIHubDeployment {
112
112
  * per-device choice.
113
113
  */
114
114
  user_language_locked: boolean;
115
+ /**
116
+ * ai-hub agent id auto-opened on load (skipping the agents picker). `null` =
117
+ * no default → the picker is shown. Ignored when only one agent exists (that
118
+ * one always opens). A dangling id falls back to the picker.
119
+ */
120
+ default_agent_id: string | null;
121
+ /**
122
+ * When `false`, users can't switch agents — the "← Agents" back link is
123
+ * hidden. (Also hidden whenever only one agent exists, regardless of this.)
124
+ */
125
+ agent_switcher_enabled: boolean;
115
126
  /**
116
127
  * Browser-facing URL of the self-hosted Letta ADE — only present when the
117
128
  * caller's role is `power_user` or higher. Absent for `user`-role callers.