@xenosystem/agent-interface-ui 0.1.21 → 0.1.23

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 (63) hide show
  1. package/dist/adapters/agentHostPlatformBridge.d.ts.map +1 -1
  2. package/dist/adapters/agentHostPlatformBridge.js +131 -0
  3. package/dist/adapters/agentHostPlatformBridge.js.map +1 -1
  4. package/dist/adapters/hubAgentPlatformBridge.d.ts.map +1 -1
  5. package/dist/adapters/hubAgentPlatformBridge.js +9 -0
  6. package/dist/adapters/hubAgentPlatformBridge.js.map +1 -1
  7. package/dist/components/agent/AgentInterface.d.ts.map +1 -1
  8. package/dist/components/agent/AgentInterface.js +247 -32
  9. package/dist/components/agent/AgentInterface.js.map +1 -1
  10. package/dist/components/agent/AgentPtyTerminalPanel.d.ts.map +1 -1
  11. package/dist/components/agent/AgentPtyTerminalPanel.js +66 -21
  12. package/dist/components/agent/AgentPtyTerminalPanel.js.map +1 -1
  13. package/dist/components/agent/AgentView.d.ts +5 -0
  14. package/dist/components/agent/AgentView.d.ts.map +1 -1
  15. package/dist/components/agent/AgentView.js +243 -37
  16. package/dist/components/agent/AgentView.js.map +1 -1
  17. package/dist/components/agent/nativeTuiComposerInput.d.ts +42 -0
  18. package/dist/components/agent/nativeTuiComposerInput.d.ts.map +1 -0
  19. package/dist/components/agent/nativeTuiComposerInput.js +51 -0
  20. package/dist/components/agent/nativeTuiComposerInput.js.map +1 -0
  21. package/dist/components/agent/ptyOutputReconciliation.d.ts +41 -0
  22. package/dist/components/agent/ptyOutputReconciliation.d.ts.map +1 -0
  23. package/dist/components/agent/ptyOutputReconciliation.js +68 -0
  24. package/dist/components/agent/ptyOutputReconciliation.js.map +1 -0
  25. package/dist/components/agent/settings/sections/DefaultAgentConfigSection.d.ts.map +1 -1
  26. package/dist/components/agent/settings/sections/DefaultAgentConfigSection.js +3 -2
  27. package/dist/components/agent/settings/sections/DefaultAgentConfigSection.js.map +1 -1
  28. package/dist/components/agent/settings/sections/WorkspaceDirectoryAccessSection.js +1 -1
  29. package/dist/components/agent/settings/sections/WorkspaceDirectoryAccessSection.js.map +1 -1
  30. package/dist/components/agent/terminalSurfaceAvailability.d.ts +22 -0
  31. package/dist/components/agent/terminalSurfaceAvailability.d.ts.map +1 -0
  32. package/dist/components/agent/terminalSurfaceAvailability.js +43 -0
  33. package/dist/components/agent/terminalSurfaceAvailability.js.map +1 -0
  34. package/dist/components/agent/webJobProgressView.d.ts +90 -0
  35. package/dist/components/agent/webJobProgressView.d.ts.map +1 -0
  36. package/dist/components/agent/webJobProgressView.js +104 -0
  37. package/dist/components/agent/webJobProgressView.js.map +1 -0
  38. package/dist/platformApiTypes.d.ts +32 -0
  39. package/dist/platformApiTypes.d.ts.map +1 -1
  40. package/dist/platformBridge.d.ts +9 -0
  41. package/dist/platformBridge.d.ts.map +1 -1
  42. package/dist/platformBridge.js +11 -0
  43. package/dist/platformBridge.js.map +1 -1
  44. package/dist/stores/agentChatStore.d.ts +11 -1
  45. package/dist/stores/agentChatStore.d.ts.map +1 -1
  46. package/dist/stores/agentChatStore.js +103 -6
  47. package/dist/stores/agentChatStore.js.map +1 -1
  48. package/dist/stores/xenoChatStore.d.ts.map +1 -1
  49. package/dist/stores/xenoChatStore.js +2 -1
  50. package/dist/stores/xenoChatStore.js.map +1 -1
  51. package/dist/utils/acpExecutionAuthority.d.ts +8 -0
  52. package/dist/utils/acpExecutionAuthority.d.ts.map +1 -1
  53. package/dist/utils/acpExecutionAuthority.js +24 -0
  54. package/dist/utils/acpExecutionAuthority.js.map +1 -1
  55. package/dist/utils/modelAvailability.d.ts +11 -0
  56. package/dist/utils/modelAvailability.d.ts.map +1 -0
  57. package/dist/utils/modelAvailability.js +10 -0
  58. package/dist/utils/modelAvailability.js.map +1 -0
  59. package/dist/utils/providerAvailability.d.ts +8 -0
  60. package/dist/utils/providerAvailability.d.ts.map +1 -0
  61. package/dist/utils/providerAvailability.js +13 -0
  62. package/dist/utils/providerAvailability.js.map +1 -0
  63. package/package.json +5 -5
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { getAgentPlatformBridge } from '../../platformBridge.js';
2
+ import { getAgentPlatformBridge, getOptionalAgentPlatformBridge } from '../../platformBridge.js';
3
3
  /*
4
4
  * AgentView.tsx - Active Agent Chat Interface
5
5
  *
@@ -23,7 +23,10 @@ import { AIStatusIndicator } from './AIStatusIndicator.js';
23
23
  import { CommandToolCard } from './CommandToolCard.js';
24
24
  import { AssistantTurnErrorNotice, AssistantTurnStatusNotice } from './AssistantTurnStatusNotice.js';
25
25
  import { commandPreviewText, compactCommandPreview, deriveToolInteractionState, hideDuplicateToolTitle, isCommandToolCall } from './toolCardDisplay.js';
26
+ import { deriveWebJobProgressView, webJobStopAvailability, WEB_JOB_STOP_LABEL, WEB_JOB_STOP_TITLE } from './webJobProgressView.js';
26
27
  import { cloudModelName, cloudModelSortScore } from '../../utils/cloudModels.js';
28
+ import { isSelectableLiveModel } from '../../utils/modelAvailability.js';
29
+ import { availableAcpConfigIds } from '../../utils/providerAvailability.js';
27
30
  import { extractWindowsFilePathsFromToolResult, prepareAgentMarkdown, resolveMarkdownLocalFilePath, } from '../../utils/markdownWindowsPaths.js';
28
31
  import { agentReasoningEffortLabel, buildExplicitAgentReasoningEffortFamilies, indexAgentReasoningEffortFamilies, preferredAgentReasoningModelId, resolveAgentReasoningEffortFamily, } from '../../utils/agentReasoningEffort.js';
29
32
  import { acpAgentDisplayName, resolveHubAgentRuntimeProvider } from '../../utils/agentRuntimeProvider.js';
@@ -31,6 +34,7 @@ import { createConversationRuntimeBinding, reconcileCloudConversationModel, runt
31
34
  import { extractUserFileCreateRequest, extractUserFileEditRequest, isAbsoluteOrRootedPath } from '../../utils/fileCreateIntent.js';
32
35
  import { buildRuntimeAttachedContextPrompt } from '../../utils/agentAttachedContextPrompt.js';
33
36
  import { buildWorkspaceSelectionContinuationPrompt } from '../../utils/workspaceSelectionContinuation.js';
37
+ import { isAgentComposerInputLocked } from './nativeTuiComposerInput.js';
34
38
  import { AgentProviderSelectorController } from '../../providerSelector.js';
35
39
  import { loadHubAgentProviderCatalog, refreshHubAgentProviderCatalog, subscribeHubAgentProviderCatalog, } from '../../adapters/xenoAgentHostCatalog.js';
36
40
  const AGENT_GPT55_DEFAULT_MODEL = 'gpt-5.5-high';
@@ -2454,6 +2458,40 @@ function WebContextEvidenceCard({ toolCall, expanded }) {
2454
2458
  }
2455
2459
  return (_jsxs("div", { "data-web-context-evidence": evidence.evidenceId, className: "ml-3 mt-0.5 max-w-[calc(100%-12px)] overflow-hidden rounded-[3px] border border-sky-300/[0.08] bg-sky-300/[0.025]", children: [_jsxs("div", { className: "flex min-h-[24px] items-center gap-2 px-2 text-[10px]", children: [_jsx("span", { className: "uppercase tracking-[0.14em] text-sky-200/38", children: "Evidence" }), _jsx("span", { className: "min-w-0 flex-1 truncate text-white/42", title: evidence.finalUrl || evidence.sourceUrl, children: sourceLabel }), _jsxs("span", { className: "flex-shrink-0 text-white/24", children: [evidence.citations.length, " ", evidence.citations.length === 1 ? 'source' : 'sources'] }), projection.job && (_jsx("span", { className: "flex-shrink-0 uppercase tracking-[0.12em] text-white/20", children: projection.job.state }))] }), expanded && (_jsxs("div", { className: "border-t border-white/[0.04] px-2 py-1.5", children: [_jsx("div", { className: "truncate font-mono text-[9px] text-white/20", title: evidence.evidenceId, children: evidence.evidenceId }), visibleCitations.length > 0 && (_jsxs("div", { className: "mt-1 space-y-1", children: [visibleCitations.map((citation, index) => (_jsx("a", { href: citation.url, target: "_blank", rel: "noreferrer", className: "block min-w-0 truncate text-[10px] text-sky-200/45 hover:text-sky-100/70", title: citation.url, children: citation.title || citation.url }, `${citation.url}:${index}`))), evidence.citations.length > visibleCitations.length && (_jsxs("div", { className: "text-[9px] text-white/20", children: ["+", evidence.citations.length - visibleCitations.length, " more sources"] }))] }))] }))] }));
2456
2460
  }
2461
+ /**
2462
+ * Live progress for a durable XENO Web Context job, rendered inside the tool card
2463
+ * that owns it rather than in a surface of its own.
2464
+ *
2465
+ * It sits beside `WebContextEvidenceCard`, not inside it: progress is a live
2466
+ * projection that arrives many times while a tool runs, evidence is the terminal
2467
+ * artefact that arrives once. A tool call may carry either, both, or neither, and
2468
+ * a tool that reports no progress renders exactly what it rendered before this
2469
+ * existed.
2470
+ */
2471
+ function WebContextJobProgressRow({ toolCall, onStop, }) {
2472
+ const progress = toolCall.webJobProgress;
2473
+ if (!progress)
2474
+ return null;
2475
+ const view = deriveWebJobProgressView(progress, toolCall.status);
2476
+ // A stale row is a record, not a live one: nothing is still moving, so nothing pulses.
2477
+ const indeterminate = view.percent === null && !view.stale;
2478
+ return (_jsx("div", { "data-web-job-progress": view.jobId, "data-web-job-state": progress.state, "data-web-job-stale": view.stale ? 'true' : 'false', className: "ml-3 mt-0.5 max-w-[calc(100%-12px)] overflow-hidden rounded-[3px] border border-white/[0.06] bg-white/[0.02]", children: _jsxs("div", { className: "flex min-h-[24px] items-center gap-2 px-2 text-[10px]", children: [_jsx("span", { className: "flex-shrink-0 uppercase tracking-[0.14em] text-white/32", children: view.operationLabel }), _jsx("span", { className: `flex-shrink-0 uppercase tracking-[0.12em] ${view.tone === 'failed'
2479
+ ? 'text-[#ff8d86]/70'
2480
+ : view.tone === 'cancelled' || view.tone === 'partial'
2481
+ ? 'text-white/30'
2482
+ : 'text-white/45'}`, children: view.stateLabel }), _jsx("div", { role: "progressbar", "aria-label": view.ariaLabel, "aria-valuetext": view.valueText, ...(view.percent === null
2483
+ ? {}
2484
+ : { 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': view.percent }), className: "h-[3px] w-[56px] flex-shrink-0 overflow-hidden rounded-[1px] bg-white/[0.07]", children: _jsx("div", { className: indeterminate
2485
+ ? // No percentage is known, so nothing may claim one. The pulse says
2486
+ // "moving"; under a reduced-motion preference it stops moving and
2487
+ // still reads as a partially filled track, and the counters text
2488
+ // carries the same fact for anyone who cannot see either.
2489
+ 'h-full w-1/3 bg-white/25 animate-pulse motion-reduce:animate-none'
2490
+ : 'h-full bg-white/30', ...(indeterminate ? {} : { style: { width: `${view.percent}%` } }) }) }), _jsxs("span", { className: "min-w-0 flex-1 truncate tabular-nums text-white/38", title: view.valueText, children: [view.countersText, view.terminalReason ? ` · ${view.terminalReason}` : ''] }), onStop && (_jsx("button", { type: "button", onClick: (event) => {
2491
+ event.stopPropagation();
2492
+ onStop();
2493
+ }, title: WEB_JOB_STOP_TITLE, "aria-label": WEB_JOB_STOP_TITLE, className: "h-[18px] flex-shrink-0 rounded-[2px] border border-white/[0.08] bg-white/[0.04] px-1.5 text-[9px] uppercase tracking-[0.14em] text-white/45 transition-colors hover:border-white/[0.16] hover:bg-white/[0.07] hover:text-white/75", children: WEB_JOB_STOP_LABEL }))] }) }));
2494
+ }
2457
2495
  function readToolStringParam(params, keys) {
2458
2496
  if (!params)
2459
2497
  return '';
@@ -2759,6 +2797,18 @@ export function ToolCallsBlock({ calls, requestId, onAddApprovalRule, onOpenEdit
2759
2797
  const [expandedFilePreviewId, setExpandedFilePreviewId] = useState(null);
2760
2798
  const resolvePermission = useAgentChatStore(s => s.resolvePermission);
2761
2799
  const grantConversationDirectory = useAgentChatStore(s => s.grantConversationDirectory);
2800
+ const abortActiveTurn = useAgentChatStore(s => s.abort);
2801
+ const activeRequestId = useAgentChatStore(s => s.activeRequestId);
2802
+ /**
2803
+ * Whether a cancellation path actually exists in THIS composition. The bridge is
2804
+ * injected by the consumer shell, so an embed that wires no runtime has none —
2805
+ * and offering a Stop button that resolves to nothing is the fabricated-success
2806
+ * failure mode, not a harmless no-op.
2807
+ */
2808
+ const optionalBridge = getOptionalAgentPlatformBridge();
2809
+ const hostExposesCancellation = !!(optionalBridge?.acp?.cancelAgentPrompt
2810
+ || optionalBridge?.agent?.cancel
2811
+ || optionalBridge?.llm?.abort);
2762
2812
  if (calls.length === 0)
2763
2813
  return null;
2764
2814
  const handleGrantAccess = (pr) => {
@@ -3016,7 +3066,13 @@ export function ToolCallsBlock({ calls, requestId, onAddApprovalRule, onOpenEdit
3016
3066
  setExpandedFilePreviewId(tc.id);
3017
3067
  }
3018
3068
  }
3019
- }, className: "flex-shrink-0", "aria-label": isExpanded ? 'Collapse tool details' : 'Expand tool details', children: _jsx(ChevronRight, { className: `w-3 h-3 text-white/15 transition-transform ${isExpanded ? 'rotate-90' : ''}` }) }))] })] }) })), tc.webContext && !isRunning && (_jsx(WebContextEvidenceCard, { toolCall: tc, expanded: isExpanded })), pr?.status === 'pending' && (_jsx(ToolApprovalCard, { toolCall: tc, permission: pr, canPersistAllowlist:
3069
+ }, className: "flex-shrink-0", "aria-label": isExpanded ? 'Collapse tool details' : 'Expand tool details', children: _jsx(ChevronRight, { className: `w-3 h-3 text-white/15 transition-transform ${isExpanded ? 'rotate-90' : ''}` }) }))] })] }) })), tc.webJobProgress && (_jsx(WebContextJobProgressRow, { toolCall: tc, onStop: webJobStopAvailability({
3070
+ progress: tc.webJobProgress,
3071
+ toolCallStatus: tc.status,
3072
+ requestId,
3073
+ activeRequestId,
3074
+ hostExposesCancellation,
3075
+ }).available ? abortActiveTurn : undefined })), tc.webContext && !isRunning && (_jsx(WebContextEvidenceCard, { toolCall: tc, expanded: isExpanded })), pr?.status === 'pending' && (_jsx(ToolApprovalCard, { toolCall: tc, permission: pr, canPersistAllowlist:
3020
3076
  // An ACP `execute` names no directory, and gating this on
3021
3077
  // `directory` alone hid the persist choice for every such
3022
3078
  // permission — which is why a user could not create a policy
@@ -3311,7 +3367,10 @@ export function AgentView(props) {
3311
3367
  acpAgents: acpAgentsForRuntime,
3312
3368
  }), [acpAgentsForRuntime, effectiveRuntimeSelection, props.permissionProfile]);
3313
3369
  const runtimeMode = acpModePending || runtimeProvider.kind === 'acp-provider' ? 'acp' : inferenceMode;
3314
- const selectableAcpAgents = useMemo(() => acpAgentsForRuntime.filter((agent) => agent.enabled), [acpAgentsForRuntime]);
3370
+ const selectableAcpAgents = useMemo(() => {
3371
+ const availableConfigIds = availableAcpConfigIds(sharedRuntimeProviders);
3372
+ return acpAgentsForRuntime.filter((agent) => agent.enabled && availableConfigIds.has(agent.id));
3373
+ }, [acpAgentsForRuntime, sharedRuntimeProviders]);
3315
3374
  const rawAcpProviderModel = effectiveRuntimeSelection?.providerKind === 'acp-provider'
3316
3375
  ? effectiveRuntimeSelection.providerModel
3317
3376
  : undefined;
@@ -3321,6 +3380,7 @@ export function AgentView(props) {
3321
3380
  : EMPTY_ACP_MODEL_CHOICES;
3322
3381
  const acpModelOptions = useMemo(() => acpProviderModelOptions
3323
3382
  .filter((option) => !option.familyBaseModelId)
3383
+ .filter((option) => option.available !== false)
3324
3384
  .map((option) => option.id), [acpProviderModelOptions]);
3325
3385
  const acpProviderModelOptionById = useMemo(() => new Map(acpProviderModelOptions.map((option) => [option.id, option])), [acpProviderModelOptions]);
3326
3386
  const acpModelMessage = selectedAcpAgentConfigId ? acpModelMessagesByAgentId[selectedAcpAgentConfigId] : undefined;
@@ -3399,6 +3459,38 @@ export function AgentView(props) {
3399
3459
  cancelled = true;
3400
3460
  };
3401
3461
  }, [acpModelListOpen, selectedAcpAgentConfigId]);
