From e2b383ca52ee784ddbfdfa4708647be4b5fc40fb Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 00:22:56 -0400 Subject: [PATCH 01/28] fix(ui): pin monaco 0.55, drop dead css import Restore a green @argos/ui build broken by the deps update (#42). style.css removed a dead shadcn/tailwind.css import to a file that never existed (silently skipped on some platforms, failed in CI). monaco-editor pinned to ^0.55.1 because 0.56.0 tightened its exports map and no longer exposes the deep editor.api subpath that a transitive importer needs; 0.55.1 wildcard exports still allow it. bun.lock updated. --- bun.lock | 12 +++--------- packages/ui/package.json | 2 +- packages/ui/src/assets/style.css | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index e5b2e2b95..855477437 100644 --- a/bun.lock +++ b/bun.lock @@ -386,7 +386,7 @@ "babel-plugin-react-compiler": "^1.0.0", "jsdom": "catalog:", "mermaid": "^11.16.0", - "monaco-editor": "^0.56.0", + "monaco-editor": "^0.55.1", "stream-monaco": "^0.0.49", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "catalog:", @@ -2518,7 +2518,7 @@ "module-error": ["module-error@1.0.2", "", {}, "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA=="], - "monaco-editor": ["monaco-editor@0.56.0", "", { "dependencies": { "dompurify": "3.4.8", "marked": "14.0.0" } }, "sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg=="], + "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -3388,7 +3388,7 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "monaco-editor/dompurify": ["dompurify@3.4.8", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ=="], + "monaco-editor/dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], @@ -3436,8 +3436,6 @@ "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "stream-monaco/monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], - "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], @@ -3690,10 +3688,6 @@ "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "stream-monaco/monaco-editor/dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], - - "stream-monaco/monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], - "tiny-runtime-injector/axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], "tiny-runtime-injector/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], diff --git a/packages/ui/package.json b/packages/ui/package.json index e193a4cda..94226c4c5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -71,7 +71,7 @@ "babel-plugin-react-compiler": "^1.0.0", "jsdom": "catalog:", "mermaid": "^11.16.0", - "monaco-editor": "^0.56.0", + "monaco-editor": "^0.55.1", "stream-monaco": "^0.0.49", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "catalog:", diff --git a/packages/ui/src/assets/style.css b/packages/ui/src/assets/style.css index 76d0487b8..894bb25fa 100644 --- a/packages/ui/src/assets/style.css +++ b/packages/ui/src/assets/style.css @@ -2,7 +2,6 @@ @import "tw-animate-css"; @import "tailwind-scrollbar-hide/v4"; @import "../../components/agent-elements/agent-ui.css"; -@import "shadcn/tailwind.css"; @import "@fontsource-variable/inter"; @plugin "@tailwindcss/typography"; From 511e25548ab014caa82179e7b16e35b2d0367cf8 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 15:06:15 -0400 Subject: [PATCH 02/28] fix(chat): acp loading, model groups, send/stop --- .../daemon/src/host/acp-provider-execution.ts | 57 +++++- apps/daemon/test/daemonSessionRoutes.test.ts | 3 +- .../components/chat/AcpAdvancedSettings.tsx | 109 +++++++++--- .../ui/src/components/chat/ChatInputBox.tsx | 8 + .../src/components/chat/ChatInputToolbar.tsx | 25 ++- .../ui/src/components/chat/ChatStatusBar.tsx | 163 +++++++++++++----- .../composables/useChatStatusBarAcpConfig.ts | 16 +- packages/ui/src/pages/ChatPage.tsx | 72 +++++--- packages/ui/src/pages/NewThreadPage.tsx | 31 ++-- packages/ui/src/stores/ui/message.ts | 2 +- 10 files changed, 369 insertions(+), 117 deletions(-) diff --git a/apps/daemon/src/host/acp-provider-execution.ts b/apps/daemon/src/host/acp-provider-execution.ts index dca45eacc..390b7601b 100644 --- a/apps/daemon/src/host/acp-provider-execution.ts +++ b/apps/daemon/src/host/acp-provider-execution.ts @@ -47,7 +47,16 @@ type PendingAcpPermission = { */ export class AcpProviderExecutionPort implements ProviderExecutionPort { private runtimePromise: Promise | null = null; - private activeTurns = new Map(); + private activeTurns = new Map< + string, + { + controller: AbortController; + eventId: string; + runId: string; + donePromise: Promise; + doneResolve: () => void; + } + >(); private pendingPermissions = new Map(); private readonly contentMapper = new AcpContentMapper(); @@ -138,10 +147,16 @@ export class AcpProviderExecutionPort implements ProviderExecutionPort { const runtime = await this.getRuntime(); const record = await this.getSessionRecord(sessionId); const controller = new AbortController(); + let doneResolve!: () => void; + const donePromise = new Promise((resolve) => { + doneResolve = resolve; + }); this.activeTurns.set(sessionId, { controller, eventId: assistantMessageId, runId: requestId, + donePromise, + doneResolve, }); void this.runTurn( @@ -154,7 +169,11 @@ export class AcpProviderExecutionPort implements ProviderExecutionPort { assistantMessageId, record?.workdir, ).finally(() => { - this.activeTurns.delete(sessionId); + const current = this.activeTurns.get(sessionId); + if (current && current.runId === requestId) { + this.activeTurns.delete(sessionId); + } + doneResolve(); }); return { requestId, messageId: assistantMessageId }; @@ -498,9 +517,37 @@ export class AcpProviderExecutionPort implements ProviderExecutionPort { }); } - async steerActiveTurn(_sessionId: string, _content: string | SendMessageInput): Promise { - await this.cancelGeneration(_sessionId); - await this.sendMessage(_sessionId, _content); + async steerActiveTurn(sessionId: string, content: string | SendMessageInput): Promise { + await this.interruptActiveTurn(sessionId); + await this.sendMessage(sessionId, content); + } + + /** + * Non-destructively interrupts the in-flight ACP prompt for a session so a new + * turn can follow (used by steer). Unlike {@link cancelGeneration}, this does + * NOT tear down the session or unbind the agent process: it asks the agent to + * cancel the active `session/prompt` request, aborts local streaming, and + * waits for the turn to settle before returning. + */ + private async interruptActiveTurn(sessionId: string): Promise { + const active = this.activeTurns.get(sessionId); + if (!active) return; + + active.controller.abort(); + + try { + const runtime = await this.getRuntime(); + const session = runtime.sessionManager.getSession(sessionId); + if (session) { + await session.connection.agent.notify(acpMethods.agent.session.cancel, { + sessionId: session.sessionId, + } as schema.CancelNotification); + } + } catch (error) { + console.warn("[ACP] Failed to send session/cancel for steer:", error); + } + + await Promise.race([active.donePromise, new Promise((resolve) => setTimeout(resolve, 4000))]).catch(() => {}); } async getAcpSessionCommands(conversationId: string): Promise< diff --git a/apps/daemon/test/daemonSessionRoutes.test.ts b/apps/daemon/test/daemonSessionRoutes.test.ts index b18ab3160..3751f5d08 100644 --- a/apps/daemon/test/daemonSessionRoutes.test.ts +++ b/apps/daemon/test/daemonSessionRoutes.test.ts @@ -887,6 +887,7 @@ describe("daemon ACP session routes", () => { vi.spyOn(provider as any, "getRuntime").mockResolvedValue(runtime); vi.spyOn(provider as any, "cancelGeneration").mockResolvedValue(undefined); + vi.spyOn(provider as any, "interruptActiveTurn").mockResolvedValue(undefined); vi.spyOn(provider as any, "sendMessage").mockResolvedValue({ requestId: null, messageId: null }); await expect(provider.setAcpSessionConfigOption("session-1", "__acp_legacy_model__", "model-b")).resolves.toEqual({ @@ -905,7 +906,7 @@ describe("daemon ACP session routes", () => { }); await expect(provider.steerActiveTurn("session-1", "steer this")).resolves.toBeUndefined(); - expect(provider.cancelGeneration).toHaveBeenCalledWith("session-1"); + expect(provider.interruptActiveTurn).toHaveBeenCalledWith("session-1"); expect(provider.sendMessage).toHaveBeenCalledWith("session-1", "steer this"); expect(runtime.processManager.updateBoundProcessConfigState).toHaveBeenCalledWith("session-1", { source: "legacy", diff --git a/packages/ui/src/components/chat/AcpAdvancedSettings.tsx b/packages/ui/src/components/chat/AcpAdvancedSettings.tsx index 9c2d6f9c6..8d3f6ae07 100644 --- a/packages/ui/src/components/chat/AcpAdvancedSettings.tsx +++ b/packages/ui/src/components/chat/AcpAdvancedSettings.tsx @@ -16,6 +16,39 @@ interface AcpAdvancedSettingsProps { const switchId = (optionId: string) => `acp-advanced-option-${optionId}`; +type AcpOptionValueLike = { + value: string; + label: string; + groupId?: string | null; + groupLabel?: string | null; +}; + +const resolveAcpOptionGroup = (entry: AcpOptionValueLike): { key: string; label: string } => { + if (entry.groupId && entry.groupId.trim()) { + return { key: entry.groupId, label: entry.groupLabel?.trim() ? entry.groupLabel : entry.groupId }; + } + + const valueSlash = entry.value.indexOf("/"); + const labelSlash = entry.label.indexOf("/"); + const labSource = valueSlash > 0 ? entry.value : labelSlash > 0 ? entry.label : ""; + if (labSource) { + const lab = labSource.slice(0, labSource.indexOf("/")); + if (lab.trim()) { + return { key: `__lab__${lab.toLowerCase()}`, label: lab }; + } + } + + return { key: "__default__", label: "" }; +}; + +const resolveAcpOptionDisplayLabel = (entry: { label: string }): string => { + const idx = entry.label.indexOf("/"); + if (idx > 0 && entry.label.slice(idx + 1).trim()) { + return entry.label.slice(idx + 1); + } + return entry.label; +}; + export default function AcpAdvancedSettings({ options, readOnly, @@ -51,6 +84,20 @@ export default function AcpAdvancedSettings({
{options.map((option) => { const disabled = readOnly || isOptionSaving(option.id); + const selectEntries = option.type === "select" ? (option.options ?? []) : []; + const selectGrouped = selectEntries.reduce< + Record + >((acc, entry) => { + const g = resolveAcpOptionGroup(entry); + if (!acc[g.key]) { + acc[g.key] = { label: g.label, entries: [] }; + } + acc[g.key].entries.push(entry); + return acc; + }, {}); + const selectGroupKeys = Object.keys(selectGrouped); + const hasSelectGroups = + selectGroupKeys.length > 1 || (selectGroupKeys.length === 1 && selectGroupKeys[0] !== "__default__"); return (
- {(option.options ?? []).map((entry) => { - const isSelected = String(option.currentValue) === entry.value; + {selectGroupKeys.map((groupKey) => { + const group = selectGrouped[groupKey]; return ( - +
+ {hasSelectGroups && group.label && ( +
+ {group.label} +
+ )} + {group.entries.map((entry) => { + const isSelected = String(option.currentValue) === entry.value; + return ( + + ); + })} +
); })}
diff --git a/packages/ui/src/components/chat/ChatInputBox.tsx b/packages/ui/src/components/chat/ChatInputBox.tsx index 5cf22dc97..dbf995712 100644 --- a/packages/ui/src/components/chat/ChatInputBox.tsx +++ b/packages/ui/src/components/chat/ChatInputBox.tsx @@ -65,6 +65,7 @@ const ChatInputBox = forwardRef< insertWorkspaceReference: (targetPath: string) => boolean; getPendingSkillsSnapshot: () => string[]; focusInput: () => void; + clearInput: () => void; }, ChatInputBoxProps >( @@ -338,6 +339,12 @@ const ChatInputBox = forwardRef< if (editor) setCaretToEnd(editor); } + function clearInput() { + const ed = editorRef.current; + if (!ed) return; + ed.commands.setContent(toEditorDoc(""), { emitUpdate: false }); + } + useImperativeHandle( ref, () => ({ @@ -346,6 +353,7 @@ const ChatInputBox = forwardRef< insertWorkspaceReference, getPendingSkillsSnapshot, focusInput, + clearInput, }), [editor], ); diff --git a/packages/ui/src/components/chat/ChatInputToolbar.tsx b/packages/ui/src/components/chat/ChatInputToolbar.tsx index 05885bf2d..7096bb262 100644 --- a/packages/ui/src/components/chat/ChatInputToolbar.tsx +++ b/packages/ui/src/components/chat/ChatInputToolbar.tsx @@ -5,6 +5,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "#shadcn/components/ui/t interface ChatInputToolbarProps { isGenerating?: boolean; + isCancelling?: boolean; hasInput?: boolean; hasText?: boolean; sendDisabled?: boolean; @@ -22,6 +23,7 @@ interface ChatInputToolbarProps { const ChatInputToolbar: FC = ({ isGenerating = false, + isCancelling = false, hasInput = false, hasText = false, sendDisabled = false, @@ -70,10 +72,10 @@ const ChatInputToolbar: FC = ({ }, [isGenerating, hasActiveInput]); const primaryTooltip = useMemo(() => { - if (buttonMode === "stop") return "Stop"; + if (buttonMode === "stop") return isCancelling ? "Cancelling…" : "Stop"; if (buttonMode === "queue") return "Queue"; return "Send"; - }, [buttonMode]); + }, [buttonMode, isCancelling]); const handlePrimaryAction = () => { if (buttonMode === "stop") { @@ -164,27 +166,38 @@ const ChatInputToolbar: FC = ({ diff --git a/packages/ui/src/components/chat/ChatStatusBar.tsx b/packages/ui/src/components/chat/ChatStatusBar.tsx index 9d41aea06..817795082 100644 --- a/packages/ui/src/components/chat/ChatStatusBar.tsx +++ b/packages/ui/src/components/chat/ChatStatusBar.tsx @@ -88,6 +88,39 @@ const isSameModelSelection = ( right: ModelSelection | null | undefined, ): boolean => Boolean(left && right && left.providerId === right.providerId && left.modelId === right.modelId); +type AcpOptionValueLike = { + value: string; + label: string; + groupId?: string | null; + groupLabel?: string | null; +}; + +const resolveAcpOptionGroup = (entry: AcpOptionValueLike): { key: string; label: string } => { + if (entry.groupId && entry.groupId.trim()) { + return { key: entry.groupId, label: entry.groupLabel?.trim() ? entry.groupLabel : entry.groupId }; + } + + const valueSlash = entry.value.indexOf("/"); + const labelSlash = entry.label.indexOf("/"); + const labSource = valueSlash > 0 ? entry.value : labelSlash > 0 ? entry.label : ""; + if (labSource) { + const lab = labSource.slice(0, labSource.indexOf("/")); + if (lab.trim()) { + return { key: `__lab__${lab.toLowerCase()}`, label: lab }; + } + } + + return { key: "__default__", label: "" }; +}; + +const resolveAcpOptionDisplayLabel = (entry: { label: string }): string => { + const idx = entry.label.indexOf("/"); + if (idx > 0 && entry.label.slice(idx + 1).trim()) { + return entry.label.slice(idx + 1); + } + return entry.label; +}; + type SystemPromptOption = { id: string; label: string; @@ -101,6 +134,18 @@ type GroupedModelList = { models: RENDERER_MODEL_META[]; }; +type ModelDisplayEntry = { + model: RENDERER_MODEL_META; + providerId: string; + displayName: string; +}; + +type ModelDisplaySection = { + key: string; + label: string; + entries: ModelDisplayEntry[]; +}; + const TEMPERATURE_STEP = 0.1; const TOP_P_STEP = 0.1; const TOP_P_MIN = 0.1; @@ -360,6 +405,30 @@ const ChatStatusBar = forwardRef( .filter((group) => group.models.length > 0); }, [modelSearchKeyword, modelGroups]); + const modelDisplaySections = useMemo(() => { + const sections: ModelDisplaySection[] = []; + const sectionIndex = new Map(); + for (const group of filteredModelGroups) { + for (const model of group.models) { + const slashIndex = model.id.indexOf("/"); + const hasLabSplit = slashIndex > 0 && model.id.slice(slashIndex + 1).trim().length > 0; + const sectionKey = hasLabSplit + ? `${group.providerId}::${model.id.slice(0, slashIndex).toLowerCase()}` + : group.providerId; + const sectionLabel = hasLabSplit ? model.id.slice(0, slashIndex) : group.providerName; + const displayName = hasLabSplit ? model.id.slice(slashIndex + 1) : model.id; + let idx = sectionIndex.get(sectionKey); + if (idx === undefined) { + idx = sections.length; + sections.push({ key: sectionKey, label: sectionLabel, entries: [] }); + sectionIndex.set(sectionKey, idx); + } + sections[idx].entries.push({ model, providerId: group.providerId, displayName }); + } + } + return sections; + }, [filteredModelGroups]); + const modelSettingsTarget = useMemo( () => modelSettingsSelection ?? effectiveModelSelection, [modelSettingsSelection, effectiveModelSelection], @@ -1291,11 +1360,11 @@ const ChatStatusBar = forwardRef( const grouped = optionEntries.reduce< Record >((acc, entry) => { - const key = entry.groupId ?? "__default__"; - if (!acc[key]) { - acc[key] = { label: entry.groupLabel ?? "", entries: [] }; + const g = resolveAcpOptionGroup(entry); + if (!acc[g.key]) { + acc[g.key] = { label: g.label, entries: [] }; } - acc[key].entries.push(entry); + acc[g.key].entries.push(entry); return acc; }, {}); const groupKeys = Object.keys(grouped); @@ -1375,7 +1444,9 @@ const ChatStatusBar = forwardRef( className={`mt-0.5 h-3.5 w-3.5 shrink-0 ${isSelected ? "text-primary" : "text-transparent"}`} />
-
{entry.label}
+
+ {resolveAcpOptionDisplayLabel(entry)} +
{entry.description && (
{entry.description} @@ -1405,7 +1476,7 @@ const ChatStatusBar = forwardRef( className={`mt-0.5 h-3.5 w-3.5 shrink-0 ${isSelected ? "text-primary" : "text-transparent"}`} />
-
{entry.label}
+
{resolveAcpOptionDisplayLabel(entry)}
{entry.description && (
{entry.description} @@ -1500,45 +1571,53 @@ const ChatStatusBar = forwardRef( )} {!showModelOptionsLoading && !hasModelOptionsError && filteredModelGroups.length > 0 && (
- {filteredModelGroups.map((group) => ( -
+ {modelDisplaySections.map((section) => ( +
- {group.providerName} + {section.label}
- {group.models.map((model) => ( -
- - -
- ))} + {section.entries.map((entry) => { + const { model, providerId, displayName } = entry; + return ( +
+ + +
+ ); + })}
))} diff --git a/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts b/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts index 2a583cacd..8de4a5e38 100644 --- a/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts +++ b/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts @@ -84,6 +84,7 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti const [acpInlineOpenOptionId, setAcpInlineOpenOptionId] = useState(null); const [acpOptionSavingIds, setAcpOptionSavingIds] = useState([]); const [acpConfigError, setAcpConfigError] = useState(null); + const [isAcpSessionConfigLoading, setIsAcpSessionConfigLoading] = useState(false); const acpConfigCacheByKeyRef = useRef(new Map()); const acpConfigSyncTokenRef = useRef(0); @@ -143,7 +144,15 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti const acpConfigOptions = useMemo(() => acpConfigState?.options ?? [], [acpConfigState]); const isAcpConfigLoading = useMemo(() => { - if (!options.isAcpAgent || options.activeAcpSessionId || !options.acpWorkspacePath) { + if (!options.isAcpAgent) { + return false; + } + + if (options.activeAcpSessionId) { + return isAcpSessionConfigLoading; + } + + if (!options.acpWorkspacePath) { return false; } @@ -152,6 +161,7 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti }, [ options.isAcpAgent, options.activeAcpSessionId, + isAcpSessionConfigLoading, options.acpWorkspacePath, acpConfigRequestKey, acpConfigLoadingRequestKey, @@ -255,6 +265,7 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti setAcpConfigLoadedRequestKey(null); setAcpConfigLoadingRequestKey(null); setAcpConfigError(null); + setIsAcpSessionConfigLoading(false); return; } @@ -264,6 +275,7 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti setAcpConfigLoadingRequestKey(null); setAcpConfigState(null); setAcpConfigLoadedRequestKey(null); + setIsAcpSessionConfigLoading(true); let loaded = false; const delays = [0, 1500, 3000]; @@ -312,11 +324,13 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti setAcpConfigLoadedRequestKey(null); setAcpConfigError("Failed to load agent configuration"); } + setIsAcpSessionConfigLoading(false); clearAcpConfigLoadingRequest(requestKey); return; } setAcpConfigLoadedRequestKey(null); + setIsAcpSessionConfigLoading(false); const cacheKey = acpConfigCacheKey; const cachedState = getCachedAcpConfigState(cacheKey); setAcpConfigState(cachedState); diff --git a/packages/ui/src/pages/ChatPage.tsx b/packages/ui/src/pages/ChatPage.tsx index 7eb4f55a3..9f1afefa4 100644 --- a/packages/ui/src/pages/ChatPage.tsx +++ b/packages/ui/src/pages/ChatPage.tsx @@ -22,7 +22,7 @@ import { createChatClient } from "../../api/ChatClient"; import { createModelClient } from "#api/ModelClient"; import { useUiSettingsStore } from "#/stores/uiSettingsStore"; import { sessionStore, fetchSessions, selectSession, applyRestoredSession } from "#/stores/ui/session"; -import { useMessageStore } from "#/stores/ui/message"; +import { useMessageStore, addOptimisticUserMessage } from "#/stores/ui/message"; import { agentPlanStore } from "#/stores/ui/agentPlan"; import { agentStore } from "#/stores/ui/agent"; @@ -106,6 +106,7 @@ function ChatPage({ sessionId }: ChatPageProps) { insertWorkspaceReference: (targetPath: string) => boolean; getPendingSkillsSnapshot: () => string[]; focusInput: () => void; + clearInput: () => void; } | null>(null); const chatSearchBarRef = useRef<{ focusInput: () => void; selectInput: () => void } | null>(null); @@ -123,6 +124,7 @@ function ChatPage({ sessionId }: ChatPageProps) { const [attachedFiles, setAttachedFiles] = useState([]); const isVoiceInputEnabled = false; const [isHandlingInteraction, setIsHandlingInteraction] = useState(false); + const [isCancelling, setIsCancelling] = useState(false); const spotlightJumpTimerRef = useRef(null); const scrollReadFrameRef = useRef(null); @@ -443,15 +445,22 @@ function ChatPage({ sessionId }: ChatPageProps) { if (!isGenerating) setMessage(""); return; } - if (isGenerating) { - await queueInput(sessionId, { text, files }); - } else { - clearPlanSnapshot(sessionId); - await chatClient.sendMessage(sessionId, { text, files }); + try { + if (isGenerating) { + await queueInput(sessionId, { text, files }); + } else { + clearPlanSnapshot(sessionId); + addOptimisticUserMessage(sessionId, text, files); + await chatClient.sendMessage(sessionId, { text, files }); + } + } catch (error) { + console.error("[ChatPage] send message failed:", error); + } finally { + setMessage(""); + setAttachedFiles([]); + chatInputRef.current?.clearInput(); + schedulePostSubmitScrollToBottom(); } - setMessage(""); - setAttachedFiles([]); - schedulePostSubmitScrollToBottom(); }, [ isReadOnlySession, isAcpWorkdirMissing, @@ -476,14 +485,21 @@ function ChatPage({ sessionId }: ChatPageProps) { if (!text) return; if (await handleManualCompactionCommand(text)) return; const files = await prepareFilesForCurrentModel([...attachedFiles]); - if (isGenerating) { - await queueInput(sessionId, { text, files }); - } else { - clearPlanSnapshot(sessionId); - await chatClient.sendMessage(sessionId, { text, files }); + try { + if (isGenerating) { + await queueInput(sessionId, { text, files }); + } else { + clearPlanSnapshot(sessionId); + addOptimisticUserMessage(sessionId, text, files); + await chatClient.sendMessage(sessionId, { text, files }); + } + } catch (error) { + console.error("[ChatPage] send command failed:", error); + } finally { + setAttachedFiles([]); + chatInputRef.current?.clearInput(); + schedulePostSubmitScrollToBottom(); } - setAttachedFiles([]); - schedulePostSubmitScrollToBottom(); }, [ isReadOnlySession, @@ -531,9 +547,16 @@ function ChatPage({ sessionId }: ChatPageProps) { if (!text && files.length === 0) return; if (await handleManualCompactionCommand(text)) return; clearPlanSnapshot(sessionId); - await chatClient.steerActiveTurn(sessionId, { text, files }); - setMessage(""); - setAttachedFiles([]); + addOptimisticUserMessage(sessionId, text, files); + try { + await chatClient.steerActiveTurn(sessionId, { text, files }); + } catch (error) { + console.error("[ChatPage] steer failed:", error); + } finally { + setMessage(""); + setAttachedFiles([]); + chatInputRef.current?.clearInput(); + } }, [ isReadOnlySession, isAcpWorkdirMissing, @@ -584,13 +607,19 @@ function ChatPage({ sessionId }: ChatPageProps) { ); const onStop = useCallback(async () => { - if (isReadOnlySession || !isGenerating) return; + if (isReadOnlySession || !isGenerating || isCancelling) return; + setIsCancelling(true); try { await chatClient.stopStream({ sessionId }); } catch (error) { console.error("[ChatPage] cancel generation failed:", error); + setIsCancelling(false); } - }, [isReadOnlySession, isGenerating, sessionId, chatClient]); + }, [isReadOnlySession, isGenerating, isCancelling, sessionId, chatClient]); + + useEffect(() => { + if (!isGenerating) setIsCancelling(false); + }, [isGenerating]); const onMessageRetry = useCallback( async (messageId: string) => { @@ -1164,6 +1193,7 @@ function ChatPage({ sessionId }: ChatPageProps) { toolbar={ boolean; getPendingSkillsSnapshot: () => string[]; focusInput: () => void; + clearInput: () => void; } | null>(null); const [acpDraftSessionId, setAcpDraftSessionId] = useState(null); const [acpDraftModelSelection, setAcpDraftModelSelection] = useState(null); @@ -814,7 +815,6 @@ function NewThreadPage() { )}
- {/* @ts-expect-error - Complex type intersection issue */} {}} - > - {}} - onSteer={() => {}} - onStop={() => {}} - showVoiceInput={isVoiceInputEnabled} - isVoiceInputListening={false} - isVoiceInputTranscribing={false} - sendDisabled={isAcpWorkdirUnavailable || !isDaemonConnected || !message.trim()} - onAttach={onAttach} - onVoiceInput={() => {}} - onSend={onSubmit} - /> - + toolbar={ + {}} + onSteer={() => {}} + onStop={() => {}} + showVoiceInput={isVoiceInputEnabled} + isVoiceInputListening={false} + isVoiceInputTranscribing={false} + sendDisabled={isAcpWorkdirUnavailable || !isDaemonConnected || !message.trim()} + onAttach={onAttach} + onVoiceInput={() => {}} + onSend={onSubmit} + /> + } + />
diff --git a/packages/ui/src/stores/ui/message.ts b/packages/ui/src/stores/ui/message.ts index 6ff30786c..1c6389b8c 100644 --- a/packages/ui/src/stores/ui/message.ts +++ b/packages/ui/src/stores/ui/message.ts @@ -394,7 +394,7 @@ async function getMessage(id: string): Promise { return null; } -function addOptimisticUserMessage(sessionId: string, text: string, files: MessageFile[] = []): void { +export function addOptimisticUserMessage(sessionId: string, text: string, files: MessageFile[] = []): void { const id = `__optimistic_user_${Date.now()}`; const record: ChatMessageRecord = { id, From a6a3f172b3d9ae0059a190c536ff443e70d61d7c Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 15:06:33 -0400 Subject: [PATCH 03/28] chore: checkpoint ui + base-ui migration skill --- .agents/skills/migrate-radix-to-base/SKILL.md | 173 +++++++ .../migrate-radix-to-base/class-mapping.md | 62 +++ .../migrate-radix-to-base/consumer-props.md | 58 +++ .../migrate-radix-to-base/disclosure.md | 353 ++++++++++++++ .../migrate-radix-to-base/display-misc.md | 410 ++++++++++++++++ .../migrate-radix-to-base/form-controls.md | 390 +++++++++++++++ .agents/skills/migrate-radix-to-base/menus.md | 409 ++++++++++++++++ .../skills/migrate-radix-to-base/overlays.md | 459 ++++++++++++++++++ .../universal-patterns.md | 286 +++++++++++ .../migrate-radix-to-base/wrapper-shapes.md | 110 +++++ .../skills/migrate-radix-to-base/SKILL.md | 173 +++++++ .../migrate-radix-to-base/class-mapping.md | 62 +++ .../migrate-radix-to-base/consumer-props.md | 58 +++ .../migrate-radix-to-base/disclosure.md | 353 ++++++++++++++ .../migrate-radix-to-base/display-misc.md | 410 ++++++++++++++++ .../migrate-radix-to-base/form-controls.md | 390 +++++++++++++++ .../skills/migrate-radix-to-base/menus.md | 409 ++++++++++++++++ .../skills/migrate-radix-to-base/overlays.md | 459 ++++++++++++++++++ .../universal-patterns.md | 286 +++++++++++ .../migrate-radix-to-base/wrapper-shapes.md | 110 +++++ build/artifact-demo/nebula-flow.html | 367 ++++++++++++++ build/artifact-demo/neon-snake.html | 396 +++++++++++++++ packages/ui/src/components/AppBar.tsx | 11 +- packages/ui/src/routeTree.gen.ts | 420 ++++++++-------- skills-lock.json | 6 + 25 files changed, 6406 insertions(+), 214 deletions(-) create mode 100644 .agents/skills/migrate-radix-to-base/SKILL.md create mode 100644 .agents/skills/migrate-radix-to-base/class-mapping.md create mode 100644 .agents/skills/migrate-radix-to-base/consumer-props.md create mode 100644 .agents/skills/migrate-radix-to-base/disclosure.md create mode 100644 .agents/skills/migrate-radix-to-base/display-misc.md create mode 100644 .agents/skills/migrate-radix-to-base/form-controls.md create mode 100644 .agents/skills/migrate-radix-to-base/menus.md create mode 100644 .agents/skills/migrate-radix-to-base/overlays.md create mode 100644 .agents/skills/migrate-radix-to-base/universal-patterns.md create mode 100644 .agents/skills/migrate-radix-to-base/wrapper-shapes.md create mode 100644 .commandcode/skills/migrate-radix-to-base/SKILL.md create mode 100644 .commandcode/skills/migrate-radix-to-base/class-mapping.md create mode 100644 .commandcode/skills/migrate-radix-to-base/consumer-props.md create mode 100644 .commandcode/skills/migrate-radix-to-base/disclosure.md create mode 100644 .commandcode/skills/migrate-radix-to-base/display-misc.md create mode 100644 .commandcode/skills/migrate-radix-to-base/form-controls.md create mode 100644 .commandcode/skills/migrate-radix-to-base/menus.md create mode 100644 .commandcode/skills/migrate-radix-to-base/overlays.md create mode 100644 .commandcode/skills/migrate-radix-to-base/universal-patterns.md create mode 100644 .commandcode/skills/migrate-radix-to-base/wrapper-shapes.md create mode 100644 build/artifact-demo/nebula-flow.html create mode 100644 build/artifact-demo/neon-snake.html diff --git a/.agents/skills/migrate-radix-to-base/SKILL.md b/.agents/skills/migrate-radix-to-base/SKILL.md new file mode 100644 index 000000000..5eb5dc570 --- /dev/null +++ b/.agents/skills/migrate-radix-to-base/SKILL.md @@ -0,0 +1,173 @@ +--- +name: migrate-radix-to-base +description: Migrates React projects and components from Radix UI to Base UI. Use when asked to migrate from radix, move to base-ui, convert radix primitives, or switch a shadcn project's base library. Handles single components ("migrate accordion") and whole projects. +--- + +# Radix UI -> Base UI migration + +You migrate shadcn wrappers, hand-rolled radix compositions, and their +consumers to `@base-ui/react`, keeping the project buildable at every step. +Be precise; never guess a mapping. When a prop or part is not in these +reference files, check `node_modules/@base-ui/react/**/*.d.ts` before +transforming, and record gaps in the report. + +## Preflight (always) + +1. `npx shadcn@latest info --json` (or the project's runner): gives the + current base, STYLE (e.g. `radix-lyra`), tailwind version, aliases, + installed components, and package manager. Trust it over inference. +2. Detect the package manager (packageManager field / lockfile: + pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json) and use IT for + every install. Never leave a stale lockfile. +3. Require a clean git tree; work on a branch; one commit per component. +4. Baseline check BEFORE touching dependencies: run the project's + typecheck/build so pre-existing failures are never attributed to you. +5. Install `@base-ui/react` alongside radix. Radix packages are removed only + after the LAST component is migrated (both coexist fine). + +## Strategy: golden pair first, transformation engine second + +- **Golden pair via the CLI (preferred).** If the project is shadcn with a + known style (`radix- + + + + +
+

✦ Nebula

+

interactive flow field — move your cursor

+
+ +
-- fps
-- particles
+ +
+

Controls

+
2200
+
1.1×
+
1.4×
+
+
+ + + + +
+
+
+ + + + +
+
space pause · r reset · move cursor to sculpt the field
+
+ +
Paused
+ + + + diff --git a/build/artifact-demo/neon-snake.html b/build/artifact-demo/neon-snake.html new file mode 100644 index 000000000..470fe3062 --- /dev/null +++ b/build/artifact-demo/neon-snake.html @@ -0,0 +1,396 @@ + + + + + +NEON SNAKE + + + + + + + +
+
+

Neon Snake

+
grow · glow · don't bite yourself
+
↑ ↓ ← →W A S DP pauseR restart
+
PRESS SPACE TO START
+
+
+ + + + + + + + diff --git a/packages/ui/src/components/AppBar.tsx b/packages/ui/src/components/AppBar.tsx index 4beda059a..5e09f61fd 100644 --- a/packages/ui/src/components/AppBar.tsx +++ b/packages/ui/src/components/AppBar.tsx @@ -8,6 +8,7 @@ import MaximizeIcon from "./icons/MaximizeIcon"; import RestoreIcon from "./icons/RestoreIcon"; import CloseIcon from "./icons/CloseIcon"; import MinimizeIcon from "./icons/MinimizeIcon"; +import { isBrowserMode } from "#api/runtimeKind"; const windowClient = createWindowClient(); const deviceClient = createDeviceClient(); @@ -22,8 +23,10 @@ export default function AppBar() { const [stopListener, setStopListener] = useState<(() => void) | null>(null); const routeName = window.location.pathname; + const isBrowser = isBrowserMode(); + const showUpdateButton = useMemo( - () => routeName !== "welcome" && upgrade.shouldShowTopbarInstallButton, + () => !isBrowser || (routeName !== "welcome" && upgrade.shouldShowTopbarInstallButton), [routeName, upgrade.shouldShowTopbarInstallButton], ); @@ -84,7 +87,7 @@ export default function AppBar() { )}
- {!isMacOS && ( + {(!isMacOS || !isBrowser) && ( )} - {!isMacOS && ( + {(!isMacOS || !isBrowser) && (
diff --git a/packages/ui/src/stores/artifact.ts b/packages/ui/src/stores/artifact.ts index 5caac4236..2b15a5317 100644 --- a/packages/ui/src/stores/artifact.ts +++ b/packages/ui/src/stores/artifact.ts @@ -141,7 +141,7 @@ const updateArtifactContent = (updates: Partial) => { } }; -const syncArtifact = (artifact: ArtifactState, messageId: string, threadId: string) => { +export const syncArtifact = (artifact: ArtifactState, messageId: string, threadId: string) => { if (!artifactStore.state.currentArtifact || validateContext(messageId, threadId)) { artifactStore.setState((prev) => ({ ...prev, @@ -152,7 +152,7 @@ const syncArtifact = (artifact: ArtifactState, messageId: string, threadId: stri } }; -const completeArtifact = (artifact: ArtifactState, messageId: string, threadId: string) => { +export const completeArtifact = (artifact: ArtifactState, messageId: string, threadId: string) => { const contextKey = makeContextKey(artifact.id, messageId, threadId); const panelWasHidden = !sidepanelStore.state.open; const currentMatches = From b47aec4ef340b49be57b1041f49f7f7094e8ad54 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 19:34:24 -0400 Subject: [PATCH 09/28] style(ui): format separator --- packages/ui/shadcn/components/ui/separator.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/ui/shadcn/components/ui/separator.tsx b/packages/ui/shadcn/components/ui/separator.tsx index ef7f5989e..e70e1b22f 100644 --- a/packages/ui/shadcn/components/ui/separator.tsx +++ b/packages/ui/shadcn/components/ui/separator.tsx @@ -2,11 +2,7 @@ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"; import { cn } from "#shadcn/lib/utils"; -function Separator({ - className, - orientation = "horizontal", - ...props -}: SeparatorPrimitive.Props) { +function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) { return ( Date: Mon, 3 Aug 2026 19:41:02 -0400 Subject: [PATCH 10/28] perf(ui): stop rate-limit interval churn, stabilize row callbacks --- .../ui/src/components/chat/MessageList.tsx | 8 +++--- .../components/message/MessageBlockAction.tsx | 8 ++++-- .../message/MessageItemAssistant.tsx | 11 +++++--- packages/ui/src/pages/ChatPage.tsx | 27 +++++++++++-------- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index 4f929ecc1..c97c907d6 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -1,4 +1,4 @@ -import { type FC, useCallback, useMemo } from "react"; +import { type FC, useCallback, useMemo, useRef } from "react"; import { MessageBlockAction } from "#/components/message/MessageBlockAction"; import { useMessageCapture } from "#/composables/message/useMessageCapture"; import { useThemeStore } from "#/stores/theme"; @@ -43,6 +43,8 @@ const MessageList: FC = ({ const allRenderedMessages = useMemo(() => messages, [messages]); const displayMessages = useMemo(() => allRenderedMessages, [allRenderedMessages]); const { isCapturing, captureMessage } = useMessageCapture(themeStore.isDark); + const captureMessageRef = useRef(captureMessage); + captureMessageRef.current = captureMessage; const resolveCaptureParentId = useCallback( (messageId: string, parentId?: string): string | undefined => { @@ -70,9 +72,9 @@ const MessageList: FC = ({ modelInfo: { model_name: string; model_provider: string }, ) => { const resolvedParentId = resolveCaptureParentId(messageId, parentId); - await captureMessage({ messageId, parentId: resolvedParentId, fromTop, modelInfo }); + await captureMessageRef.current({ messageId, parentId: resolvedParentId, fromTop, modelInfo }); }, - [resolveCaptureParentId, captureMessage], + [resolveCaptureParentId], ); return ( diff --git a/packages/ui/src/components/message/MessageBlockAction.tsx b/packages/ui/src/components/message/MessageBlockAction.tsx index 2d08c5c8a..e3926f121 100644 --- a/packages/ui/src/components/message/MessageBlockAction.tsx +++ b/packages/ui/src/components/message/MessageBlockAction.tsx @@ -22,6 +22,7 @@ export const MessageBlockAction: FC = ({ const progressTimer = useRef(null); const isReadOnly = isReadOnlyProp === true; const isRateLimitBlock = block.action_type === "rate_limit"; + const isRateLimitActive = isRateLimitBlock && (block.status === "loading" || block.status === "pending"); const elapsedSeconds = useMemo(() => { if (!isRateLimitBlock) return 0; @@ -40,7 +41,10 @@ export const MessageBlockAction: FC = ({ }; useEffect(() => { - if (isRateLimitBlock) { + if (isRateLimitActive) { + // Don't keep historical/stale rate-limit blocks ticking forever — they + // accumulate across a long conversation and re-render every second. + if (Date.now() - block.timestamp > 180_000) return; progressTimer.current = window.setInterval(() => { setCurrentTime(Date.now()); }, 1000); @@ -50,7 +54,7 @@ export const MessageBlockAction: FC = ({ clearInterval(progressTimer.current); } }; - }, [isRateLimitBlock]); + }, [isRateLimitActive, block.timestamp]); return (
diff --git a/packages/ui/src/components/message/MessageItemAssistant.tsx b/packages/ui/src/components/message/MessageItemAssistant.tsx index ddd73bdd9..d98ec357b 100644 --- a/packages/ui/src/components/message/MessageItemAssistant.tsx +++ b/packages/ui/src/components/message/MessageItemAssistant.tsx @@ -268,10 +268,13 @@ const MessageItemAssistant = forwardRef { - if (isReadOnly) return; - props.onContinue?.(conversationId, messageId); - }; + const handleBlockContinue = useCallback( + (conversationId: string, messageId: string) => { + if (isReadOnly) return; + props.onContinue?.(conversationId, messageId); + }, + [isReadOnly, props.onContinue], + ); const handleBlockSwitchProvider = () => { if (isReadOnly) return; diff --git a/packages/ui/src/pages/ChatPage.tsx b/packages/ui/src/pages/ChatPage.tsx index 9f1afefa4..3d62f8c47 100644 --- a/packages/ui/src/pages/ChatPage.tsx +++ b/packages/ui/src/pages/ChatPage.tsx @@ -914,17 +914,22 @@ function ChatPage({ sessionId }: ChatPageProps) { const messageWindow = useMessageWindow(displayMessages); - const onMessageMeasure = useCallback( - (payload: { messageId: string; height: number }) => { - const isBottomFollowing = scrollMode === "initial-bottom" || scrollMode === "auto-follow"; - const delta = messageWindow.setMeasuredHeight(payload.messageId, payload.height); - if (delta === 0) return; - if (isBottomFollowing) { - scrollToBottom(scrollMode === "initial-bottom"); - } - }, - [scrollMode, messageWindow, scrollToBottom], - ); + const scrollModeRef = useRef(scrollMode); + scrollModeRef.current = scrollMode; + const messageWindowRef = useRef(messageWindow); + messageWindowRef.current = messageWindow; + const scrollToBottomRef = useRef(scrollToBottom); + scrollToBottomRef.current = scrollToBottom; + + const onMessageMeasure = useCallback((payload: { messageId: string; height: number }) => { + const mode = scrollModeRef.current; + const isBottomFollowing = mode === "initial-bottom" || mode === "auto-follow"; + const delta = messageWindowRef.current.setMeasuredHeight(payload.messageId, payload.height); + if (delta === 0) return; + if (isBottomFollowing) { + scrollToBottomRef.current(mode === "initial-bottom"); + } + }, []); function resolveAssistantModelName(modelId: string): string { if (!modelId) return "Assistant"; From ad35630ca15cf385d0dd897ef774be1dda9fb068 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 22:30:12 -0400 Subject: [PATCH 11/28] perf(ui): memo MessageListRow + MessageBlockContent --- packages/ui/src/components/chat/MessageListRow.tsx | 7 +++++-- packages/ui/src/components/message/MessageBlockContent.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/chat/MessageListRow.tsx b/packages/ui/src/components/chat/MessageListRow.tsx index 0db02e362..64e864d0a 100644 --- a/packages/ui/src/components/chat/MessageListRow.tsx +++ b/packages/ui/src/components/chat/MessageListRow.tsx @@ -1,4 +1,4 @@ -import { type FC, useEffect, useRef, useCallback } from "react"; +import { type FC, useEffect, useRef, useCallback, memo } from "react"; import MessageItemAssistant from "#/components/message/MessageItemAssistant"; import MessageItemUser from "#/components/message/MessageItemUser"; import { @@ -29,7 +29,7 @@ interface MessageListRowProps { onMeasure: (payload: { messageId: string; height: number }) => void; } -const MessageListRow: FC = ({ +const MessageListRowBase: FC = ({ item, isGenerating = false, showTrace = false, @@ -155,4 +155,7 @@ const MessageListRow: FC = ({ ); }; +const MessageListRow = memo(MessageListRowBase); +MessageListRow.displayName = "MessageListRow"; + export default MessageListRow; diff --git a/packages/ui/src/components/message/MessageBlockContent.tsx b/packages/ui/src/components/message/MessageBlockContent.tsx index 557034b55..a0e926f00 100644 --- a/packages/ui/src/components/message/MessageBlockContent.tsx +++ b/packages/ui/src/components/message/MessageBlockContent.tsx @@ -1,4 +1,4 @@ -import { type FC, useEffect, useMemo, useRef } from "react"; +import { type FC, useEffect, useMemo, useRef, memo } from "react"; import { useBlockContent, type ProcessedPart } from "#/composables/useArtifacts"; import { completeArtifact, syncArtifact } from "#/stores/artifact"; import { ArtifactThinking } from "../artifacts/ArtifactThinking"; @@ -13,7 +13,7 @@ interface MessageBlockContentProps { threadId: string; } -export const MessageBlockContent: FC = ({ block, messageId, threadId }) => { +const MessageBlockContentBase: FC = ({ block, messageId, threadId }) => { const propsRef = useRef({ block, messageId, threadId }); propsRef.current = { block, messageId, threadId }; @@ -141,3 +141,5 @@ export const MessageBlockContent: FC = ({ block, messa ); }; + +export const MessageBlockContent = memo(MessageBlockContentBase); From 1d83591ee1a7ed323d725fb632beaaf85f3ed38d Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 23:03:15 -0400 Subject: [PATCH 12/28] feat(ui): setting to hide Continued indicator --- .../src/main/routes/settings/settingsAdapter.ts | 4 ++++ .../src/dispatch/settings/settingsAdapter.ts | 4 ++++ .../shared-contracts/src/routes/settings.routes.ts | 6 ++++++ packages/ui/settings/components/CommonSettings.tsx | 13 +++++++++++++ .../src/components/message/MessageBlockAction.tsx | 5 ++++- packages/ui/src/stores/uiSettingsStore.ts | 10 ++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/main/routes/settings/settingsAdapter.ts b/apps/desktop/src/main/routes/settings/settingsAdapter.ts index d2023a640..af24b460e 100644 --- a/apps/desktop/src/main/routes/settings/settingsAdapter.ts +++ b/apps/desktop/src/main/routes/settings/settingsAdapter.ts @@ -30,6 +30,7 @@ export const readSettingsSnapshot = (configPresenter: IConfigPresenter): Setting traceDebugEnabled: configPresenter.getSetting("traceDebugEnabled") ?? false, copyWithCotEnabled: configPresenter.getCopyWithCotEnabled(), loggingEnabled: configPresenter.getLoggingEnabled(), + showContinueIndicator: configPresenter.getSetting("showContinueIndicator") ?? false, }); export const pickSettingsSnapshot = ( @@ -93,6 +94,9 @@ export const applySettingChange = (configPresenter: IConfigPresenter, change: Se case "loggingEnabled": configPresenter.setLoggingEnabled(change.value); return; + case "showContinueIndicator": + configPresenter.setSetting("showContinueIndicator", change.value); + return; } }; diff --git a/packages/backend-core/src/dispatch/settings/settingsAdapter.ts b/packages/backend-core/src/dispatch/settings/settingsAdapter.ts index d2023a640..af24b460e 100644 --- a/packages/backend-core/src/dispatch/settings/settingsAdapter.ts +++ b/packages/backend-core/src/dispatch/settings/settingsAdapter.ts @@ -30,6 +30,7 @@ export const readSettingsSnapshot = (configPresenter: IConfigPresenter): Setting traceDebugEnabled: configPresenter.getSetting("traceDebugEnabled") ?? false, copyWithCotEnabled: configPresenter.getCopyWithCotEnabled(), loggingEnabled: configPresenter.getLoggingEnabled(), + showContinueIndicator: configPresenter.getSetting("showContinueIndicator") ?? false, }); export const pickSettingsSnapshot = ( @@ -93,6 +94,9 @@ export const applySettingChange = (configPresenter: IConfigPresenter, change: Se case "loggingEnabled": configPresenter.setLoggingEnabled(change.value); return; + case "showContinueIndicator": + configPresenter.setSetting("showContinueIndicator", change.value); + return; } }; diff --git a/packages/shared-contracts/src/routes/settings.routes.ts b/packages/shared-contracts/src/routes/settings.routes.ts index 662463953..eda79c765 100644 --- a/packages/shared-contracts/src/routes/settings.routes.ts +++ b/packages/shared-contracts/src/routes/settings.routes.ts @@ -17,6 +17,7 @@ export const SETTINGS_KEYS = [ "traceDebugEnabled", "copyWithCotEnabled", "loggingEnabled", + "showContinueIndicator", ] as const; export const SettingsKeySchema = zod.enum(SETTINGS_KEYS); @@ -37,6 +38,7 @@ export const SettingsSnapshotValuesSchema = zod.object({ traceDebugEnabled: zod.boolean(), copyWithCotEnabled: zod.boolean(), loggingEnabled: zod.boolean(), + showContinueIndicator: zod.boolean(), }); export const SettingsChangeSchema = zod.discriminatedUnion("key", [ @@ -100,6 +102,10 @@ export const SettingsChangeSchema = zod.discriminatedUnion("key", [ key: zod.literal("loggingEnabled"), value: zod.boolean(), }), + zod.object({ + key: zod.literal("showContinueIndicator"), + value: zod.boolean(), + }), ]); export const settingsGetSnapshotRoute = defineRouteContract({ diff --git a/packages/ui/settings/components/CommonSettings.tsx b/packages/ui/settings/components/CommonSettings.tsx index eef4227ae..55fac3845 100644 --- a/packages/ui/settings/components/CommonSettings.tsx +++ b/packages/ui/settings/components/CommonSettings.tsx @@ -7,6 +7,7 @@ import { setLaunchAtLoginEnabled, setCopyWithCotEnabled, setTraceDebugEnabled, + setShowContinueIndicator, } from "#/stores/uiSettingsStore"; import ProxySettingsSection from "./common/ProxySettingsSection"; import LoggingSettingsSection from "./common/LoggingSettingsSection"; @@ -20,6 +21,10 @@ export default function CommonSettings() { const autoScrollEnabled = useMemo(() => uiSettingsStore.autoScrollEnabled, [uiSettingsStore.autoScrollEnabled]); const copyWithCotEnabled = useMemo(() => uiSettingsStore.copyWithCotEnabled, [uiSettingsStore.copyWithCotEnabled]); const traceDebugEnabled = useMemo(() => uiSettingsStore.traceDebugEnabled, [uiSettingsStore.traceDebugEnabled]); + const showContinueIndicator = useMemo( + () => uiSettingsStore.showContinueIndicator, + [uiSettingsStore.showContinueIndicator], + ); const launchAtLoginEnabled = useMemo( () => uiSettingsStore.launchAtLoginEnabled, [uiSettingsStore.launchAtLoginEnabled], @@ -29,6 +34,7 @@ export default function CommonSettings() { const handleLaunchAtLoginChange = useCallback((value: boolean) => setLaunchAtLoginEnabled(value), []); const handleCopyWithCotChange = useCallback((value: boolean) => setCopyWithCotEnabled(value), []); const handleTraceDebugChange = useCallback((value: boolean) => setTraceDebugEnabled(value), []); + const handleShowContinueIndicatorChange = useCallback((value: boolean) => setShowContinueIndicator(value), []); return ( @@ -63,6 +69,13 @@ export default function CommonSettings() { modelValue={traceDebugEnabled} onUpdateModelValue={handleTraceDebugChange} /> +
diff --git a/packages/ui/src/components/message/MessageBlockAction.tsx b/packages/ui/src/components/message/MessageBlockAction.tsx index e3926f121..35cf67a91 100644 --- a/packages/ui/src/components/message/MessageBlockAction.tsx +++ b/packages/ui/src/components/message/MessageBlockAction.tsx @@ -1,7 +1,9 @@ import { type FC, useState, useEffect, useMemo, useRef } from "react"; +import { useSelector } from "@tanstack/react-store"; import { Icon } from "@iconify/react"; import { Button } from "#shadcn/components/ui/button"; import type { DisplayAssistantMessageBlock } from "#/components/chat/messageListItems"; +import { uiSettingsStore } from "#/stores/uiSettingsStore"; interface MessageBlockActionProps { messageId: string; @@ -23,6 +25,7 @@ export const MessageBlockAction: FC = ({ const isReadOnly = isReadOnlyProp === true; const isRateLimitBlock = block.action_type === "rate_limit"; const isRateLimitActive = isRateLimitBlock && (block.status === "loading" || block.status === "pending"); + const showContinueIndicator = useSelector(uiSettingsStore, (s) => s.showContinueIndicator); const elapsedSeconds = useMemo(() => { if (!isRateLimitBlock) return 0; @@ -88,7 +91,7 @@ export const MessageBlockAction: FC = ({
) : null} - {!block.extra?.needContinue && block.action_type !== "rate_limit" && ( + {!block.extra?.needContinue && block.action_type !== "rate_limit" && showContinueIndicator && (
Continued diff --git a/packages/ui/src/stores/uiSettingsStore.ts b/packages/ui/src/stores/uiSettingsStore.ts index 098cbcca8..bab515a16 100644 --- a/packages/ui/src/stores/uiSettingsStore.ts +++ b/packages/ui/src/stores/uiSettingsStore.ts @@ -38,6 +38,7 @@ export const uiSettingsStore = new Store({ traceDebugEnabled: false, notificationsEnabled: true, loggingEnabled: false, + showContinueIndicator: false, }); export const getFontSizeClass = (fontSizeLevel?: number) => @@ -95,6 +96,9 @@ const applySettingsValues = (values: Partial) => { if (typeof values.loggingEnabled === "boolean") { patch.loggingEnabled = values.loggingEnabled; } + if (typeof values.showContinueIndicator === "boolean") { + patch.showContinueIndicator = values.showContinueIndicator; + } if (Object.keys(patch).length > 0) { uiSettingsStore.setState((s) => ({ ...s, ...patch })); } @@ -246,6 +250,12 @@ export const setLoggingEnabled = async (enabled: boolean) => { await updateSettings([{ key: "loggingEnabled", value: nextValue }]); }; +export const setShowContinueIndicator = async (enabled: boolean) => { + const nextValue = Boolean(enabled); + uiSettingsStore.setState((s) => ({ ...s, showContinueIndicator: nextValue })); + await updateSettings([{ key: "showContinueIndicator", value: nextValue }]); +}; + const setupListeners = () => { if (unsubscribeFromSettings) return; unsubscribeFromSettings = settingsClient.onChanged((payload) => { From 958726e2a0dcc6929a500b8423e77eb54930c82c Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Mon, 3 Aug 2026 23:03:54 -0400 Subject: [PATCH 13/28] fix(ui): adjust AgentAvatar size for consistency --- packages/ui/src/components/message/MessageItemAssistant.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/message/MessageItemAssistant.tsx b/packages/ui/src/components/message/MessageItemAssistant.tsx index d98ec357b..b241eec48 100644 --- a/packages/ui/src/components/message/MessageItemAssistant.tsx +++ b/packages/ui/src/components/message/MessageItemAssistant.tsx @@ -374,7 +374,7 @@ const MessageItemAssistant = forwardRef { const agent = agentStore.state.agents.find((a) => a.id === currentMessage.model_id); return agent ? ( - + ) : ( ); From ec341b0592556db08c6d5dd9ce3bc5b83596a0e8 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Tue, 4 Aug 2026 10:41:34 -0400 Subject: [PATCH 14/28] fix: address PR review (compiler refs, interval, steer) --- .../daemon/src/host/acp-provider-execution.ts | 6 ++++++ .../ui/src/components/chat/MessageList.tsx | 6 ++++-- .../composables/useChatStatusBarAcpConfig.ts | 1 + .../components/message/MessageBlockAction.tsx | 21 ++++++++++++------- .../message/MessageItemAssistant.tsx | 4 +++- packages/ui/src/pages/ChatPage.tsx | 10 +++++---- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/apps/daemon/src/host/acp-provider-execution.ts b/apps/daemon/src/host/acp-provider-execution.ts index b0d2c5367..6cc3bfb14 100644 --- a/apps/daemon/src/host/acp-provider-execution.ts +++ b/apps/daemon/src/host/acp-provider-execution.ts @@ -560,6 +560,12 @@ export class AcpProviderExecutionPort implements ProviderExecutionPort { active.controller.abort(); + for (const [toolCallId, pending] of this.pendingPermissions) { + if (pending.sessionId !== sessionId) continue; + this.pendingPermissions.delete(toolCallId); + pending.resolve({ outcome: { outcome: "cancelled" } }); + } + try { const runtime = await this.getRuntime(); const session = runtime.sessionManager.getSession(sessionId); diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index c97c907d6..40e0d9443 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -1,4 +1,4 @@ -import { type FC, useCallback, useMemo, useRef } from "react"; +import { type FC, useCallback, useEffect, useMemo, useRef } from "react"; import { MessageBlockAction } from "#/components/message/MessageBlockAction"; import { useMessageCapture } from "#/composables/message/useMessageCapture"; import { useThemeStore } from "#/stores/theme"; @@ -44,7 +44,9 @@ const MessageList: FC = ({ const displayMessages = useMemo(() => allRenderedMessages, [allRenderedMessages]); const { isCapturing, captureMessage } = useMessageCapture(themeStore.isDark); const captureMessageRef = useRef(captureMessage); - captureMessageRef.current = captureMessage; + useEffect(() => { + captureMessageRef.current = captureMessage; + }); const resolveCaptureParentId = useCallback( (messageId: string, parentId?: string): string | undefined => { diff --git a/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts b/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts index 8de4a5e38..c3f3c2f3c 100644 --- a/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts +++ b/packages/ui/src/components/chat/composables/useChatStatusBarAcpConfig.ts @@ -466,6 +466,7 @@ export function useChatStatusBarAcpConfig(options: UseChatStatusBarAcpConfigOpti setAcpConfigState(payload.configState); setAcpConfigLoadedRequestKey(`session:${conversationId}`); setAcpConfigError(null); + setIsAcpSessionConfigLoading(false); clearAcpConfigLoadingRequest(`session:${conversationId}`); return; } diff --git a/packages/ui/src/components/message/MessageBlockAction.tsx b/packages/ui/src/components/message/MessageBlockAction.tsx index 35cf67a91..d41eee547 100644 --- a/packages/ui/src/components/message/MessageBlockAction.tsx +++ b/packages/ui/src/components/message/MessageBlockAction.tsx @@ -44,14 +44,19 @@ export const MessageBlockAction: FC = ({ }; useEffect(() => { - if (isRateLimitActive) { - // Don't keep historical/stale rate-limit blocks ticking forever — they - // accumulate across a long conversation and re-render every second. - if (Date.now() - block.timestamp > 180_000) return; - progressTimer.current = window.setInterval(() => { - setCurrentTime(Date.now()); - }, 1000); - } + if (!isRateLimitActive) return; + if (Date.now() - block.timestamp > 180_000) return; + + progressTimer.current = window.setInterval(() => { + setCurrentTime(Date.now()); + if (Date.now() - block.timestamp > 180_000) { + if (progressTimer.current) { + clearInterval(progressTimer.current); + progressTimer.current = null; + } + } + }, 1000); + return () => { if (progressTimer.current) { clearInterval(progressTimer.current); diff --git a/packages/ui/src/components/message/MessageItemAssistant.tsx b/packages/ui/src/components/message/MessageItemAssistant.tsx index b241eec48..9928299a4 100644 --- a/packages/ui/src/components/message/MessageItemAssistant.tsx +++ b/packages/ui/src/components/message/MessageItemAssistant.tsx @@ -342,7 +342,9 @@ const MessageItemAssistant = forwardRef { + handleActionRef.current = handleAction; + }); const toolbarHandlers = useMemo( () => ({ diff --git a/packages/ui/src/pages/ChatPage.tsx b/packages/ui/src/pages/ChatPage.tsx index 3d62f8c47..1f36ecde0 100644 --- a/packages/ui/src/pages/ChatPage.tsx +++ b/packages/ui/src/pages/ChatPage.tsx @@ -619,7 +619,7 @@ function ChatPage({ sessionId }: ChatPageProps) { useEffect(() => { if (!isGenerating) setIsCancelling(false); - }, [isGenerating]); + }, [isGenerating, sessionId]); const onMessageRetry = useCallback( async (messageId: string) => { @@ -915,11 +915,13 @@ function ChatPage({ sessionId }: ChatPageProps) { const messageWindow = useMessageWindow(displayMessages); const scrollModeRef = useRef(scrollMode); - scrollModeRef.current = scrollMode; const messageWindowRef = useRef(messageWindow); - messageWindowRef.current = messageWindow; const scrollToBottomRef = useRef(scrollToBottom); - scrollToBottomRef.current = scrollToBottom; + useEffect(() => { + scrollModeRef.current = scrollMode; + messageWindowRef.current = messageWindow; + scrollToBottomRef.current = scrollToBottom; + }); const onMessageMeasure = useCallback((payload: { messageId: string; height: number }) => { const mode = scrollModeRef.current; From 2a1107779a94c9254201c59a277cf49925aec3e4 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Tue, 4 Aug 2026 11:25:36 -0400 Subject: [PATCH 15/28] fix(ui): migrate alert-dialog, sweep asChild --- .../components/BuiltinKnowledgeSettings.tsx | 6 +-- .../ui/settings/components/DataSettings.tsx | 21 ++++++---- .../settings/components/KnowledgeFileItem.tsx | 40 ++++++++++-------- .../components/MemoryManagerPanel.tsx | 30 ++++++------- .../prompt/CustomPromptSettingsSection.tsx | 20 +++++---- .../prompt/SystemPromptSettingsSection.tsx | 20 +++++---- .../ui/shadcn/components/ui/alert-dialog.tsx | 42 +++++++++---------- 7 files changed, 97 insertions(+), 82 deletions(-) diff --git a/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx b/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx index b5e583dae..5bd54d002 100644 --- a/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx +++ b/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx @@ -164,10 +164,8 @@ export default function BuiltinKnowledgeSettings({ onShowDetail }: BuiltinKnowle - - + }> + diff --git a/packages/ui/settings/components/DataSettings.tsx b/packages/ui/settings/components/DataSettings.tsx index 1ca11523c..ab3d239f7 100644 --- a/packages/ui/settings/components/DataSettings.tsx +++ b/packages/ui/settings/components/DataSettings.tsx @@ -998,14 +998,21 @@ export default function DataSettings() {
- - + } + > + + {isClearingSandbox ? "Clearing..." : "Clear Sandbox"} diff --git a/packages/ui/settings/components/KnowledgeFileItem.tsx b/packages/ui/settings/components/KnowledgeFileItem.tsx index 4f1bbc789..a9bf99a7a 100644 --- a/packages/ui/settings/components/KnowledgeFileItem.tsx +++ b/packages/ui/settings/components/KnowledgeFileItem.tsx @@ -109,15 +109,17 @@ export default function KnowledgeFileItem({ file, onDelete, onReAdd }: Knowledge {file.status !== "processing" && ( - - + + } + > + @@ -133,15 +135,17 @@ export default function KnowledgeFileItem({ file, onDelete, onReAdd }: Knowledge )} - - + + } + > + diff --git a/packages/ui/settings/components/MemoryManagerPanel.tsx b/packages/ui/settings/components/MemoryManagerPanel.tsx index b08ca6015..cc3f2adc4 100644 --- a/packages/ui/settings/components/MemoryManagerPanel.tsx +++ b/packages/ui/settings/components/MemoryManagerPanel.tsx @@ -298,11 +298,11 @@ export function MemoryManagerPanel({ {memories.length > 0 && ( - - + } + > + + Clear all @@ -470,15 +470,17 @@ export function MemoryManagerPanel({
- - + + } + > + diff --git a/packages/ui/settings/components/prompt/CustomPromptSettingsSection.tsx b/packages/ui/settings/components/prompt/CustomPromptSettingsSection.tsx index 63345f806..03b641457 100644 --- a/packages/ui/settings/components/prompt/CustomPromptSettingsSection.tsx +++ b/packages/ui/settings/components/prompt/CustomPromptSettingsSection.tsx @@ -333,15 +333,17 @@ const CustomPromptSettingsSection = forwardRef - - + + } + > + diff --git a/packages/ui/settings/components/prompt/SystemPromptSettingsSection.tsx b/packages/ui/settings/components/prompt/SystemPromptSettingsSection.tsx index ad3e1ac27..1525e11de 100644 --- a/packages/ui/settings/components/prompt/SystemPromptSettingsSection.tsx +++ b/packages/ui/settings/components/prompt/SystemPromptSettingsSection.tsx @@ -246,15 +246,17 @@ export default function SystemPromptSettingsSection() { ) : ( - - + + } + > + + Delete diff --git a/packages/ui/shadcn/components/ui/alert-dialog.tsx b/packages/ui/shadcn/components/ui/alert-dialog.tsx index 668192cb7..93457672b 100644 --- a/packages/ui/shadcn/components/ui/alert-dialog.tsx +++ b/packages/ui/shadcn/components/ui/alert-dialog.tsx @@ -1,27 +1,27 @@ import * as React from "react"; -import { AlertDialog as AlertDialogPrimitive } from "radix-ui"; +import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; import { cn } from "#shadcn/lib/utils"; import { Button } from "#shadcn/components/ui/button"; -function AlertDialog({ ...props }: React.ComponentProps) { +function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { return ; } -function AlertDialogTrigger({ ...props }: React.ComponentProps) { +function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { return ; } -function AlertDialogPortal({ ...props }: React.ComponentProps) { +function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { return ; } -function AlertDialogOverlay({ className, ...props }: React.ComponentProps) { +function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { return ( - & { +}: AlertDialogPrimitive.Popup.Props & { size?: "default" | "sm"; }) { return ( - & - Pick, "variant" | "size">) { - return ( - - ); +}: React.ComponentProps & { + variant?: React.ComponentProps["variant"]; + size?: React.ComponentProps["size"]; +}) { + return + } + {...props} + /> ); } From 99c5eced58e903725f09a14148c4010bfc5e78ee Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Tue, 4 Aug 2026 16:52:43 -0400 Subject: [PATCH 16/28] fix(ui): move components.json, add shadcn alias --- {apps/desktop => packages/ui}/components.json | 12 ++++++------ packages/ui/shadcn/components/ui/switch.tsx | 7 ++----- packages/ui/tsconfig.app.json | 3 +++ packages/ui/vite.config.ts | 1 + 4 files changed, 12 insertions(+), 11 deletions(-) rename {apps/desktop => packages/ui}/components.json (62%) diff --git a/apps/desktop/components.json b/packages/ui/components.json similarity index 62% rename from apps/desktop/components.json rename to packages/ui/components.json index 2fc692625..581326132 100644 --- a/apps/desktop/components.json +++ b/packages/ui/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/renderer/src/assets/style.css", + "css": "src/assets/style.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" @@ -13,11 +13,11 @@ "iconLibrary": "lucide", "rtl": false, "aliases": { - "components": "#shadcn/components", - "utils": "#shadcn/lib/utils", - "ui": "#shadcn/components/ui", - "lib": "#shadcn/lib", - "hooks": "#shadcn/hooks" + "components": "shadcn/components", + "utils": "shadcn/lib/utils", + "ui": "shadcn/components/ui", + "lib": "shadcn/lib", + "hooks": "shadcn/hooks" }, "menuColor": "default", "menuAccent": "subtle", diff --git a/packages/ui/shadcn/components/ui/switch.tsx b/packages/ui/shadcn/components/ui/switch.tsx index d0a83c288..efd4bd485 100644 --- a/packages/ui/shadcn/components/ui/switch.tsx +++ b/packages/ui/shadcn/components/ui/switch.tsx @@ -1,7 +1,4 @@ -"use client"; - -import * as React from "react"; -import { Switch as SwitchPrimitive } from "radix-ui"; +import { Switch as SwitchPrimitive } from "@base-ui/react/switch"; import { cn } from "#shadcn/lib/utils"; @@ -9,7 +6,7 @@ function Switch({ className, size = "default", ...props -}: React.ComponentProps & { +}: SwitchPrimitive.Root.Props & { size?: "sm" | "default"; }) { return ( diff --git a/packages/ui/tsconfig.app.json b/packages/ui/tsconfig.app.json index 53bc669f1..f5c1cdf0e 100644 --- a/packages/ui/tsconfig.app.json +++ b/packages/ui/tsconfig.app.json @@ -48,6 +48,9 @@ "#shadcn/*": [ "./shadcn/*" ], + "shadcn/*": [ + "./shadcn/*" + ], "#settings/*": [ "./settings/*" ] diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index d1dca0982..bee77d2c7 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -53,6 +53,7 @@ export default defineConfig(({ mode, command }) => { { find: "@argos/shared", replacement: path.resolve(projectRoot, "..", "..", "packages", "shared", "src") }, { find: "#api", replacement: resolve("api") }, { find: "#shadcn", replacement: resolve("shadcn") }, + { find: "shadcn", replacement: resolve("shadcn") }, { find: "#settings", replacement: resolve("settings") }, ], }, From 11188bf10df7e0d6b2990bc0f1a4ddbea4c1dae2 Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Tue, 4 Aug 2026 17:35:57 -0400 Subject: [PATCH 17/28] fix(ui): sweep tooltip asChild to render prop --- .../ui/settings/components/AcpDiagnostics.tsx | 50 +-- .../ui/settings/components/AcpSettings.tsx | 104 +++--- .../BedrockProviderSettingsDetail.tsx | 2 +- .../components/BuiltinKnowledgeSettings.tsx | 10 +- .../components/DifyKnowledgeSettings.tsx | 10 +- .../components/FastGptKnowledgeSettings.tsx | 10 +- .../ui/settings/components/KnowledgeFile.tsx | 22 +- .../OllamaProviderSettingsDetail.tsx | 22 +- .../settings/components/ProviderApiConfig.tsx | 22 +- .../components/RagflowKnowledgeSettings.tsx | 10 +- packages/ui/shadcn/components/ui/tooltip.tsx | 52 +-- .../ui/src/components/ConnectionIndicator.tsx | 22 +- packages/ui/src/components/WindowSideBar.tsx | 198 ++++++------ .../src/components/chat/ChatInputToolbar.tsx | 158 +++++----- .../ui/src/components/chat/ChatStatusBar.tsx | 26 +- .../components/message/MessageBlockImage.tsx | 20 +- .../MessageBlockToolCallImagePreview.tsx | 20 +- .../src/components/message/MessageToolbar.tsx | 298 ++++++++++-------- packages/ui/src/pages/ChatPage.tsx | 2 +- packages/ui/src/pages/NewThreadPage.tsx | 2 +- 20 files changed, 574 insertions(+), 486 deletions(-) diff --git a/packages/ui/settings/components/AcpDiagnostics.tsx b/packages/ui/settings/components/AcpDiagnostics.tsx index 68f31933d..1a18055ce 100644 --- a/packages/ui/settings/components/AcpDiagnostics.tsx +++ b/packages/ui/settings/components/AcpDiagnostics.tsx @@ -316,32 +316,36 @@ export default function AcpDiagnostics({
- - - + void runDiagnostics()} + > + {probing ? : } + + } + /> {canRun ? checkLabel : `Enable ${agentName} before checking`} - - - + setManualDetailsOpen(!detailsOpen)} + > + + + } + /> {detailsLabel}
diff --git a/packages/ui/settings/components/AcpSettings.tsx b/packages/ui/settings/components/AcpSettings.tsx index 1ec78015f..22b6fcf48 100644 --- a/packages/ui/settings/components/AcpSettings.tsx +++ b/packages/ui/settings/components/AcpSettings.tsx @@ -630,21 +630,23 @@ export default function AcpSettings() { />
- - + + setAgentConfigurationOpen((current) => ({ + ...current, + [agent.id]: !current[agent.id], + })) + } + /> + } + > + {agentConfigurationOpen[agent.id] ? "Hide setup" : "Configure agent"} @@ -821,49 +823,55 @@ export default function AcpSettings() { />
- - + openManualDialog(agent)} + /> + } + > + Edit agent - - + openInspector(agent.id, agent.name)} + /> + } + > + {agent.enabled ? "Open debug console" : "Enable the agent before debugging"} - - + { + if (window.confirm(`Delete agent "${agent.name}"?`)) { + void deleteManualAgent(agent); + } + }} + /> + } + > + Delete agent diff --git a/packages/ui/settings/components/BedrockProviderSettingsDetail.tsx b/packages/ui/settings/components/BedrockProviderSettingsDetail.tsx index fcdc16cac..dbf1f141c 100644 --- a/packages/ui/settings/components/BedrockProviderSettingsDetail.tsx +++ b/packages/ui/settings/components/BedrockProviderSettingsDetail.tsx @@ -286,7 +286,7 @@ export default function BedrockProviderSettingsDetail({ Verify - + diff --git a/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx b/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx index 5bd54d002..9667d9833 100644 --- a/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx +++ b/packages/ui/settings/components/BuiltinKnowledgeSettings.tsx @@ -129,10 +129,12 @@ export default function BuiltinKnowledgeSettings({ onShowDetail }: BuiltinKnowle
- - - - + + + } + /> {!mcpStore.mcpEnabled && (

Enable MCP to access

diff --git a/packages/ui/settings/components/DifyKnowledgeSettings.tsx b/packages/ui/settings/components/DifyKnowledgeSettings.tsx index 8d519b40e..3324dc686 100644 --- a/packages/ui/settings/components/DifyKnowledgeSettings.tsx +++ b/packages/ui/settings/components/DifyKnowledgeSettings.tsx @@ -167,10 +167,12 @@ const DifyKnowledgeSettings = () => {
- - - - + + + } + /> {!mcpStore.mcpEnabled && (

Enable MCP to access

diff --git a/packages/ui/settings/components/FastGptKnowledgeSettings.tsx b/packages/ui/settings/components/FastGptKnowledgeSettings.tsx index ddbecb136..24be3745e 100644 --- a/packages/ui/settings/components/FastGptKnowledgeSettings.tsx +++ b/packages/ui/settings/components/FastGptKnowledgeSettings.tsx @@ -151,10 +151,12 @@ const FastGptKnowledgeSettings = () => {
- - - - + + + } + /> {!mcpStore.mcpEnabled && (

Enable MCP to access

diff --git a/packages/ui/settings/components/KnowledgeFile.tsx b/packages/ui/settings/components/KnowledgeFile.tsx index 0d44d0ecd..7b34d042d 100644 --- a/packages/ui/settings/components/KnowledgeFile.tsx +++ b/packages/ui/settings/components/KnowledgeFile.tsx @@ -300,16 +300,18 @@ export default function KnowledgeFile({ builtinKnowledgeDetail, onHideKnowledgeF score:{(item.distance * 100).toFixed(2) + "%"}
- - - + + handleCopy(item.metadata.content, item.id)} + /> + } + > + {copyId === item.id ? "Copied" : "Copy"} diff --git a/packages/ui/settings/components/OllamaProviderSettingsDetail.tsx b/packages/ui/settings/components/OllamaProviderSettingsDetail.tsx index f299e93fa..4caf99a11 100644 --- a/packages/ui/settings/components/OllamaProviderSettingsDetail.tsx +++ b/packages/ui/settings/components/OllamaProviderSettingsDetail.tsx @@ -439,17 +439,19 @@ export default function OllamaProviderSettingsDetail({ />
{hasDefaultBaseUrl ? ( - + - - + void fillDefaultBaseUrl()} + /> + } + > + Default: {defaultBaseUrl} Fill with default base URL diff --git a/packages/ui/settings/components/ProviderApiConfig.tsx b/packages/ui/settings/components/ProviderApiConfig.tsx index 2f179a179..5c5a78a1d 100644 --- a/packages/ui/settings/components/ProviderApiConfig.tsx +++ b/packages/ui/settings/components/ProviderApiConfig.tsx @@ -254,17 +254,19 @@ export default function ProviderApiConfig({ )}
{hasDefaultBaseUrl && !showLockedBaseUrl ? ( - + - - + + } + > + Default: {defaultBaseUrl} Fill with default base URL diff --git a/packages/ui/settings/components/RagflowKnowledgeSettings.tsx b/packages/ui/settings/components/RagflowKnowledgeSettings.tsx index 916732f56..06d551bd7 100644 --- a/packages/ui/settings/components/RagflowKnowledgeSettings.tsx +++ b/packages/ui/settings/components/RagflowKnowledgeSettings.tsx @@ -167,10 +167,12 @@ const RagflowKnowledgeSettings = () => {
- - - - + + + } + /> {!mcpStore.mcpEnabled && (

Enable MCP to access

diff --git a/packages/ui/shadcn/components/ui/tooltip.tsx b/packages/ui/shadcn/components/ui/tooltip.tsx index 62ab7263d..b731fbb9b 100644 --- a/packages/ui/shadcn/components/ui/tooltip.tsx +++ b/packages/ui/shadcn/components/ui/tooltip.tsx @@ -1,44 +1,52 @@ -"use client"; +import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; -import * as React from "react"; -import { Tooltip as TooltipPrimitive } from "radix-ui"; +import { cn } from "shadcn/lib/utils"; -import { cn } from "#shadcn/lib/utils"; - -function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps) { - return ; +function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) { + return ; } -function Tooltip({ ...props }: React.ComponentProps) { +function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { return ; } -function TooltipTrigger({ ...props }: React.ComponentProps) { +function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { return ; } function TooltipContent({ className, - sideOffset = 0, + side = "top", + sideOffset = 4, + align = "center", + alignOffset = 0, children, ...props -}: React.ComponentProps) { +}: TooltipPrimitive.Popup.Props & + Pick) { return ( - - {children} - - + + {children} + + + ); } -export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/packages/ui/src/components/ConnectionIndicator.tsx b/packages/ui/src/components/ConnectionIndicator.tsx index 5e18f32de..059cb2376 100644 --- a/packages/ui/src/components/ConnectionIndicator.tsx +++ b/packages/ui/src/components/ConnectionIndicator.tsx @@ -50,16 +50,18 @@ export default function ConnectionIndicator() { return ( - -
- -
+ + } + > +
diff --git a/packages/ui/src/components/WindowSideBar.tsx b/packages/ui/src/components/WindowSideBar.tsx index 73581fbac..344be50d7 100644 --- a/packages/ui/src/components/WindowSideBar.tsx +++ b/packages/ui/src/components/WindowSideBar.tsx @@ -309,27 +309,29 @@ export default function WindowSideBar() { showShortcutBadges && visibleShortcutSessions.some((s) => s.id === sessionId); return ( - +
- - + handleAgentSelect(null)} + /> + } + > + All Agents @@ -338,22 +340,24 @@ export default function WindowSideBar() { {agentStore.enabledAgents.map((agent) => ( - - + handleAgentSelect(agent.id)} + /> + } + > + {agent.name} @@ -363,63 +367,71 @@ export default function WindowSideBar() {
- - + spotlightStore.toggleSpotlight()} + /> + } + > + Search - - + themeStore.cycleTheme()} + /> + } + > + + + Theme · {themeModeLabel} - - + } + > + {collapsed ? "Expand Sidebar" : "Collapse Sidebar"} - - + + } + > + Settings @@ -445,31 +457,35 @@ export default function WindowSideBar() { {selectedAgentName}
- - + sessionStore.toggleGroupMode()} + /> + } + > + {sessionStore.groupMode === "project" ? "Group by Date" : "Group by Project"} - - + + } + > + New Chat diff --git a/packages/ui/src/components/chat/ChatInputToolbar.tsx b/packages/ui/src/components/chat/ChatInputToolbar.tsx index 7096bb262..551480288 100644 --- a/packages/ui/src/components/chat/ChatInputToolbar.tsx +++ b/packages/ui/src/components/chat/ChatInputToolbar.tsx @@ -93,15 +93,17 @@ const ChatInputToolbar: FC = ({
- - + + } + > +

Attach file

@@ -112,28 +114,30 @@ const ChatInputToolbar: FC = ({
{showVoiceInput && ( - - + + } + > + {isVoiceInputListening && ( +

{voiceInputTooltip}

@@ -143,17 +147,19 @@ const ChatInputToolbar: FC = ({ {isGenerating && hasActiveInput && ( - - + + } + > + + Steer

Interrupt & send as next turn

@@ -162,44 +168,46 @@ const ChatInputToolbar: FC = ({ )} - - + } + > +

{primaryTooltip}

diff --git a/packages/ui/src/components/chat/ChatStatusBar.tsx b/packages/ui/src/components/chat/ChatStatusBar.tsx index 817795082..f82fa589c 100644 --- a/packages/ui/src/components/chat/ChatStatusBar.tsx +++ b/packages/ui/src/components/chat/ChatStatusBar.tsx @@ -1323,26 +1323,26 @@ const ChatStatusBar = forwardRef( )}
{isAcpConfigLoading && !hasAcpConfigOptions && ( - + - -
- - Loading… -
+ } + > + + Loading… Loading agent modes and models…
)} {!isAcpConfigLoading && acpConfigError && !hasAcpConfigOptions && ( - + - -
- - Unavailable -
+ } + > + + Unavailable Failed to load agent configuration: {acpConfigError} @@ -1656,7 +1656,7 @@ const ChatStatusBar = forwardRef(
)} {isModelSettingsReady && localSettings && ( - + {!showOpenAIMediaGenerationSettings && showTemperatureControl && (
diff --git a/packages/ui/src/components/message/MessageBlockImage.tsx b/packages/ui/src/components/message/MessageBlockImage.tsx index 2f0d1da42..5066d791e 100644 --- a/packages/ui/src/components/message/MessageBlockImage.tsx +++ b/packages/ui/src/components/message/MessageBlockImage.tsx @@ -158,15 +158,17 @@ export const MessageBlockImage: FC = ({ block }) => {
Image - - + + } + > + Save diff --git a/packages/ui/src/components/message/MessageBlockToolCallImagePreview.tsx b/packages/ui/src/components/message/MessageBlockToolCallImagePreview.tsx index d526e830f..94fb6ebde 100644 --- a/packages/ui/src/components/message/MessageBlockToolCallImagePreview.tsx +++ b/packages/ui/src/components/message/MessageBlockToolCallImagePreview.tsx @@ -124,15 +124,17 @@ export const MessageBlockToolCallImagePreview: FC{selectedPreview?.title || "Image Preview"} {selectedPreview && ( - - + + } + > + Save diff --git a/packages/ui/src/components/message/MessageToolbar.tsx b/packages/ui/src/components/message/MessageToolbar.tsx index be76b0a3f..eeacca1eb 100644 --- a/packages/ui/src/components/message/MessageToolbar.tsx +++ b/packages/ui/src/components/message/MessageToolbar.tsx @@ -119,29 +119,33 @@ export const MessageToolbar: FC = ({ {isEditMode ? ( <> - - - + + + } + > + Save - - - + + + } + > + Cancel @@ -149,32 +153,36 @@ export const MessageToolbar: FC = ({ ) : ( <> {!isAssistant && !isEditMode && !isReadOnly && ( - - - + + + } + > + Retry )} - - - + + + } + > + Previous variant @@ -183,67 +191,73 @@ export const MessageToolbar: FC = ({ {(currentVariantIndex ?? 0) + 1} / {totalVariants} - - - + + = (totalVariants || 0) - 1} + onClick={onNext} + /> + } + > + Next variant - - + + } + > + + {showCopyTip && ( + + Copied + + )} Copy - - + + } + > + {isCapturingImage ? ( + + ) : ( + + )} + {showCopyImageTip && ( + + Image copied + + )} + {showCopyFromTopTip && ( + + Copied from top + + )} {isCapturingImage ? "Capturing..." : "Copy image (long press for from top)"} @@ -252,15 +266,17 @@ export const MessageToolbar: FC = ({ {isAssistant && !isReadOnly && ( - - + + } + > + Retry @@ -268,15 +284,17 @@ export const MessageToolbar: FC = ({ {isAssistant && traceDebugEnabled && allowTrace && ( - - + + } + > + Trace debug @@ -284,15 +302,17 @@ export const MessageToolbar: FC = ({ {isAssistant && !loading && !isInGeneratingThread && !isReadOnly && ( - - + + } + > + Fork @@ -300,15 +320,17 @@ export const MessageToolbar: FC = ({ {!isAssistant && !isEditMode && !isReadOnly && ( - - + + } + > + Edit @@ -316,15 +338,17 @@ export const MessageToolbar: FC = ({ {!isReadOnly && ( - - + + } + > + Delete diff --git a/packages/ui/src/pages/ChatPage.tsx b/packages/ui/src/pages/ChatPage.tsx index 1f36ecde0..745325ed4 100644 --- a/packages/ui/src/pages/ChatPage.tsx +++ b/packages/ui/src/pages/ChatPage.tsx @@ -1053,7 +1053,7 @@ function ChatPage({ sessionId }: ChatPageProps) { } return ( - +
+
From 87206ddbc673456055e5e0b08beecb46942d7dbc Mon Sep 17 00:00:00 2001 From: Francisco Pizarro Date: Tue, 4 Aug 2026 18:00:00 -0400 Subject: [PATCH 18/28] fix(ui): single root-level TooltipProvider --- .../ui/settings/components/AcpDiagnostics.tsx | 74 +- .../ui/settings/components/AcpSettings.tsx | 1305 ++++++++--------- .../BedrockProviderSettingsDetail.tsx | 20 +- .../components/BuiltinKnowledgeSettings.tsx | 28 +- .../components/DifyKnowledgeSettings.tsx | 28 +- .../components/FastGptKnowledgeSettings.tsx | 28 +- .../ui/settings/components/KnowledgeFile.tsx | 34 +- .../OllamaProviderSettingsDetail.tsx | 34 +- .../settings/components/ProviderApiConfig.tsx | 34 +- .../components/RagflowKnowledgeSettings.tsx | 28 +- packages/ui/settings/main.tsx | 1 + packages/ui/src/components/WindowSideBar.tsx | 6 +- .../ui/src/components/chat/ChatStatusBar.tsx | 50 +- .../mcp-config/components/McpServerCard.tsx | 20 +- .../src/components/message/MessageToolbar.tsx | 382 +++-- .../workspace/WorkspaceFileNode.tsx | 2 +- packages/ui/src/main.tsx | 1 + packages/ui/src/pages/ChatPage.tsx | 323 ++-- packages/ui/src/pages/NewThreadPage.tsx | 258 ++-- 19 files changed, 1305 insertions(+), 1351 deletions(-) diff --git a/packages/ui/settings/components/AcpDiagnostics.tsx b/packages/ui/settings/components/AcpDiagnostics.tsx index 1a18055ce..1093cb3fc 100644 --- a/packages/ui/settings/components/AcpDiagnostics.tsx +++ b/packages/ui/settings/components/AcpDiagnostics.tsx @@ -6,7 +6,7 @@ import { Input } from "#shadcn/components/ui/input"; import { Field, FieldDescription, FieldLabel } from "#shadcn/components/ui/field"; import { Spinner } from "#shadcn/components/ui/spinner"; import { Collapsible, CollapsibleContent } from "#shadcn/components/ui/collapsible"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "#shadcn/components/ui/tooltip"; +import { Tooltip, TooltipContent, TooltipTrigger } from "#shadcn/components/ui/tooltip"; import { cn } from "#shadcn/lib/utils"; import type { AcpAgentDiagnostics, AcpRemoteSessionSummary, AcpDebugRunResult } from "@argos/shared/presenter"; import { toast } from "#/components/use-toast"; @@ -313,43 +313,41 @@ export default function AcpDiagnostics({

{connectionCopy.description}

- -
- - void runDiagnostics()} - > - {probing ? : } - - } - /> - {canRun ? checkLabel : `Enable ${agentName} before checking`} - - - setManualDetailsOpen(!detailsOpen)} - > - - - } - /> - {detailsLabel} - -
-
+
+ + void runDiagnostics()} + > + {probing ? : } + + } + /> + {canRun ? checkLabel : `Enable ${agentName} before checking`} + + + setManualDetailsOpen(!detailsOpen)} + > + + + } + /> + {detailsLabel} + +
diff --git a/packages/ui/settings/components/AcpSettings.tsx b/packages/ui/settings/components/AcpSettings.tsx index 22b6fcf48..916d5a4d5 100644 --- a/packages/ui/settings/components/AcpSettings.tsx +++ b/packages/ui/settings/components/AcpSettings.tsx @@ -19,7 +19,7 @@ import { import { Field, FieldDescription, FieldGroup, FieldLabel } from "#shadcn/components/ui/field"; import { Skeleton } from "#shadcn/components/ui/skeleton"; import { Spinner } from "#shadcn/components/ui/spinner"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "#shadcn/components/ui/tooltip"; +import { Tooltip, TooltipContent, TooltipTrigger } from "#shadcn/components/ui/tooltip"; import { AlertDialog, AlertDialogAction, @@ -476,720 +476,707 @@ export default function AcpSettings() { }; return ( - -
-
-
-
-

Agent Client Protocol

-

Connect external coding agents to Argos.

-
-
-
-
ACP {acpEnabled ? "enabled" : "disabled"}
-

Applies to all agents

-
- +
+
+
+
+

Agent Client Protocol

+

Connect external coding agents to Argos.

+
+
+
+
ACP {acpEnabled ? "enabled" : "disabled"}
+

Applies to all agents

+
- - {acpEnabled && ( -

- - Install enables an agent and checks its default connection automatically. -

- )}
-
- {acpEnabled ? ( -
- {loadError && ( - - - ACP settings could not be loaded - {loadError} - - )} - - {showSharedMcpSection && ( - -
-
-

Shared MCP access

-

Manage which MCP tools are shared with ACP agents

-
-
- MCP Access: {sharedMcpCount} - -
-
- -
- setSharedMcpCount(selections.length)} - /> -
-
-
- )} + {acpEnabled && ( +

+ + Install enables an agent and checks its default connection automatically. +

+ )} +
-
+
+ {acpEnabled ? ( +
+ {loadError && ( + + + ACP settings could not be loaded + {loadError} + + )} + + {showSharedMcpSection && ( +
-

Agents

-

- Manage installed agents and verify their connections. -

+

Shared MCP access

+

Manage which MCP tools are shared with ACP agents

-
- -
+
+ +
+ setSharedMcpCount(selections.length)} + /> +
+
+
+ )} + +
+
+
+

Agents

+

Manage installed agents and verify their connections.

+
+
+ + +
+
+ + {loading && !installedRegistryAgents.length ? ( +
+ {[0, 1].map((item) => ( +
+ +
+ + +
+ +
+ ))} +
+ ) : !installedRegistryAgents.length ? ( + + + + + + No agents installed + + Choose an ACP agent from the registry. Argos will install and enable it. + + + + -
+ + + ) : ( +
+ {installedRegistryAgents.map((agent) => ( + + setAgentConfigurationOpen((current) => ({ ...current, [agent.id]: open })) + } + > +
+
+
+ +
+

{agent.name}

+

+ {agent.description || `Built-in ${agent.name} agent`} +

+
+
+
+
+ void toggleRegistryAgent(agent, value)} + /> +
+ + + setAgentConfigurationOpen((current) => ({ + ...current, + [agent.id]: !current[agent.id], + })) + } + /> + } + > + + + + {agentConfigurationOpen[agent.id] ? "Hide setup" : "Configure agent"} + + +
+
+ + consumeConnectionCheckRequest(agent.id, request)} + /> + + +
+
+
+
Agent ID
+
{agent.id}
+
+
+
Version
+
{agent.version}
+
+
+
Command
+
{buildPreviewCommand(agent)}
+
+
+ + + Environment overrides +