@agent-native/core 0.172.4 → 0.172.8

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 (40) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-24-chat-draft-preservation.md +5 -0
  3. package/corpus/templates/clips/changelog/2026-08-24-muted-microphone-start-guidance.md +5 -0
  4. package/corpus/templates/clips/desktop/src/app.tsx +4 -24
  5. package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +3 -20
  6. package/corpus/templates/clips/desktop/src/styles.css +0 -20
  7. package/corpus/templates/factory/AGENTS.md +5 -3
  8. package/corpus/templates/factory/actions/babysit-pull-request.ts +6 -0
  9. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -2
  10. package/corpus/templates/factory/actions/save-factory-automation.ts +0 -5
  11. package/corpus/templates/factory/changelog/2026-08-24-slack-checking-works-for-teammate-created-factories.md +6 -0
  12. package/corpus/templates/factory/changelog/2026-08-24-teammates-can-edit-and-run-factory-jobs.md +6 -0
  13. package/corpus/templates/factory/server/lib/factory-scope.ts +8 -0
  14. package/corpus/templates/factory/server/lib/require-factory-automation.ts +1 -9
  15. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +2 -1
  16. package/dist/automations/service.d.ts +6 -0
  17. package/dist/automations/service.js +20 -0
  18. package/dist/client/AssistantChat.js +1 -1
  19. package/dist/client/settings/SettingsPanel.js +80 -33
  20. package/dist/jobs/run-now.js +2 -2
  21. package/dist/server/attribution.d.ts +33 -3
  22. package/dist/server/attribution.js +24 -2
  23. package/dist/server/auth.js +20 -5
  24. package/dist/server/better-auth-instance.d.ts +54 -2
  25. package/dist/server/better-auth-instance.js +94 -46
  26. package/dist/server/google-oauth.d.ts +9 -0
  27. package/dist/server/google-oauth.js +7 -5
  28. package/dist/server/identity-sso.js +4 -0
  29. package/dist/server/request-context.d.ts +6 -0
  30. package/dist/tracking/providers.js +3 -0
  31. package/dist/vite/client.js +5 -2
  32. package/docs/content/docs-components.mdx +20 -1
  33. package/docs/content/template-calendar-agent.mdx +63 -26
  34. package/docs/content/template-calendar-developers.mdx +165 -40
  35. package/docs/content/template-calendar-features.mdx +276 -0
  36. package/docs/content/template-calendar-integrations.mdx +78 -0
  37. package/docs/content/template-calendar.mdx +56 -123
  38. package/package.json +3 -3
  39. package/docs/content/template-calendar-booking-links.mdx +0 -73
  40. package/docs/content/template-calendar-scheduling.mdx +0 -128
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8592
34
+ - template files: 8596
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-24
4
+ ---
5
+ Analytics chat drafts now stay in place while a new chat finishes loading.
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-24
4
+ ---
5
+ Muted microphone alerts now return you to recording setup before recording starts.
@@ -1087,8 +1087,6 @@ export function App({
1087
1087
  );
1088
1088
  const [micOn, setMicOn] = useState<boolean>(() => loadBool(MIC_ON_KEY, true));
1089
1089
  const [micOffConfirmOpen, setMicOffConfirmOpen] = useState(false);
1090
- const pendingStartOptionsRef =
1091
- useRef<Parameters<typeof handleStartRecording>[0]>(undefined);
1092
1090
  const [systemAudioOn, setSystemAudioOn] = useState<boolean>(() =>
1093
1091
  loadBool(SYSTEM_AUDIO_KEY, true),
1094
1092
  );
@@ -3591,16 +3589,14 @@ export function App({
3591
3589
  }, []);
3592
3590
 
3593
3591
  // Gates every start-recording gesture (button, global shortcut, permission
3594
- // retry) on the mic toggle. When the mic is off we hold the actual
3595
- // getDisplayMedia/getUserMedia call until the user confirms in
3596
- // micOffConfirmOpen — the confirm button's own click supplies the user
3597
- // activation handleStartRecording needs, same as the direct gesture would.
3592
+ // retry) on the mic toggle. When the mic is off we show the informational
3593
+ // mic-off screen and wait for the user to go back and change that setting
3594
+ // before the actual getDisplayMedia/getUserMedia call runs.
3598
3595
  function beginRecording(
3599
3596
  options?: Parameters<typeof handleStartRecording>[0],
3600
3597
  beginOptions?: { revealPopoverIfMicOff?: boolean },
3601
3598
  ) {
3602
3599
  if (!micOn) {
3603
- pendingStartOptionsRef.current = options;
3604
3600
  if (beginOptions?.revealPopoverIfMicOff) {
3605
3601
  invoke("show_popover").catch(() => {});
3606
3602
  }
@@ -3611,21 +3607,9 @@ export function App({
3611
3607
  }
3612
3608
 
3613
3609
  function closeMicOffConfirmation() {
3614
- pendingStartOptionsRef.current = undefined;
3615
3610
  setMicOffConfirmOpen(false);
3616
3611
  }
3617
3612
 
3618
- function unmuteFromConfirmation() {
3619
- setMicOn(true);
3620
- closeMicOffConfirmation();
3621
- }
3622
-
3623
- function continueWithoutMic() {
3624
- const options = pendingStartOptionsRef.current;
3625
- closeMicOffConfirmation();
3626
- void handleStartRecording(options);
3627
- }
3628
-
3629
3613
  recordShortcutHandlerRef.current = () => {
3630
3614
  if (recorder) {
3631
3615
  emit("clips:recorder-stop").catch(() => {});
@@ -4319,11 +4303,7 @@ export function App({
4319
4303
  return (
4320
4304
  <div className="app app-recorder" ref={appRef}>
4321
4305
  {micOffConfirmOpen ? (
4322
- <MicOffConfirmation
4323
- onBack={closeMicOffConfirmation}
4324
- onUnmute={unmuteFromConfirmation}
4325
- onContinue={continueWithoutMic}
4326
- />
4306
+ <MicOffConfirmation onBack={closeMicOffConfirmation} />
4327
4307
  ) : null}
4328
4308
 
4329
4309
  <div
@@ -1,14 +1,6 @@
1
1
  import { IconArrowLeft, IconMicrophone2 } from "@tabler/icons-react";
2
2
 
3
- export function MicOffConfirmation({
4
- onBack,
5
- onUnmute,
6
- onContinue,
7
- }: {
8
- onBack: () => void;
9
- onUnmute: () => void;
10
- onContinue: () => void;
11
- }) {
3
+ export function MicOffConfirmation({ onBack }: { onBack: () => void }) {
12
4
  return (
13
5
  <section className="mic-off-confirmation" aria-labelledby="mic-off-title">
14
6
  <header className="mic-off-confirmation-header">
@@ -29,19 +21,10 @@ export function MicOffConfirmation({
29
21
  </div>
30
22
  <h2 id="mic-off-title">Your mic is muted</h2>
31
23
  <p>
32
- To have sound in your video, you&apos;ll need to unmute your
33
- microphone.
24
+ You can change your microphone setting and then come back to start
25
+ recording.
34
26
  </p>
35
27
  </div>
36
-
37
- <footer className="mic-off-confirmation-actions">
38
- <button type="button" className="secondary" onClick={onUnmute}>
39
- Unmute
40
- </button>
41
- <button type="button" className="primary" onClick={onContinue}>
42
- Continue
43
- </button>
44
- </footer>
45
28
  </section>
46
29
  );
47
30
  }
@@ -2177,26 +2177,6 @@ body[data-clips-route="recording-pill"] #root {
2177
2177
  line-height: 1.55;
2178
2178
  }
2179
2179
 
2180
- .mic-off-confirmation-actions {
2181
- display: grid;
2182
- grid-template-columns: repeat(2, minmax(0, 1fr));
2183
- gap: 10px;
2184
- padding: 16px 18px 18px;
2185
- border-top: 1px solid var(--border);
2186
- background: var(--surface);
2187
- }
2188
-
2189
- .mic-off-confirmation-actions .primary,
2190
- .mic-off-confirmation-actions .secondary {
2191
- width: auto;
2192
- min-width: 0;
2193
- height: 42px;
2194
- padding: 0 12px;
2195
- border-radius: var(--radius);
2196
- font-size: 13px;
2197
- white-space: nowrap;
2198
- }
2199
-
2200
2180
  /* ------------------------------------------------------------------------- */
2201
2181
  /* Alert dialog */
2202
2182
  /* ------------------------------------------------------------------------- */
@@ -76,9 +76,11 @@ decisions, feedback, agent runs, and provider audit records.
76
76
 
77
77
  Rules start in shadow mode; hard guards apply. Organization automations use
78
78
  stored prompts; external mutations require durable, idempotent runs and
79
- provider confirmation. Use the visual editor for graph changes and agent chat
80
- for proposals; persist complete graphs with `save-factory-graph`. Change rules
81
- through triage actions, never graph JSON.
79
+ provider confirmation. Poll and Builder/PR dispatch run only as this factory's
80
+ scheduled job, not chat and not a workspace-owner email match; teammates may
81
+ edit and Run now Factory jobs. Use the visual editor for graph changes and
82
+ agent chat for proposals; persist complete graphs with `save-factory-graph`.
83
+ Change rules through triage actions, never graph JSON.
82
84
 
83
85
  ## Source Changes
84
86
 
@@ -2,6 +2,7 @@ import { defineAction } from "@agent-native/core/action";
2
2
  import { z } from "zod";
3
3
 
4
4
  import { resolveConnectorSecret } from "../server/connectors/credentials.js";
5
+ import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
5
6
  import {
6
7
  requireWorkspaceMember,
7
8
  workspaceMemberIdentityFromContext,
@@ -106,6 +107,11 @@ export function createBabysitPullRequestAction(
106
107
  const { userEmail, orgId } = await requireWorkspaceMember(
107
108
  workspaceMemberIdentityFromContext(context),
108
109
  );
110
+ await requireFactoryAutomation(
111
+ context,
112
+ { userEmail, orgId },
113
+ "prBabysit",
114
+ );
109
115
  const { comments, truncated } = await fetchComments({
110
116
  repo: input.repo,
111
117
  pullRequestNumber: input.pullRequestNumber,
@@ -47,7 +47,7 @@ export default defineAction({
47
47
  factoryId,
48
48
  );
49
49
  return Promise.all(
50
- scoped.map(async ({ resource, name, meta, body, canUpdate }) => {
50
+ scoped.map(async ({ resource, name, meta, body }) => {
51
51
  const runs = await listAutomationRuns({
52
52
  owners: [resource.owner],
53
53
  automation: automationRunHistoryKey(resource.path),
@@ -72,7 +72,7 @@ export default defineAction({
72
72
  Number.isFinite(resource.updatedAt) && resource.updatedAt > 0
73
73
  ? new Date(resource.updatedAt).toISOString()
74
74
  : null,
75
- canUpdate,
75
+ canUpdate: true,
76
76
  runs: runs.filter((run) => run.path === resource.path),
77
77
  };
78
78
  }),
@@ -76,11 +76,6 @@ export default defineAction({
76
76
  "Factory automation id and name do not refer to the same automation.",
77
77
  );
78
78
  }
79
- if (!definition.canUpdate) {
80
- throw new Error(
81
- "Only the automation's creator or an organization admin can update it.",
82
- );
83
- }
84
79
  if (definition.meta.triggerType === "schedule" && !isValidCron(schedule)) {
85
80
  throw new Error(`Invalid cron expression "${schedule}".`);
86
81
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-24
4
+ ---
5
+
6
+ Slack checking now works for factories created by someone other than the deploy owner.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-24
4
+ ---
5
+
6
+ Workspace teammates can edit and run Factory jobs, not only the person who created them.
@@ -352,6 +352,14 @@ export function resolveAutomationDisplayName(
352
352
  return readAutomationDisplayName(content) ?? automationName;
353
353
  }
354
354
 
355
+ export function assignCreatedByIfMissing(
356
+ content: string,
357
+ createdBy: string,
358
+ ): string {
359
+ if (readFrontmatterField(content, "createdBy")) return content;
360
+ return setAutomationFrontmatterField(content, "createdBy", createdBy);
361
+ }
362
+
355
363
  export function setAutomationFrontmatterField(
356
364
  content: string,
357
365
  key: string,
@@ -25,12 +25,6 @@ const FACTORY_AUTOMATION_NAMES = {
25
25
 
26
26
  export type FactoryAutomationRole = keyof typeof FACTORY_AUTOMATION_NAMES;
27
27
 
28
- function workspaceOwnerEmail(): string | undefined {
29
- const email = process.env.WORKSPACE_OWNER_EMAIL?.trim().toLowerCase(); // guard:allow-env-credential - deployment owner identity, not a user credential
30
- if (!email || /[\r\n]/.test(email)) return undefined;
31
- return email;
32
- }
33
-
34
28
  export async function requireFactoryAutomation(
35
29
  context: ActionRunContext | undefined,
36
30
  identity: Pick<WorkspaceMemberIdentity, "userEmail" | "orgId">,
@@ -61,15 +55,13 @@ export async function requireFactoryAutomation(
61
55
  "organization",
62
56
  )
63
57
  ).find((entry) => entry.resource.id === lineage.triggerId);
64
- const ownerEmail = workspaceOwnerEmail();
65
58
  if (
66
59
  !definition ||
67
60
  definition.name !== lineage.triggerName ||
68
61
  definition.meta.domain !== "factory" ||
69
62
  definition.meta.orgId !== identity.orgId ||
70
63
  definition.meta.runAs !== "creator" ||
71
- !ownerEmail ||
72
- definition.meta.createdBy?.trim().toLowerCase() !== ownerEmail
64
+ !definition.meta.createdBy?.trim()
73
65
  ) {
74
66
  throw new Error(
75
67
  "The action was not invoked by a governed Factory automation.",
@@ -21,6 +21,7 @@ import { triageConfig } from "../db/schema.js";
21
21
  import { repairFactoryAutomationsFromConfig } from "../lib/factory-automation-repair.js";
22
22
  import {
23
23
  DEFAULT_FACTORY_ID,
24
+ assignCreatedByIfMissing,
24
25
  factoryAutomationJobPath,
25
26
  factoryConfigRowId,
26
27
  patchAutomationResource,
@@ -541,7 +542,7 @@ export async function ensureFactoryAutomations(
541
542
  repaired = setFrontmatterField(repaired, "appId", "factory");
542
543
  repaired = setFrontmatterField(repaired, "orgId", orgId);
543
544
  repaired = setFrontmatterField(repaired, "factoryId", factoryId);
544
- repaired = setFrontmatterField(repaired, "createdBy", ownerEmail);
545
+ repaired = assignCreatedByIfMissing(repaired, ownerEmail);
545
546
  repaired = setFrontmatterField(repaired, "runAs", "creator");
546
547
  if (!frontmatterField(repaired, "model")) {
547
548
  repaired = setFrontmatterField(repaired, "model", seed.model);
@@ -67,6 +67,12 @@ export interface UpdateAutomationInput {
67
67
  * automation classifier.
68
68
  */
69
69
  export declare function canUpdateAutomationResource(actorInput: AutomationActor, resource: Resource): Promise<boolean>;
70
+ /**
71
+ * Factory is a shared team workspace: any current org member may queue Run now
72
+ * for that app's Factory-domain org jobs. Mail/CRM and other automations stay
73
+ * on creator-or-admin `canUpdate`.
74
+ */
75
+ export declare function canQueueAutomationRunNow(actorInput: AutomationActor, resource: Resource, scope: AutomationScope): Promise<boolean>;
70
76
  export declare function listAutomationDefinitions(actorInput: AutomationActor, scope: AutomationScope): Promise<AutomationDefinition[]>;
71
77
  export declare function defineAutomation(actorInput: AutomationActor, input: DefineAutomationInput): Promise<DefinedAutomation>;
72
78
  export declare function updateAutomation(actorInput: AutomationActor, input: UpdateAutomationInput): Promise<AutomationDefinition>;
@@ -108,6 +108,26 @@ export async function canUpdateAutomationResource(actorInput, resource) {
108
108
  return false;
109
109
  return (await mutationAccess(actor, resource, meta)).canUpdate;
110
110
  }
111
+ /**
112
+ * Factory is a shared team workspace: any current org member may queue Run now
113
+ * for that app's Factory-domain org jobs. Mail/CRM and other automations stay
114
+ * on creator-or-admin `canUpdate`.
115
+ */
116
+ export async function canQueueAutomationRunNow(actorInput, resource, scope) {
117
+ const { meta } = parseJobResource(resource.content);
118
+ const actor = normalizeActor(actorInput);
119
+ if (!jobBelongsToApp(meta, actor.appId))
120
+ return false;
121
+ const access = await mutationAccess(actor, resource, meta);
122
+ if (access.canUpdate)
123
+ return true;
124
+ const resourceOrgId = organizationIdFromResourceOwner(resource.owner);
125
+ return (scope === "organization" &&
126
+ Boolean(resourceOrgId) &&
127
+ actor.orgId === resourceOrgId &&
128
+ actor.appId === "factory" &&
129
+ meta.domain === "factory");
130
+ }
111
131
  function assertExplicitAutomation(resource) {
112
132
  const parsed = parseJobResource(resource.content);
113
133
  if (parsed.classification.kind !== "automation") {
@@ -1458,7 +1458,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1458
1458
  // Cleared on the next message send.
1459
1459
  const [composerError, setComposerError] = useState(null);
1460
1460
  const [composerText, setComposerText] = useState("");
1461
- const composerDraftScope = threadId || tabId;
1461
+ const composerDraftScope = tabId || threadId;
1462
1462
  const initialComposerText = useMemo(() => readAssistantChatComposerDraft(composerDraftScope), [composerDraftScope]);
1463
1463
  const handleComposerTextChange = useCallback((text) => {
1464
1464
  setComposerText(text);
@@ -12,6 +12,7 @@ import { AgentWorkspaceContent } from "../agent-page/AgentWorkspaceContent.js";
12
12
  import { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
13
13
  import { agentNativePath } from "../api-path.js";
14
14
  import { BuilderBMark } from "../builder-mark.js";
15
+ import { fetchAgentEngineStatus, fetchEnvironmentStatus, } from "../client-status-requests.js";
15
16
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
16
17
  import { Tooltip, TooltipContent, TooltipTrigger, } from "../components/ui/tooltip.js";
17
18
  import { useT } from "../i18n.js";
@@ -332,7 +333,7 @@ const PROVIDER_DOCS = {
332
333
  "ai-sdk:mistral": "https://console.mistral.ai/api-keys/",
333
334
  "ai-sdk:cohere": "https://dashboard.cohere.com/api-keys",
334
335
  };
335
- function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, orgName, envManaged, credentialSource, grouped = false, open, onToggle, }) {
336
+ function LLMSectionInner({ builderFlow, builderLoading, builderStatusAvailable, connectUrl, connected, orgName, envManaged, credentialSource, grouped = false, open, onToggle, }) {
336
337
  const isPage = useSettingsSurface() === "page";
337
338
  const t = useT();
338
339
  const [envKeys, setEnvKeys] = useState([]);
@@ -354,24 +355,40 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
354
355
  const [testResult, setTestResult] = useState(null);
355
356
  const [settingsStatus, setSettingsStatus] = useState(null);
356
357
  const [disconnectError, setDisconnectError] = useState(null);
357
- const [envLoaded, setEnvLoaded] = useState(false);
358
+ const [envProbeAvailable, setEnvProbeAvailable] = useState(false);
358
359
  const [enginesLoaded, setEnginesLoaded] = useState(false);
359
- const [statusLoaded, setStatusLoaded] = useState(false);
360
- const initialLoading = !envLoaded || !enginesLoaded || !statusLoaded || !!builderLoading;
361
- useEffect(() => {
362
- fetch(agentNativePath("/_agent-native/env-status"))
363
- .then((r) => (r.ok ? r.json() : []))
364
- .then(setEnvKeys)
365
- .catch(() => { })
366
- .finally(() => setEnvLoaded(true));
367
- }, [saved]);
360
+ const [statusProbeAvailable, setStatusProbeAvailable] = useState(false);
361
+ const probeGenerationRef = useRef({ env: 0, status: 0 });
362
+ const initialLoading = !enginesLoaded || !!builderLoading;
363
+ const refreshEnvKeys = useCallback(() => {
364
+ const generation = ++probeGenerationRef.current.env;
365
+ setEnvProbeAvailable(false);
366
+ setEnvKeys([]);
367
+ void fetchEnvironmentStatus().then((result) => {
368
+ if (generation !== probeGenerationRef.current.env)
369
+ return;
370
+ if (result.state !== "available" || !Array.isArray(result.value)) {
371
+ return;
372
+ }
373
+ setEnvKeys(result.value);
374
+ setEnvProbeAvailable(true);
375
+ });
376
+ }, []);
368
377
  const notifyConfigChanged = useCallback(() => {
369
378
  window.dispatchEvent(new CustomEvent("agent-engine:configured-changed"));
370
379
  }, []);
371
380
  const refreshSettingsStatus = useCallback(() => {
372
- fetch(agentNativePath("/_agent-native/agent-engine/status"))
373
- .then((r) => (r.ok ? r.json() : null))
374
- .then((data) => {
381
+ const generation = ++probeGenerationRef.current.status;
382
+ setStatusProbeAvailable(false);
383
+ setSettingsStatus(null);
384
+ setBaseUrlConfigured(false);
385
+ void fetchAgentEngineStatus()
386
+ .then((result) => {
387
+ if (generation !== probeGenerationRef.current.status)
388
+ return;
389
+ if (result.state !== "available")
390
+ return;
391
+ const data = result.value;
375
392
  setBaseUrlConfigured(Boolean(data?.openAiBaseUrlConfigured));
376
393
  if (data?.configured &&
377
394
  typeof data.engine === "string" &&
@@ -387,13 +404,34 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
387
404
  else {
388
405
  setSettingsStatus(null);
389
406
  }
407
+ setStatusProbeAvailable(true);
390
408
  })
391
- .catch(() => { })
392
- .finally(() => setStatusLoaded(true));
409
+ .catch(() => { });
393
410
  }, []);
411
+ useEffect(() => {
412
+ refreshEnvKeys();
413
+ }, [refreshEnvKeys]);
394
414
  useEffect(() => {
395
415
  refreshSettingsStatus();
396
416
  }, [refreshSettingsStatus]);
417
+ useEffect(() => {
418
+ const refresh = () => {
419
+ refreshEnvKeys();
420
+ refreshSettingsStatus();
421
+ };
422
+ window.addEventListener("agent-engine:configured-changed", refresh);
423
+ window.addEventListener("focus", refresh);
424
+ const onVisibilityChange = () => {
425
+ if (document.visibilityState === "visible")
426
+ refresh();
427
+ };
428
+ document.addEventListener("visibilitychange", onVisibilityChange);
429
+ return () => {
430
+ window.removeEventListener("agent-engine:configured-changed", refresh);
431
+ window.removeEventListener("focus", refresh);
432
+ document.removeEventListener("visibilitychange", onVisibilityChange);
433
+ };
434
+ }, [refreshEnvKeys, refreshSettingsStatus]);
397
435
  useEffect(() => {
398
436
  callAction("manage-agent-engine", { action: "list" })
399
437
  .then((data) => {
@@ -441,14 +479,21 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
441
479
  }
442
480
  return configured;
443
481
  }, [envKeys, settingsStatus]);
444
- const builderConnected = connected || builderFlow.configured;
445
- const anyKeyConfigured = builderConnected ||
446
- (selectedEnginePackageInstalled &&
447
- (envConfigured || settingsConfigured || configuredProviderIds.size > 0));
482
+ const builderConnected = builderStatusAvailable && (connected || builderFlow.configured);
483
+ const configurationKnown = envProbeAvailable && statusProbeAvailable;
484
+ const builderEngineSelected = selectedEngine === "builder";
485
+ const selectedConfigurationKnown = builderEngineSelected
486
+ ? builderStatusAvailable
487
+ : configurationKnown;
488
+ const anyKeyConfigured = (builderEngineSelected && builderConnected) ||
489
+ (!builderEngineSelected &&
490
+ configurationKnown &&
491
+ selectedEnginePackageInstalled &&
492
+ (envConfigured || settingsConfigured));
448
493
  const sourceBadge = computeSourceBadge({
449
- settingsConfigured,
450
- settingsStatus,
451
- envConfigured,
494
+ settingsConfigured: configurationKnown && settingsConfigured,
495
+ settingsStatus: configurationKnown ? settingsStatus : null,
496
+ envConfigured: configurationKnown && envConfigured,
452
497
  envVar,
453
498
  builderConnected,
454
499
  });
@@ -479,7 +524,6 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
479
524
  setBaseUrlConfigured(true);
480
525
  if (clearBaseUrl)
481
526
  setBaseUrlConfigured(false);
482
- refreshSettingsStatus();
483
527
  notifyConfigChanged();
484
528
  setTimeout(() => setSaved(false), 2000);
485
529
  }
@@ -496,7 +540,6 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
496
540
  if (res.ok) {
497
541
  setTestResult(null);
498
542
  setApplyNote(false);
499
- refreshSettingsStatus();
500
543
  notifyConfigChanged();
501
544
  return;
502
545
  }
@@ -560,7 +603,6 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
560
603
  setCurrentEngine(selectedEngine);
561
604
  setCurrentModel(selectedModel);
562
605
  setApplyNote(true);
563
- refreshSettingsStatus();
564
606
  notifyConfigChanged();
565
607
  setTimeout(() => setApplyNote(false), 4000);
566
608
  }
@@ -568,7 +610,9 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
568
610
  setApplyError(err instanceof Error ? err.message : String(err));
569
611
  }
570
612
  };
571
- return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", required: true, connected: initialLoading ? undefined : anyKeyConfigured, subtitle: isPage
613
+ return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", required: true, connected: initialLoading || !selectedConfigurationKnown
614
+ ? undefined
615
+ : anyKeyConfigured, subtitle: isPage
572
616
  ? undefined
573
617
  : t("agentPanel.builderOrOwnKeys", {
574
618
  defaultValue: "Choose Builder.io or custom keys.",
@@ -576,11 +620,11 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
576
620
  ? "flex items-center justify-between gap-4 px-5 py-4 sm:px-6"
577
621
  : "flex items-center justify-between gap-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "AI provider" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: t("agentPanel.builderOrOwnKeys", {
578
622
  defaultValue: "Choose Builder.io or custom keys.",
579
- }) })] })), _jsxs("div", { className: cn("flex flex-wrap items-center justify-end gap-2"), children: [!anyKeyConfigured && (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: true })), _jsx(ManualSetupCard, { id: "llm-manual-setup", title: "Custom keys", hint: manualSetupHint, sourceBadge: builderConnected ? undefined : sourceBadge, bare: isPage, popover: true, popoverLabel: settingsConfigured
623
+ }) })] })), _jsxs("div", { className: cn("flex flex-wrap items-center justify-end gap-2"), children: [selectedConfigurationKnown && !anyKeyConfigured && (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: builderConnected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: true })), _jsx(ManualSetupCard, { id: "llm-manual-setup", title: "Custom keys", hint: manualSetupHint, sourceBadge: builderConnected ? undefined : sourceBadge, bare: isPage, popover: true, popoverLabel: settingsConfigured
580
624
  ? "Manage"
581
625
  : t("agentPanel.addOwnKeys", {
582
626
  defaultValue: "Custom keys",
583
- }), summaryContent: anyKeyConfigured ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io" })) : undefined, children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, configuredProviders: configuredProviderIds, layout: isPage ? "page" : "compact", onChange: (provider) => {
627
+ }), summaryContent: selectedConfigurationKnown && anyKeyConfigured ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: builderConnected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io" })) : undefined, children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, configuredProviders: configurationKnown ? configuredProviderIds : undefined, layout: isPage ? "page" : "compact", onChange: (provider) => {
584
628
  const option = getAgentProviderOption(provider);
585
629
  setSelectedEngine(option.engine);
586
630
  setSelectedModel(option.defaultModel);
@@ -611,7 +655,9 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
611
655
  : `${envVar} configured`] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
612
656
  if (e.key === "Enter")
613
657
  handleSave();
614
- }, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Apply failed: ", applyError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })] })] })) }));
658
+ }, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing ||
659
+ !selectedConfigurationKnown ||
660
+ !anyKeyConfigured, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Apply failed: ", applyError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })] })] })) }));
615
661
  }
616
662
  function friendlyAppName(appId) {
617
663
  return appId
@@ -1056,10 +1102,11 @@ function CapabilityStatusStrip({ isDevMode, builderConnected, builderLoading, bu
1056
1102
  return (_jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-2.5 py-2", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-medium text-muted-foreground", children: "Available now" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(CapabilityStatusRow, { label: "App", value: "Chat + actions", active: true }), _jsx(CapabilityStatusRow, { label: "Code", value: codeLabel, active: codeAvailable }), _jsx(CapabilityStatusRow, { label: "Builder", active: builderConnected, value: builderLoading ? (_jsx(Skeleton, { className: "h-3 w-16" })) : builderConnected ? ("Connected") : (_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: onOpenLlm, className: "rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground", children: "Connect" })) })] })] }));
1057
1103
  }
1058
1104
  function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, sections = ALL_SETTINGS_SECTIONS, showCapabilityStrip = true, className, surface = "sidebar", builderConnectionOwnedExternally = false, agentAdditionalContent, }) {
1059
- const { status: builder, loading: builderLoading } = useBuilderStatus({
1105
+ const { status: builder, loading: builderLoading, stale: builderStatusStale, } = useBuilderStatus({
1060
1106
  enabled: !builderConnectionOwnedExternally,
1061
1107
  });
1062
1108
  const connected = builder?.configured ?? false;
1109
+ const builderStatusAvailable = !builderLoading && !builderStatusStale && builder != null;
1063
1110
  const connectUrl = builder?.connectUrl;
1064
1111
  const orgName = builder?.orgName;
1065
1112
  const envManaged = !!builder?.envManaged;
@@ -1088,7 +1135,7 @@ function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, se
1088
1135
  const isPage = surface === "page";
1089
1136
  const isWorkspacePage = isPage && sections.includes("hosting");
1090
1137
  return (_jsx(SettingsSurfaceProvider, { surface: surface, children: _jsxs("div", { className: cn(isPage ? "space-y-8" : "flex-1 min-h-0 overflow-y-auto p-3 space-y-2", className), style: isPage ? undefined : { overflowY: "auto" }, children: [showCapabilityStrip && (_jsx(CapabilityStatusStrip, { isDevMode: isDevMode, builderConnected: connected, builderLoading: builderLoading, builderBranchesAvailable: builderBranchesAvailable, onOpenLlm: () => openSettingsSection("llm", true) })), isPage &&
1091
- ["llm", "app-models", "limits", "voice"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent", children: [shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, grouped: true, open: openSection === "llm", onToggle: () => toggle("llm") })), shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { grouped: true, open: openSection === "app-models", onToggle: () => toggle("app-models") })), shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { grouped: true, open: openSection === "limits", onToggle: () => toggle("limits") })), shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "Choose how voice input is transcribed.", grouped: true, flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, { compact: true }) }))] })), isPage && agentAdditionalContent ? (_jsx(SettingsGroup, { title: "Notifications", children: agentAdditionalContent })) : null, isPage &&
1138
+ ["llm", "app-models", "limits", "voice"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent", children: [shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, builderStatusAvailable: builderStatusAvailable, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, grouped: true, open: openSection === "llm", onToggle: () => toggle("llm") })), shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { grouped: true, open: openSection === "app-models", onToggle: () => toggle("app-models") })), shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { grouped: true, open: openSection === "limits", onToggle: () => toggle("limits") })), shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "Choose how voice input is transcribed.", grouped: true, flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, { compact: true }) }))] })), isPage && agentAdditionalContent ? (_jsx(SettingsGroup, { title: "Notifications", children: agentAdditionalContent })) : null, isPage &&
1092
1139
  ["automations", "background"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent workflows", children: [shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", grouped: true, flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(SettingsRow, { label: "Automations", description: "Schedule agent tasks or run them from events.", control: _jsxs("a", { href: "/settings/agent/automations", className: "inline-flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground no-underline transition-colors hover:bg-accent/40", children: ["Open automations", _jsx(IconExternalLink, { size: 14 })] }) }) })), shouldShowSection("background") && (_jsx(SettingsSection, { id: settingsSectionDomId("background"), icon: _jsx(IconGitBranch, { size: 14 }), title: "Background Agent", subtitle: "Make code changes from production mode via Builder.", grouped: true, flat: true, connected: connected, open: openSection === "background", onToggle: () => toggle("background"), children: _jsx(SettingsRow, { label: "Background agent", description: "Make code changes from production mode via Builder.", control: _jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "background_agent_settings", trackingFlow: "background_agent", compact: true }) }) }))] })), isWorkspacePage && (_jsxs(SettingsGroup, { title: "Workspace", children: [shouldShowSection("demo-mode") && (_jsx(SettingsRow, { id: settingsSectionDomId("demo-mode"), label: "Demo mode", description: "Use sample data in this browser for presentations.", control: _jsx(DemoModeSection, { compact: true }) })), shouldShowSection("hosting") && (_jsx(SettingsRow, { id: settingsSectionDomId("hosting"), label: "Hosting", description: "Deploy the app to the cloud.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [!connected && (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting", compact: true })), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: docsUrl("deployment", {
1093
1140
  campaign: "onboarding",
1094
1141
  content: "deployment_settings",
@@ -1101,7 +1148,7 @@ function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, se
1101
1148
  }), dim: connected, bare: true, popover: true, popoverLabel: "Manage", summaryContent: connected ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "file_upload_settings", trackingFlow: "file_upload" })) : undefined, children: _jsx(FileStorageSettingsForm, {}) })] }) })), shouldShowSection("auth") && (_jsx(SettingsRow, { id: settingsSectionDomId("auth"), label: "Authentication", description: "Set up sign-in and access control.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [!connected && (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth", compact: true })), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Configure Better Auth and optional Google or GitHub providers.", docsUrl: docsUrl("authentication", {
1102
1149
  campaign: "onboarding",
1103
1150
  content: "authentication_settings",
1104
- }), dim: connected, bare: true, popover: true, popoverLabel: "Manage", summaryContent: connected ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth" })) : undefined })] }) }))] })), !isPage && shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, open: openSection === "llm", onToggle: () => toggle("llm") })), !isPage && shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { open: openSection === "app-models", onToggle: () => toggle("app-models") })), !isPage && shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { open: openSection === "limits", onToggle: () => toggle("limits") })), !isPage && shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "How the composer microphone turns your voice into text.", flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, {}) })), !isPage && shouldShowSection("demo-mode") && (_jsx(SettingsSection, { id: settingsSectionDomId("demo-mode"), icon: _jsx(IconEyeOff, { size: 14 }), title: "Demo mode", subtitle: "Replace displayed emails with realistic fake data in this browser and reshape supported charts for presentations. Backend, agent integrations, and agent results stay real and access-scoped.", flat: true, open: openSection === "demo-mode", onToggle: () => toggle("demo-mode"), children: _jsx(DemoModeSection, {}) })), !isPage && shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(AutomationsSection, {}) })), shouldShowSection("secrets") && (_jsx(SettingsSection, { id: settingsSectionDomId("secrets"), icon: _jsx(IconKey, { size: 14 }), title: "API keys", subtitle: "Service credentials and automation keys.", flat: true, open: openSection === "secrets", onToggle: () => toggle("secrets"), children: _jsx(SecretsSection, { focusKey: focusSecretKey }) })), !isPage && shouldShowSection("hosting") && (_jsx(SettingsSection, { id: settingsSectionDomId("hosting"), icon: _jsx(IconCloud, { size: 14 }), title: "Hosting", subtitle: "Deploy your app to the cloud.", flat: true, connected: connected, open: openSection === "hosting", onToggle: () => toggle("hosting"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting" }), _jsx(ManualSetupCard, { hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: docsUrl("deployment", {
1151
+ }), dim: connected, bare: true, popover: true, popoverLabel: "Manage", summaryContent: connected ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth" })) : undefined })] }) }))] })), !isPage && shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, builderStatusAvailable: builderStatusAvailable, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, open: openSection === "llm", onToggle: () => toggle("llm") })), !isPage && shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { open: openSection === "app-models", onToggle: () => toggle("app-models") })), !isPage && shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { open: openSection === "limits", onToggle: () => toggle("limits") })), !isPage && shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "How the composer microphone turns your voice into text.", flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, {}) })), !isPage && shouldShowSection("demo-mode") && (_jsx(SettingsSection, { id: settingsSectionDomId("demo-mode"), icon: _jsx(IconEyeOff, { size: 14 }), title: "Demo mode", subtitle: "Replace displayed emails with realistic fake data in this browser and reshape supported charts for presentations. Backend, agent integrations, and agent results stay real and access-scoped.", flat: true, open: openSection === "demo-mode", onToggle: () => toggle("demo-mode"), children: _jsx(DemoModeSection, {}) })), !isPage && shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(AutomationsSection, {}) })), shouldShowSection("secrets") && (_jsx(SettingsSection, { id: settingsSectionDomId("secrets"), icon: _jsx(IconKey, { size: 14 }), title: "API keys", subtitle: "Service credentials and automation keys.", flat: true, open: openSection === "secrets", onToggle: () => toggle("secrets"), children: _jsx(SecretsSection, { focusKey: focusSecretKey }) })), !isPage && shouldShowSection("hosting") && (_jsx(SettingsSection, { id: settingsSectionDomId("hosting"), icon: _jsx(IconCloud, { size: 14 }), title: "Hosting", subtitle: "Deploy your app to the cloud.", flat: true, connected: connected, open: openSection === "hosting", onToggle: () => toggle("hosting"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting" }), _jsx(ManualSetupCard, { hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: docsUrl("deployment", {
1105
1152
  campaign: "onboarding",
1106
1153
  content: "deployment_settings",
1107
1154
  }), dim: connected })] }) })), !isPage && shouldShowSection("database") && (_jsx(SettingsSection, { id: settingsSectionDomId("database"), icon: _jsx(IconDatabase, { size: 14 }), title: "Database", subtitle: "Connect a cloud database for persistent storage.", flat: true, connected: connected, open: openSection === "database", onToggle: () => toggle("database"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "database_settings", trackingFlow: "database" }), _jsx(ManualSetupCard, { hint: "Set DATABASE_URL in your .env to connect Neon, Supabase, Turso, any Postgres/SQLite database, or local PGlite with pglite:./data/pglite.", docsUrl: docsUrl("database", {