3462
+ useEffect(() => {
3463
+ if (!modelListOpen || runtimeMode !== 'cloud')
3464
+ return;
3465
+ let cancelled = false;
3466
+ setSharedProviderCatalogRefreshing(true);
3467
+ refreshHubAgentProviderCatalog({ force: true })
3468
+ .then((catalog) => {
3469
+ if (cancelled)
3470
+ return;
3471
+ setSharedRuntimeProviders(catalog.providers);
3472
+ setSharedProviderCatalogLoaded(true);
3473
+ })
3474
+ .catch(() => {
3475
+ if (cancelled)
3476
+ return;
3477
+ setSharedRuntimeProviders((current) => current.map((provider) => (provider.id === 'xeno-cloud'
3478
+ ? {
3479
+ ...provider,
3480
+ availability: 'unavailable',
3481
+ safeUnavailableReason: 'The live XENO model catalog could not be refreshed.',
3482
+ models: [],
3483
+ }
3484
+ : provider)));
3485
+ })
3486
+ .finally(() => {
3487
+ if (!cancelled)
3488
+ setSharedProviderCatalogRefreshing(false);
3489
+ });
3490
+ return () => {
3491
+ cancelled = true;
3492
+ };
3493
+ }, [modelListOpen, runtimeMode]);
3402
3494
  useEffect(() => {
3403
3495
  if (effectiveRuntimeSelection?.providerKind !== 'acp-provider' && !modelListOpen && !acpCliListOpen && !acpModelListOpen)
3404
3496
  return;
@@ -3460,9 +3552,10 @@ export function AgentView(props) {
3460
3552
  const acpCliBtnRef = useRef(null);
3461
3553
  const acpModelBtnRef = useRef(null);
3462
3554
  const authDisplayName = useAuthStore(s => s.user?.display_name ?? null);
3555
+ const cloudRuntimeProvider = useMemo(() => sharedRuntimeProviders.find((entry) => entry.id === 'xeno-cloud'), [sharedRuntimeProviders]);
3463
3556
  const cloudModels = useMemo(() => {
3464
- const provider = sharedRuntimeProviders.find((entry) => entry.id === 'xeno-cloud');
3465
- return (provider?.models || [])
3557
+ return (cloudRuntimeProvider?.models || [])
3558
+ .filter(isSelectableLiveModel)
3466
3559
  .map((modelEntry) => ({
3467
3560
  id: modelEntry.id,
3468
3561
  name: modelEntry.displayName || cloudModelName(modelEntry.id),
@@ -3479,7 +3572,9 @@ export function AgentView(props) {
3479
3572
  const score = cloudModelSortScore(a.id) - cloudModelSortScore(b.id);
3480
3573
  return score || a.name.localeCompare(b.name);
3481
3574
  });
3482
- }, [sharedRuntimeProviders]);
3575
+ }, [cloudRuntimeProvider]);
3576
+ const cloudCatalogUnavailableReason = cloudRuntimeProvider?.safeUnavailableReason
3577
+ || 'The live XENO model catalog is unavailable.';
3483
3578
  const availableModels = useXenoChatStore(s => s.availableModels);
3484
3579
  const fetchModels = useXenoChatStore(s => s.fetchModels);
3485
3580
  const localModels = useMemo(() => availableModels.filter(m => m.provider === 'local'), [availableModels]);
@@ -3641,10 +3736,9 @@ export function AgentView(props) {
3641
3736
  }).filter((entry) => !normalizedModelSearch || `${entry.name} ${entry.searchText}`.toLowerCase().includes(normalizedModelSearch));
3642
3737
  }, [acpEffortFamilies, acpEffortFamilyByModelId, acpModelOptions, acpProviderModel, acpProviderModelOptionById, normalizedModelSearch, selectedAcpEffortFamily?.baseId]);
