Skip to content

Commit abc6ec8

Browse files
committed
Address latest review feedback
1 parent 70baa7c commit abc6ec8

3 files changed

Lines changed: 73 additions & 14 deletions

File tree

apps/server/src/provider/Layers/ProviderRegistry.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,54 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))(
526526
}),
527527
);
528528

529+
it.effect("reports cursor as unavailable when its CLI command is missing", () =>
530+
Effect.gen(function* () {
531+
const serverSettingsLayer = ServerSettingsService.layerTest({
532+
providers: {
533+
cursor: {
534+
enabled: true,
535+
binaryPath: "/tmp/t3-missing-cursor-cli",
536+
},
537+
},
538+
});
539+
const providerRegistryLayer = ProviderRegistryLive.pipe(
540+
Layer.provideMerge(serverSettingsLayer),
541+
Layer.provideMerge(
542+
mockCommandSpawnerLayer((command, args) => {
543+
const joined = args.join(" ");
544+
if (joined === "--version") {
545+
if (command === "codex") {
546+
return { stdout: "codex 1.0.0\n", stderr: "", code: 0 };
547+
}
548+
if (command === "claude") {
549+
return { stdout: "claude 1.0.0\n", stderr: "", code: 0 };
550+
}
551+
return { stdout: "", stderr: "spawn ENOENT", code: 1 };
552+
}
553+
if (joined === "login status") {
554+
return { stdout: "Logged in\n", stderr: "", code: 0 };
555+
}
556+
if (joined === "auth status") {
557+
return { stdout: "Authenticated\n", stderr: "", code: 0 };
558+
}
559+
throw new Error(`Unexpected command: ${command} ${joined}`);
560+
}),
561+
),
562+
);
563+
564+
const providers = yield* Effect.gen(function* () {
565+
const registry = yield* ProviderRegistry;
566+
return yield* registry.getProviders;
567+
}).pipe(Effect.provide(providerRegistryLayer));
568+
569+
const cursor = providers.find((provider) => provider.provider === "cursor");
570+
assert.isDefined(cursor);
571+
assert.strictEqual(cursor?.status, "warning");
572+
assert.strictEqual(cursor?.installed, false);
573+
assert.strictEqual(cursor?.message, "Cursor CLI not found on PATH.");
574+
}),
575+
);
576+
529577
it.effect("serves cached provider snapshots from getProviders without re-probing", () =>
530578
Effect.gen(function* () {
531579
let probeCount = 0;

apps/server/src/provider/Layers/ProviderRegistry.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,7 @@ const runBinaryBackedSnapshot = (
261261
? yield* Effect.tryPromise({
262262
try: () => options.fetchDiscoveredModels?.(binaryPath) ?? Promise.resolve([]),
263263
catch: wrapProbeError,
264-
}).pipe(
265-
Effect.catchCause((cause) => {
266-
const error = unwrapProbeError(Cause.squash(cause));
267-
if (isCommandMissingCause(error)) {
268-
return Effect.succeed<ReadonlyArray<{ slug: string; name: string }>>([]);
269-
}
270-
return Effect.failCause(cause);
271-
}),
272-
)
264+
})
273265
: [];
274266

275267
const baseModels =
@@ -367,16 +359,19 @@ const loadProviderSnapshot = (
367359
return yield* runBinaryBackedSnapshot("cursor", settings.providers.cursor, {
368360
fetchDiscoveredModels: (binaryPath) =>
369361
fetchCursorModels(binaryPath ? { binaryPath } : {}).then((models) => [...models]),
362+
resolveProbeBinaryPath: (binaryPath) => binaryPath ?? "agent",
370363
});
371364
case "opencode":
372365
return yield* runBinaryBackedSnapshot("opencode", settings.providers.opencode, {
373366
fetchDiscoveredModels: (binaryPath) =>
374367
fetchOpenCodeModels(binaryPath ? { binaryPath } : {}).then((models) => [...models]),
368+
resolveProbeBinaryPath: (binaryPath) => binaryPath ?? "opencode",
375369
});
376370
case "kilo":
377371
return yield* runBinaryBackedSnapshot("kilo", settings.providers.kilo, {
378372
fetchDiscoveredModels: (binaryPath) =>
379373
fetchKiloModels(binaryPath ? { binaryPath } : {}).then((models) => [...models]),
374+
resolveProbeBinaryPath: (binaryPath) => binaryPath ?? "kilo",
380375
});
381376
case "geminiCli":
382377
if (settings.providers.geminiCli.enabled) {

apps/web/src/components/settings/SettingsPanels.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ function AboutVersionSection() {
382382

383383
const updateState = updateStateQuery.data ?? null;
384384

385-
const handleButtonClick = useCallback(() => {
385+
const handleButtonClick = useCallback(async () => {
386386
const bridge = window.desktopBridge;
387387
if (!bridge) return;
388388

@@ -405,7 +405,8 @@ function AboutVersionSection() {
405405
}
406406

407407
if (action === "install") {
408-
const confirmed = window.confirm(
408+
const api = readNativeApi();
409+
const confirmed = await (api ?? ensureNativeApi()).dialogs.confirm(
409410
getDesktopUpdateInstallConfirmationMessage(
410411
updateState ?? { availableVersion: null, downloadedVersion: null },
411412
),
@@ -586,6 +587,19 @@ export function GeneralSettingsPanel() {
586587
const refreshingRef = useRef(false);
587588
const queryClient = useQueryClient();
588589
const modelListRefs = useRef<Partial<Record<ProviderKind, HTMLDivElement | null>>>({});
590+
const modelListObserverRef = useRef<MutationObserver | null>(null);
591+
const modelListObserverTimeoutRef = useRef<number | null>(null);
592+
const clearModelListObserver = useCallback(() => {
593+
modelListObserverRef.current?.disconnect();
594+
modelListObserverRef.current = null;
595+
if (modelListObserverTimeoutRef.current !== null) {
596+
window.clearTimeout(modelListObserverTimeoutRef.current);
597+
modelListObserverTimeoutRef.current = null;
598+
}
599+
}, []);
600+
601+
useEffect(() => clearModelListObserver, [clearModelListObserver]);
602+
589603
const refreshProviders = useCallback(() => {
590604
if (refreshingRef.current) return;
591605
refreshingRef.current = true;
@@ -708,14 +722,16 @@ export function GeneralSettingsPanel() {
708722
if (!el) return;
709723
const scrollToEnd = () => el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
710724
requestAnimationFrame(scrollToEnd);
725+
clearModelListObserver();
711726
const observer = new MutationObserver(() => {
712727
scrollToEnd();
713-
observer.disconnect();
728+
clearModelListObserver();
714729
});
730+
modelListObserverRef.current = observer;
715731
observer.observe(el, { childList: true, subtree: true });
716-
setTimeout(() => observer.disconnect(), 2_000);
732+
modelListObserverTimeoutRef.current = window.setTimeout(clearModelListObserver, 2_000);
717733
},
718-
[customModelInputByProvider, serverProviders, settings, updateSettings],
734+
[clearModelListObserver, customModelInputByProvider, serverProviders, settings, updateSettings],
719735
);
720736

721737
const removeCustomModel = useCallback(

0 commit comments

Comments
 (0)