-
Notifications
You must be signed in to change notification settings - Fork 673
feat: add enable/disable toggle for Claude Desktop routing #1291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, ty | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LANE_PAGE, defaultCollapsedFamilies, laneView, rowStartsOpen } from "./claude-desktop-lane"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { makeCollapseStore, toggleInSet } from "./collapse-store"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IconChevron } from "../icons"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { EmptyState, Notice } from "../ui"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { EmptyState, Notice, Switch } from "../ui"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LOCALES, useI18n, type TFn, type TKey } from "../i18n/shared"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readJsonIfOk, readJsonOrThrow } from "../fetch-json"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readSessionListCache, writeSessionListCache } from "../session-list-cache"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -60,6 +60,7 @@ interface DesktopResponse { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| models: DesktopModel[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rendered: unknown[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type PendingAction = "save" | "apply" | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -164,6 +165,9 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [message, setMessage] = useState<{ tone: "ok" | "err"; text: string } | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [announcement, setAnnouncement] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [pending, setPending] = useState<PendingAction>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [desktopEnabled, setDesktopEnabled] = useState<boolean>(() => cached?.data?.enabled !== false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [connectionPending, setConnectionPending] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const connectionInFlight = useRef(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Lane density: search and paging are RENDER-ONLY. modelsByFamily and effectiveDefaults must | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // keep seeing every model — filtering the source arrays would silently change which model is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the effective default, turning a view filter into a data mutation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -195,6 +199,7 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setProfile(normalized); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSavedProfile(cloneProfile(normalized)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setDestinations(Object.fromEntries(payload.models.map(model => [model.route, normalized.assignments[model.route]?.family ?? "opus"]))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setDesktopEnabled(payload.enabled !== false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fold empty families on load, but only while the user has no stored preference. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Doing it here rather than per render means a later move or import can never | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // re-fold a section the user opened. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -304,6 +309,39 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCollapsedFamilies(next); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Immediate on/off toggle for the Desktop 3P config files. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Waits for the response — writing to another program's config library is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * not something we should optimistically claim succeeded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const toggleDesktop = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (connectionInFlight.current) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connectionInFlight.current = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConnectionPending(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setMessage(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const next = !desktopEnabled; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${apiBase}/api/native-integrations/claude-desktop`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "PUT", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { "Content-Type": "application/json" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ enabled: next }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setDesktopEnabled(next); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (body.message) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setMessage({ tone: "ok", text: body.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setAnnouncement(body.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void desktopResource.refresh(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void statusResource.refresh(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+317
to
+336
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win Unguarded
Every other call site of Failure mode: if the PUT response is 🐛 Proposed fix- const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed"));
+ const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed")) ?? {};
setDesktopEnabled(next);
if (body.message) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setMessage({ tone: "err", text: error instanceof Error ? error.message : t("claudeDesktop.toggleFailed") }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connectionInFlight.current = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConnectionPending(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const save = async (applyAfter: boolean) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!profile || pending) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPending("save"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -392,6 +430,15 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Title/subtitle live on Claude.tsx above the Code/Desktop strip. */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="claudecode-connection-head"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span id="claude-desktop-connection-label">{t("claudeDesktop.enabledLabel")}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Switch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on={desktopEnabled} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => void toggleDesktop()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={connectionPending} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label={t("claudeDesktop.toggleAria")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="claude-desktop-toolbar"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="claude-profile-tools"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input ref={importRef} type="file" accept="application/json,.json" hidden onChange={event => void importProfile(event)} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -400,6 +447,10 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!desktopEnabled && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Notice tone="err">{t("claudeDesktop.disabledNotice")}</Notice> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Always mount the bar (pending strut when status is still cold) so a late /status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response cannot insert a full row under the title and shove the lanes down. */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -455,12 +506,14 @@ export default function ClaudeDesktop({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="claude-profile-bar"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={`claude-dirty${dirty ? " active" : ""}`}>{dirty ? t("claudeDesktop.unsaved") : t("claudeDesktop.upToDate")}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="claude-save-actions"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="btn btn-ghost" disabled={!dirty || pending !== null} onClick={() => void save(false)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className={`btn ${desktopEnabled ? "btn-ghost" : "btn-primary"}`} disabled={!dirty || pending !== null} onClick={() => void save(false)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {pending === "save" ? t("claudeDesktop.saving") : t("common.save")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="btn btn-primary" disabled={pending !== null} onClick={() => void save(true)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {pending === "apply" ? t("claudeDesktop.applying") : pending === "save" ? t("claudeDesktop.saving") : t("claudeDesktop.saveApply")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {desktopEnabled && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="btn btn-primary" disabled={pending !== null} onClick={() => void save(true)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {pending === "apply" ? t("claudeDesktop.applying") : pending === "save" ? t("claudeDesktop.saving") : t("claudeDesktop.saveApply")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { createHash, randomUUID } from "node:crypto"; | ||
| import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"; | ||
| import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs"; | ||
| import { homedir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { atomicWriteFile } from "../config"; | ||
|
|
@@ -379,3 +379,43 @@ export function atomicReplaceDesktopConfig( | |
| writer(path, content); | ||
| return existsSync(backupPath) ? { backupPath } : {}; | ||
| } | ||
|
|
||
| /** | ||
| * Remove the opencodex entry from Claude Desktop's 3P config library. | ||
| * | ||
| * Deletes the UUID-named config JSON and strips the entry from _meta.json. | ||
| * Safe to call when the library directory or metadata does not exist — it | ||
| * returns `cleared: false` rather than throwing. | ||
| */ | ||
| export function clearDesktop3pConfig(): { cleared: boolean; path: string; reason?: string } { | ||
| const libraryPath = resolveDesktop3pConfigLibraryPath(); | ||
| const metadataPath = join(libraryPath, "_meta.json"); | ||
| const sentinel = join(libraryPath, "_meta.json"); | ||
| try { | ||
| if (!existsSync(metadataPath)) { | ||
| return { cleared: false, path: sentinel, reason: "no metadata" }; | ||
| } | ||
| const metadata = parseMetadata(metadataPath); | ||
| const entry = metadata.entries.find(e => e?.name === "opencodex" && typeof e.id === "string"); | ||
| if (!entry || typeof entry.id !== "string") { | ||
| return { cleared: false, path: sentinel, reason: "no opencodex entry" }; | ||
| } | ||
| const configPath = join(libraryPath, `${entry.id}.json`); | ||
| // Remove the config file — best-effort; a missing file is not a failure. | ||
| if (existsSync(configPath)) { | ||
| unlinkSync(configPath); | ||
|
Comment on lines
+399
to
+406
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Validate the metadata entry ID before constructing the deletion path.
Require a canonical UUID before using an entry ID. Apply the validation in a shared metadata helper so 🤖 Prompt for AI Agents |
||
| } | ||
| // Strip the entry from metadata. | ||
| const entries = metadata.entries.filter(e => e !== entry); | ||
| const nextMeta: Desktop3pMetadata = { ...metadata, entries }; | ||
| // If Desktop is currently serving our profile, clear the appliedId too. | ||
| if (nextMeta.appliedId === entry.id) { | ||
| delete nextMeta.appliedId; | ||
| } | ||
| atomicWriteFile(metadataPath, JSON.stringify(nextMeta, null, 2) + "\n"); | ||
| return { cleared: true, path: configPath }; | ||
| } catch (error) { | ||
| const reason = error instanceof Error ? error.message : String(error); | ||
| return { cleared: false, path: sentinel, reason }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -662,7 +662,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise | |
| try { | ||
| const state = await buildClaudeDesktopState(config); | ||
| const runtimePort = Number(url.port) || config.port; | ||
| return jsonResponse({ ...state, port: runtimePort }); | ||
| return jsonResponse({ ...state, port: runtimePort, enabled: config.claudeCode?.desktopEnabled !== false }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Gate every Claude Desktop configuration writer with The response at Line 665 reports that routing is disabled, but After a user disables routing, a catalog update or direct apply request can recreate the Desktop 3P entry. Both paths also pass Return before auto-apply when As per path instructions, “tokens and OAuth material must never be logged or serialized into responses.” 🤖 Prompt for AI AgentsSource: Path instructions |
||
| } catch (error) { | ||
| return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400); | ||
| } | ||
|
|
@@ -809,6 +809,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise | |
| stale, | ||
| activeProfile, | ||
| health, | ||
| enabled: config.claudeCode?.desktopEnabled !== false, | ||
| }); | ||
| } catch (error) { | ||
| return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Reuse
toggleNativeIntegrationinstead of a rawfetchfor the Desktop toggle.gui/src/pages/integrations/native-api.tsalready exportstoggleNativeIntegration(apiBase, client, enabled, signal), which PUTs the same endpoint shape (/api/native-integrations/${client}), returns a typedNativeToggleEnvelope(ok,clientId,changed,state,message,reason), and throws a typedNativeApiErrorwith a parsed.refusal(includingdisableBlockedreason codes likenot_installedorconfig_busy) on failure.NativeIntegrationClientIdnow includes"claude-desktop"(native-api.ts Line 11), so this helper is directly usable here.The raw
fetch+readJsonOrThrowpath intoggleDesktop(Lines 317-329) bypasses that refusal-aware parsing. A native refusal from the server (for example, the config file busy or Desktop not installed) will still throw viareadJsonOrThrow's error path, but only as a flat string pulled fromerror/message, losing thereasoncode that the rest of the native-integration surface (claudeRow,grokRow,toggleBlocked: native?.disableBlocked) uses to drive localized, reason-specific messaging.Do you want me to generate a version of
toggleDesktopthat callstoggleNativeIntegrationand mapsNativeApiError.refusal?.messageinto the existingsetMessage/setAnnouncementflow?🤖 Prompt for AI Agents