3643
3738
  const preferredGpt55Model = useMemo(() => {
3644
- const selectableCloudModels = cloudModels.filter((modelEntry) => modelEntry.availability !== 'unavailable');
3645
- return selectableCloudModels.find(m => m.id === AGENT_GPT55_DEFAULT_MODEL)?.id
3646
- || selectableCloudModels.find(m => m.id === 'gpt-5.5')?.id
3647
- || selectableCloudModels[0]?.id
3739
+ return cloudModels.find(m => m.id === AGENT_GPT55_DEFAULT_MODEL)?.id
3740
+ || cloudModels.find(m => m.id === 'gpt-5.5')?.id
3741
+ || cloudModels[0]?.id
3648
3742
  || AGENT_GPT55_DEFAULT_MODEL;
3649
3743
  }, [cloudModels]);
3650
3744
  useEffect(() => {
@@ -3663,9 +3757,12 @@ export function AgentView(props) {
3663
3757
  return cloudModelName(selectedCloudEffortFamily.baseId);
3664
3758
  return isThinkingVariant ? `${cloud.name} (Thinking)` : cloud.name;
3665
3759
  }
3760
+ if (inferenceMode === 'cloud' && sharedProviderCatalogLoaded) {
3761
+ return cloudCatalogUnavailableReason.startsWith('Sign in') ? 'Sign in for Cloud' : 'Cloud unavailable';
3762
+ }
3666
3763
  const local = availableModels.find(m => m.id === actualModelId);
3667
3764
  return local?.name || actualModelId;
3668
- }, [cloudModels, availableModels, selectedModel, actualModelId, selectedCloudEffortFamily, isThinkingVariant, runtimeProvider.kind, runtimeProvider.displayName, acpProviderModelDisplayName]);
3765
+ }, [cloudModels, availableModels, selectedModel, actualModelId, selectedCloudEffortFamily, isThinkingVariant, runtimeProvider.kind, runtimeProvider.displayName, acpProviderModelDisplayName, inferenceMode, sharedProviderCatalogLoaded, cloudCatalogUnavailableReason]);
3669
3766
  const currentCloudModel = useMemo(() => cloudModels.find(m => m.id === actualModelId), [cloudModels, actualModelId]);
3670
3767
  const cloudModelSelectionMissing = runtimeProvider.kind !== 'acp-provider'
3671
3768
  && inferenceMode === 'cloud'
@@ -3678,11 +3775,13 @@ export function AgentView(props) {
3678
3775
  }, [actualModelId, currentCloudModel, preferredGpt55Model]);
3679
3776
  const cloudModelUnavailable = runtimeProvider.kind !== 'acp-provider'
3680
3777
  && inferenceMode === 'cloud'
3681
- && (cloudModelSelectionMissing || currentCloudModel?.availability === 'unavailable');
3778
+ && ((sharedProviderCatalogLoaded && cloudModels.length === 0) || cloudModelSelectionMissing);
3682
3779
  const cloudModelDegraded = runtimeProvider.kind !== 'acp-provider' && inferenceMode === 'cloud' && currentCloudModel?.availability === 'degraded';
3683
3780
  const cloudModelStatusReason = cloudModelSelectionMissing
3684
3781
  ? 'The selected model is not present in the live model catalog. Choose an available model.'
3685
- : (currentCloudModel?.availabilityReason || null);
3782
+ : (cloudModels.length === 0 && sharedProviderCatalogLoaded
3783
+ ? cloudCatalogUnavailableReason
3784
+ : (currentCloudModel?.availabilityReason || null));
3686
3785
  const renderModelSearch = () => (_jsx("div", { className: "shrink-0 border-b border-white/[0.06] bg-[#111111]", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-3 w-3 -translate-y-1/2 text-white/25" }), _jsx("input", { autoFocus: true, type: "text", value: modelSearchQuery, onChange: (event) => setModelSearchQuery(event.target.value), onKeyDown: (event) => {
3687
3786
  if (event.key === 'Escape') {
3688
3787
  setModelListOpen(false);
@@ -3698,6 +3797,64 @@ export function AgentView(props) {
3698
3797
  if (availableModels.length === 0)
3699
3798
  void fetchModels();
3700
3799
  }, [availableModels.length, fetchModels]);
3800
+ const providerLaneSelectionRevisionRef = useRef(0);
3801
+ const bindRuntimeProviderLane = useCallback((selection, modelId) => {
3802
+ const providerLaneSelect = getAgentPlatformBridge().workspace.providerLaneSelect;
3803
+ if (!conversationId || !conversation || !providerLaneSelect)
3804
+ return;
3805
+ const binding = resolveConversationWorkspaceBinding({
3806
+ workspaceMode: conversation.workspaceMode,
3807
+ rootDirectory: conversation.rootDirectory,
3808
+ grantedDirectories: conversation.grantedDirectories || EMPTY_STRINGS,
3809
+ inheritedDirectories: allowedDirectories,
3810
+ hasMessages: conversation.messages.length > 0,
3811
+ });
3812
+ if (binding.mode === 'unselected') {
3813
+ setToolStatus('Choose Chat or Agent mode before selecting a provider.');
3814
+ return;
3815
+ }
3816
+ const bindRevision = ++providerLaneSelectionRevisionRef.current;
3817
+ const selectedAt = selection.selectedAt;
3818
+ const providerId = selection.providerId;
3819
+ const acpAgentConfigId = selection.acpAgentConfigId;
3820
+ void providerLaneSelect({
3821
+ conversationId,
3822
+ workspaceMode: binding.mode,
3823
+ ...(binding.mode === 'workspace' && binding.rootDirectory
3824
+ ? { workingDirectory: binding.rootDirectory }
3825
+ : {}),
3826
+ ...(props.workspaceId ? { workspaceId: props.workspaceId } : {}),
3827
+ providerKind: selection.providerKind === 'acp-provider'
3828
+ ? 'acp-provider'
3829
+ : 'sdk-native',
3830
+ ...(providerId ? { providerId } : {}),
3831
+ ...(acpAgentConfigId ? { acpAgentConfigId } : {}),
3832
+ model: modelId.replace(/:thinking$/, ''),
3833
+ }).then((result) => {
3834
+ if (bindRevision !== providerLaneSelectionRevisionRef.current)
3835
+ return;
3836
+ const latest = useAgentChatStore.getState().getConversation(conversationId);
3837
+ if (!latest || latest.runtime?.selection.selectedAt !== selectedAt)
3838
+ return;
3839
+ if (!result?.success || !result.activeLane || !result.hostBinding) {
3840
+ setToolStatus(result?.error || 'The selected provider could not be bound to this conversation.');
3841
+ return;
3842
+ }
3843
+ setToolStatus(null);
3844
+ updateConversation(conversationId, {
3845
+ sessionUi: {
3846
+ ...(latest.sessionUi || {}),
3847
+ hostBinding: result.hostBinding,
3848
+ activeLane: result.activeLane,
3849
+ lanes: result.lanes || [result.activeLane],
3850
+ },
3851
+ });
3852
+ }).catch((error) => {
3853
+ if (bindRevision !== providerLaneSelectionRevisionRef.current)
3854
+ return;
3855
+ setToolStatus(error instanceof Error ? error.message : 'The selected provider could not be bound to this conversation.');
3856
+ });
3857
+ }, [allowedDirectories, conversation, conversationId, props.workspaceId, setToolStatus, updateConversation]);
3701
3858
  const persistRuntimeBinding = useCallback((selection, uiModel, mode) => {
3702
3859
  const previousSelection = conversation?.runtime?.selection;
3703
3860
  const runtime = createConversationRuntimeBinding({
@@ -3712,7 +3869,12 @@ export function AgentView(props) {
3712
3869
  schemaVersion: 2,
3713
3870
  model: uiModel,
3714
3871
  runtime,
3872
+ sessionUi: {
3873
+ ...(conversation?.sessionUi || {}),
3874
+ activeLane: undefined,
3875
+ },
3715
3876
  });
3877
+ bindRuntimeProviderLane(selection, uiModel);
3716
3878
  }
3717
3879
  if (conversationId
3718
3880
  && previousSelection?.providerKind === 'acp-provider'
@@ -3732,6 +3894,8 @@ export function AgentView(props) {
3732
3894
  return runtime;
3733
3895
  }, [
3734
3896
  agentId,
3897
+ bindRuntimeProviderLane,
3898
+ conversation?.sessionUi,
3735
3899
  conversation?.runtime?.selection,
3736
3900
  conversationId,
3737
3901
  props.teamId,
@@ -3775,11 +3939,14 @@ export function AgentView(props) {
3775
3939
  setEffortListOpen(false);
3776
3940
  setAcpCliListOpen(false);
3777
3941
  setAcpModelListOpen(false);
3778
- const rememberedCloudModel = cloudModels.find((modelEntry) => (modelEntry.id === lastCloudModel.replace(/:thinking$/, '')
3779
- && modelEntry.availability !== 'unavailable'));
3780
- const fallbackCloudModel = cloudModels.length === 0 && isAgentCloudModel(lastCloudModel)
3781
- ? lastCloudModel
3782
- : (rememberedCloudModel ? lastCloudModel : preferredGpt55Model);
3942
+ if (cloudModels.length === 0) {
3943
+ setToolStatus('No cloud models are currently available from the live XENO catalog.');
3944
+ setModeExpanded(false);
3945
+ setModelListOpen(false);
3946
+ return;
3947
+ }
3948
+ const rememberedCloudModel = cloudModels.find((modelEntry) => (modelEntry.id === lastCloudModel.replace(/:thinking$/, '')));
3949
+ const fallbackCloudModel = rememberedCloudModel ? lastCloudModel : preferredGpt55Model;
3783
3950
  setToolStatus(null);
3784
3951
  setInferenceMode('cloud');
3785
3952
  setModeExpanded(false);
@@ -3810,27 +3977,40 @@ export function AgentView(props) {
3810
3977
  setShowLocalRuntimePrompt(false);
3811
3978
  setToolStatus(null);
3812
3979
  setAcpModePending(true);
3813
- if (selectableAcpAgents.length === 0) {
3814
- await loadAcpAgentsForRuntime();
3815
- }
3816
- if (selectableAcpAgents.length === 0 && useAcpAgentStore.getState().agents.every((agent) => !agent.enabled)) {
3817
- setToolStatus('No enabled ACP configs. Open Settings to enable one.');
3818
- }
3980
+ // Close the previous mode's menus as part of the synchronous transition.
3981
+ // Doing this after the awaited Host/registry loads lets a user open the
3982
+ // ACP provider picker and then has the stale transition completion close
3983
+ // it underneath them exactly the "empty picker" race seen in the live
3984
+ // Electron scenario.
3819
3985
  setModelListOpen(false);
3820
3986
  setEffortListOpen(false);
3821
3987
  setAcpCliListOpen(false);
3822
3988
  setAcpModelListOpen(false);
3823
- }, [loadAcpAgentsForRuntime, selectableAcpAgents.length, setToolStatus]);
3989
+ let providers = sharedRuntimeProviders;
3990
+ if (!sharedProviderCatalogLoaded) {
3991
+ const catalog = await loadHubAgentProviderCatalog();
3992
+ providers = catalog.providers;
3993
+ setSharedRuntimeProviders(catalog.providers);
3994
+ setSharedProviderCatalogLoaded(true);
3995
+ }
3996
+ await loadAcpAgentsForRuntime();
3997
+ const availableConfigIds = availableAcpConfigIds(providers);
3998
+ const hasAvailableProvider = useAcpAgentStore.getState().agents.some((agent) => (agent.enabled && availableConfigIds.has(agent.id)));
3999
+ if (!hasAvailableProvider) {
4000
+ setToolStatus('No available ACP providers. Enable or repair one in Settings.');
4001
+ }
4002
+ }, [loadAcpAgentsForRuntime, setToolStatus, sharedProviderCatalogLoaded, sharedRuntimeProviders]);
3824
4003
  const selectAcpRuntime = useCallback((acpAgentConfigId, providerModel) => {
3825
4004
  const nextAcpAgentConfigId = acpAgentConfigId || selectedAcpAgentConfigId;
3826
4005
  if (!nextAcpAgentConfigId)
3827
4006
  return;
4007
+ setToolStatus(null);
3828
4008
  setAcpRuntimeSelection(nextAcpAgentConfigId, providerModel);
3829
4009
  setModelListOpen(false);
3830
4010
  setEffortListOpen(false);
3831
4011
  setAcpCliListOpen(false);
3832
4012
  setAcpModelListOpen(false);
3833
- }, [selectedAcpAgentConfigId, setAcpRuntimeSelection]);
4013
+ }, [selectedAcpAgentConfigId, setAcpRuntimeSelection, setToolStatus]);
3834
4014
  // Delay model selector visibility - hide instantly on expand, show after collapse animation (200ms)
3835
4015
  useEffect(() => {
3836
4016
  if (modeExpanded) {
@@ -3969,12 +4149,15 @@ export function AgentView(props) {
3969
4149
  && !!selectedAcpAgentConfigId
3970
4150
  && !acpRuntimeAgentDetails[selectedAcpAgentConfigId]
3971
4151
  && !acpRuntimeAgentDetailErrors[selectedAcpAgentConfigId];
3972
- const inputLocked = workspaceBinding.mode === 'unselected'
3973
- || !!props.externalInputLockReason
3974
- || isRunningCommand
3975
- || acpRuntimeReadinessPending
3976
- || (runtimeProvider.kind !== 'acp-provider' && !isModelReady)
3977
- || cloudModelUnavailable;
4152
+ const inputLocked = isAgentComposerInputLocked({
4153
+ workspaceUnselected: workspaceBinding.mode === 'unselected',
4154
+ externalInputLocked: !!props.externalInputLockReason,
4155
+ nativeTuiWriterAttached: !!props.onSendToNativeTui,
4156
+ commandRunning: isRunningCommand,
4157
+ acpRuntimeReadinessPending,
4158
+ nonAcpModelNotReady: runtimeProvider.kind !== 'acp-provider' && !isModelReady,
4159
+ cloudModelUnavailable,
4160
+ });
3978
4161
  const queuedTurnCount = conversation?.pendingTurns?.length ?? 0;
3979
4162
  const checkpoints = conversation?.checkpoints || [];
3980
4163
  const pendingTurns = conversation?.pendingTurns || [];
@@ -5968,6 +6151,30 @@ export function AgentView(props) {
5968
6151
  const hasAttachments = attachmentsToSend.length > 0;
5969
6152
  if ((!trimmed && !hasAttachments) || inputLocked)
5970
6153
  return;
6154
+ if (props.onSendToNativeTui) {
6155
+ const targetConversationId = ensureConversationId(conversationId);
6156
+ if (hasAttachments) {
6157
+ appendMessage(targetConversationId, {
6158
+ role: 'assistant',
6159
+ content: 'Attach files from the native provider TUI or return to structured chat before sending attachments.',
6160
+ error: 'Native provider TUI input accepts text only.',
6161
+ });
6162
+ return;
6163
+ }
6164
+ setInput('');
6165
+ if (textareaRef.current)
6166
+ textareaRef.current.style.height = 'auto';
6167
+ const delivered = await props.onSendToNativeTui(trimmed);
6168
+ if (!delivered.success) {
6169
+ setInput(trimmed);
6170
+ appendMessage(targetConversationId, {
6171
+ role: 'assistant',
6172
+ content: delivered.error || 'The native provider TUI could not receive the message.',
6173
+ error: delivered.error || 'Native provider TUI input failed.',
6174
+ });
6175
+ }
6176
+ return;
6177
+ }
5971
6178
  const useLegacyDeterministicFileActions = isLegacyDeterministicFileActionCompatibilityEnabled();
5972
6179
  const replaceExistingFileCreate = useLegacyDeterministicFileActions && !hasAttachments && isReplaceExistingFileIntent(trimmed)
5973
6180
  ? findLastExistingFileWriteRequest(messages)
@@ -7271,18 +7478,17 @@ export function AgentView(props) {
7271
7478
  const fixture = agent.certification?.status === 'fixture_only';
7272
7479
  const ready = fixture || (agent.certification?.status === 'certified' && agent.providerReview?.runnable === true);
7273
7480
  return (_jsxs("button", { onClick: () => { selectAcpRuntime(agent.id, selected ? acpProviderModel : undefined); }, role: "option", "aria-selected": selected, className: `w-full flex items-center justify-between gap-2 px-3 py-1.5 text-left transition-colors cursor-pointer ${selected ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`, children: [_jsx("span", { className: `min-w-0 flex-1 text-[11px] truncate ${selected ? 'text-white/70' : 'text-white/40'}`, children: acpAgentDisplayName(agent) }), _jsx("span", { className: `text-[8px] uppercase tracking-wide ${ready ? 'text-[#96e59d]/70' : 'text-[#ffd38a]/70'}`, children: ready ? 'ready' : 'check' })] }, agent.id));
7274
- })) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No enabled ACP CLI configs. Use Settings to enable one." }))] })), runtimeMode === 'acp' && acpModelListOpen && (_jsxs("div", { ref: acpModelListRef, className: "xeno-elevated-shell xeno-border-subtle absolute left-[170px] bottom-[44px] z-50 flex max-h-[280px] w-[220px] flex-col overflow-hidden rounded-md border", children: [renderModelSearch(), _jsxs("div", { role: "listbox", className: "min-h-0 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 pt-2 pb-1 text-[9px] uppercase tracking-[0.14em] text-white/25", children: [_jsx("span", { children: "ACP model" }), sharedProviderCatalogRefreshing && !acpModelsLoading && _jsx("span", { children: "Refreshing..." })] }), _jsx("button", { onClick: () => { if (selectedAcpAgentConfigId)
7481
+ })) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No available ACP CLI providers. Enable or repair one in Settings." }))] })), runtimeMode === 'acp' && acpModelListOpen && (_jsxs("div", { ref: acpModelListRef, className: "xeno-elevated-shell xeno-border-subtle absolute left-[170px] bottom-[44px] z-50 flex max-h-[280px] w-[220px] flex-col overflow-hidden rounded-md border", children: [renderModelSearch(), _jsxs("div", { role: "listbox", className: "min-h-0 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 pt-2 pb-1 text-[9px] uppercase tracking-[0.14em] text-white/25", children: [_jsx("span", { children: "ACP model" }), sharedProviderCatalogRefreshing && !acpModelsLoading && _jsx("span", { children: "Refreshing..." })] }), _jsx("button", { onClick: () => { if (selectedAcpAgentConfigId)
7275
7482
  selectAcpRuntime(selectedAcpAgentConfigId, undefined); }, disabled: !selectedAcpAgentConfigId, role: "option", "aria-selected": !acpProviderModel, className: `w-full flex items-center justify-between px-3 py-1.5 text-left transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed ${!acpProviderModel ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`, children: _jsx("span", { className: `text-[11px] truncate ${!acpProviderModel ? 'text-white/70' : 'text-white/40'}`, children: "CLI default" }) }), acpModelsLoading ? (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "Loading models..." })) : acpModelOptions.length > 0 ? (filteredAcpModelOptions.length > 0 ? filteredAcpModelOptions.map((entry) => {
7276
7483
  const selected = entry.selectionKey === (selectedAcpEffortFamily?.baseId || acpProviderModel);
7277
7484
  return (_jsxs("button", { onClick: () => { if (selectedAcpAgentConfigId)
7278
7485
  selectAcpRuntime(selectedAcpAgentConfigId, entry.id); }, disabled: !selectedAcpAgentConfigId || !entry.available, role: "option", "aria-selected": selected, className: `w-full flex items-center justify-between px-3 py-1.5 text-left transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed ${selected ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`, title: entry.reason || undefined, children: [_jsx("span", { className: `min-w-0 flex-1 text-[11px] truncate ${selected ? 'text-white/70' : 'text-white/40'}`, children: entry.name }), !entry.available && _jsx("span", { className: "ml-2 flex-shrink-0 text-[8px] uppercase tracking-wide text-[#ff8f8f]", children: "Unavailable" })] }, entry.selectionKey));
7279
- }) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No matching models" }))) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: selectedAcpAgentConfigId ? (acpModelMessage || 'No preset models for this ACP CLI') : 'Select a CLI first' }))] })] })), modelListOpen && runtimeMode !== 'acp' && (_jsxs("div", { ref: modelListRef, className: "xeno-elevated-shell xeno-border-subtle absolute left-3 bottom-[44px] z-50 flex max-h-[340px] w-[280px] flex-col overflow-hidden rounded-md border", children: [renderModelSearch(), _jsx("div", { role: "listbox", className: "min-h-0 overflow-y-auto", children: runtimeMode === 'cloud' ? (cloudModels.length > 0 ? (filteredCloudModelOptions.length > 0 ? filteredCloudModelOptions.map(entry => ((() => {
7486
+ }) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No matching models" }))) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: selectedAcpAgentConfigId ? (acpModelMessage || 'No preset models for this ACP CLI') : 'Select a CLI first' }))] })] })), modelListOpen && runtimeMode !== 'acp' && (_jsxs("div", { ref: modelListRef, className: "xeno-elevated-shell xeno-border-subtle absolute left-3 bottom-[44px] z-50 flex max-h-[340px] w-[280px] flex-col overflow-hidden rounded-md border", children: [renderModelSearch(), _jsx("div", { role: "listbox", className: "min-h-0 overflow-y-auto", children: runtimeMode === 'cloud' ? (sharedProviderCatalogRefreshing ? (_jsx("div", { className: "px-3 py-3 text-[10px] text-white/25 text-center", children: "Refreshing available models..." })) : cloudModels.length > 0 ? (filteredCloudModelOptions.length > 0 ? filteredCloudModelOptions.map(entry => ((() => {
7280
7487
  const modelMeta = cloudModels.find(cm => cm.id === entry.id.replace(/:thinking$/, ''));
7281
- const unavailable = modelMeta?.availability === 'unavailable';
7282
7488
  const degraded = modelMeta?.availability === 'degraded';
7283
7489
  const selected = entry.selectionKey === (selectedCloudEffortFamily?.baseId || selectedModel);
7284
- return (_jsxs("button", { onClick: () => { setInferenceMode('cloud'); setSelectedModel(entry.id); setModelListOpen(false); setEffortListOpen(false); }, role: "option", "aria-selected": selected, className: `w-full flex items-center justify-between px-3 py-1.5 text-left transition-colors cursor-pointer ${selected ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'} ${unavailable ? 'opacity-60' : ''}`, title: modelMeta?.availabilityReason || undefined, children: [_jsxs("div", { className: "min-w-0 flex-1 flex items-center gap-2", children: [_jsx("span", { className: `text-[11px] truncate ${selected ? 'text-white/70' : 'text-white/40'}`, children: entry.name }), unavailable && _jsx("span", { className: "text-[8px] uppercase tracking-wide text-[#ff8f8f]", children: "Unavailable" }), !unavailable && degraded && _jsx("span", { className: "text-[8px] uppercase tracking-wide text-[#ffd38a]", children: "Fallback" })] }), _jsx("span", { className: "text-[9px] text-white/20 flex-shrink-0 ml-2 tabular-nums", children: (() => { const m = cloudModels.find(cm => cm.id === entry.id.replace(/:thinking$/, '')); return m?.creditsPerKToken != null ? `${m.creditsPerKToken}/1k` : ''; })() })] }, entry.id));
7285
- })())) : (_jsx("div", { className: "px-3 py-3 text-[10px] text-white/25 text-center", children: "No matching models" }))) : (_jsx("div", { className: "px-3 py-3 text-[10px] text-white/25 text-center", children: "Loading models..." }))) : (_jsxs(_Fragment, { children: [localModels.length > 0 ? (filteredLocalModels.length > 0 ? filteredLocalModels.map(m => {
7490
+ return (_jsxs("button", { onClick: () => { setInferenceMode('cloud'); setSelectedModel(entry.id); setModelListOpen(false); setEffortListOpen(false); }, role: "option", "aria-selected": selected, className: `w-full flex items-center justify-between px-3 py-1.5 text-left transition-colors cursor-pointer ${selected ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`, title: modelMeta?.availabilityReason || undefined, children: [_jsxs("div", { className: "min-w-0 flex-1 flex items-center gap-2", children: [_jsx("span", { className: `text-[11px] truncate ${selected ? 'text-white/70' : 'text-white/40'}`, children: entry.name }), degraded && _jsx("span", { className: "text-[8px] uppercase tracking-wide text-[#ffd38a]", children: "Fallback" })] }), _jsx("span", { className: "text-[9px] text-white/20 flex-shrink-0 ml-2 tabular-nums", children: (() => { const m = cloudModels.find(cm => cm.id === entry.id.replace(/:thinking$/, '')); return m?.creditsPerKToken != null ? `${m.creditsPerKToken}/1k` : ''; })() })] }, entry.id));
7491
+ })())) : (_jsx("div", { className: "px-3 py-3 text-[10px] text-white/25 text-center", children: "No matching models" }))) : (_jsx("div", { className: "px-3 py-3 text-[10px] leading-relaxed text-white/35 text-center", children: cloudCatalogUnavailableReason }))) : (_jsxs(_Fragment, { children: [localModels.length > 0 ? (filteredLocalModels.length > 0 ? filteredLocalModels.map(m => {
7286
7492
  const selected = m.id === (selectedLocalEffortFamily?.baseId || selectedModel);
7287
7493
  return (_jsxs("button", { onClick: () => { setInferenceMode('local'); setSelectedModel(m.id); setModelListOpen(false); setEffortListOpen(false); }, role: "option", "aria-selected": selected, className: `w-full flex items-center gap-2 px-3 py-1.5 text-left transition-colors cursor-pointer ${selected ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`, children: [_jsx("span", { className: `text-[11px] truncate ${selected ? 'text-white/70' : 'text-white/40'}`, children: m.name }), m.parameterSize && _jsx("span", { className: "text-[8px] text-white/20 flex-shrink-0 ml-auto", children: m.parameterSize })] }, m.id));
7288
7494
  }) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No matching models" }))) : (_jsx("div", { className: "px-3 py-2.5 text-[10px] text-white/25 text-center", children: "No local models installed" })), _jsx("button", { onClick: () => { setModelListOpen(false); goToModelsMarketplace(); }, className: "w-full flex items-center justify-center gap-1.5 px-3 py-2 border-t border-white/[0.06] text-[10px] text-white/35 hover:text-white/60 hover:bg-white/[0.04] transition-colors cursor-pointer", children: "Browse models" })] })) })] })), _jsxs("div", { className: "space-y-0", children: [pendingTurns.length > 0 && (_jsxs("div", { className: "xeno-border-subtle rounded-t-md border border-b-0 bg-[#111111] overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2 border-b xeno-border-subtle", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-[10px] uppercase tracking-[0.14em] xeno-text-faint whitespace-nowrap", children: "Queued Messages" }), _jsx("span", { className: "text-[10px] xeno-text-muted tabular-nums whitespace-nowrap", children: pendingTurns.length })] }), _jsx("button", { onClick: () => patchSessionUi({ panelOpen: true }), className: "text-[10px] xeno-text-faint hover:text-[color:var(--xeno-ink-secondary)] transition-colors whitespace-nowrap cursor-pointer focus-visible:outline focus-visible:outline-1 focus-visible:outline-white/35", children: "Manage queue" })] }), _jsxs("div", { className: "divide-y xeno-border-subtle", children: [pendingTurns.slice(0, 3).map((turn) => (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx("span", { className: "inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] bg-white/[0.04] px-1.5 text-[10px] xeno-text-faint tabular-nums", children: new Date(turn.queuedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }), _jsx("span", { className: "min-w-0 flex-1 text-[12px] leading-4 xeno-text-secondary truncate", children: turn.content }), _jsx("button", { onClick: () => handleRemoveQueuedConversationTurn(turn.id), className: "text-[10px] xeno-text-faint hover:text-[color:var(--xeno-ink-secondary)] transition-colors whitespace-nowrap cursor-pointer focus-visible:outline focus-visible:outline-1 focus-visible:outline-white/35", title: "Remove queued message", children: "Remove" })] }, turn.id))), pendingTurns.length > 3 && (_jsxs("div", { className: "px-3 py-2 text-[11px] xeno-text-faint", children: [pendingTurns.length - 3, " more queued"] }))] })] })), _jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", "data-agent-workspace-binding": workspaceBinding.mode, children: [_jsxs("div", { className: "inline-flex items-center rounded-md border border-white/[0.08] bg-white/[0.025] p-0.5 text-[11px]", children: [_jsx("button", { type: "button", onClick: () => { void handleSelectWorkspaceMode('chat-only'); }, disabled: isBusy || !!props.externalInputLockReason || isSelectingConversationWorkspace, className: `rounded px-2.5 py-1 font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${workspaceBinding.mode !== 'workspace'