Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,37 @@ file is the human-readable summary of what changed and why.
auto-upload stay quiet. No sessions are lost while stalled — everything
pending ships on the first upload after signing in.
([#312](https://github.com/quickbeard/codev-code/pull/312))
- **CoDev Desktop uploads sessions too** — the background session upload used
to exist only in the terminal app; the desktop's local sessions never
- **CoDev Desktop uploads sessions too** — the background session upload
used to exist only in the terminal app; the desktop's local sessions never
reached the analysis backend. The desktop's embedded server now runs the
same uploader at every app start, under the same rules: signed-in machines
only, deduplicated, shared lock with the terminal app and the hub so
nothing double-uploads, and `CODEV_DISABLE_AUTO_UPLOAD=1` turns it off.
Signing in from the desktop's gateway setup uploads the pending backlog
immediately, and when uploads stall because the CoDev sign-in expired, the
desktop shows a system notification at startup — its version of the
terminal app's "Session upload is paused" warning. Work (sandbox) sessions
are covered too: each sandbox keeps its sessions in its own guest database
that the host cannot read as a file, so whenever a sandbox comes up, the
desktop reads its sessions over the sandbox's own authenticated API and
uploads them like any others — filed under the project folder the sandbox
belongs to, alongside that project's local sessions. A sandbox that is off
simply uploads on its next start; nothing is lost in between. An **Upload
Session Logs** menu item (under "Get a new API Key..." in the CoDev menu on
macOS, the File menu on Windows/Linux) runs the same sweep on demand — for
when you want a session analyzed now rather than at the next launch.
desktop shows a "Session upload is paused" toast at startup — its version
of the terminal app's warning. Work (sandbox) sessions are covered too:
each sandbox keeps its sessions in its own guest database that the host
cannot read as a file, so whenever a sandbox comes up, the desktop reads
its sessions over the sandbox's own authenticated API and uploads them
like any others — filed under the project folder the sandbox belongs to,
alongside that project's local sessions. A sandbox that is off simply
uploads on its next start; nothing is lost in between. An **Upload Session
Logs** menu item (under **Log in/Configure** in the CoDev menu on macOS,
the File menu on Windows/Linux) runs the same sweep on demand — for when
you want a session analyzed now rather than at the next launch.
([#313](https://github.com/quickbeard/codev-code/pull/313))
- **The "Session upload is paused" warning can now sign you back in** — the
warning that your sessions stopped uploading arrived as a system
notification with nothing to click: you had to go find the sign-in yourself,
and on Linux the notification often never appeared at all. It is now a toast
inside the app with a **Log in** button that opens the same sign-in dialog
the menu item does, and it stays put until you dismiss it instead of fading
while you are reading code. That menu item is now called **Log
in/Configure** — in the **CoDev** menu on macOS, under **File** on Windows
and Linux, and in the command palette everywhere — since signing back in,
rather than minting a key, is what it is usually reached for.
([#320](https://github.com/quickbeard/codev-code/pull/320))
- **A dropped model connection retries instead of silently stopping the agent** —
when the connection to the model died mid-response (corporate proxies are fond
of cutting long-lived streams), the session just went idle: no error, no
Expand Down Expand Up @@ -501,7 +512,7 @@ already in use` and no way forward short of finding the file in a temporary
it starts: if it has been rejected, it quietly gets a new one using your
existing session, and you never see it happen. Only when that is not possible
— no key at all, or a session too old to renew — does it ask you to sign in.
And there is now a **Get a new API Key...** item to do it yourself at any
And there is now a **Log in/Configure** item to do it yourself at any
time: in the **CoDev** menu on macOS, under **File** on Windows and Linux,
both next to Settings, and in the command palette on every platform. A
gateway that cannot be reached is never treated as a bad key, so a flaky
Expand Down
4 changes: 2 additions & 2 deletions DIVERGENCES.md

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion packages/app/src/components/gateway/gateway-configure-gate.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEffect, createSignal, onCleanup } from "solid-js"
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
Expand All @@ -7,6 +7,7 @@ import { useServer } from "@/context/server"
import { useServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { shouldOpenConfigureGate } from "@/gateway/configure-gate"
import { showToast } from "@/utils/toast"
import { DialogConfigure } from "./dialog-configure"

// ServerScopedProviders can be mounted by the legacy and new shells at the
Expand Down Expand Up @@ -93,6 +94,25 @@ export function GatewayConfigureGate() {
],
)

// The background session upload stalls when the CoDev SSO session dies, and
// nothing else notices — the gateway api key it runs on keeps working. Main
// reports that stall once per launch (index.ts consumes the flag), so this
// fires in exactly one window even though the gate mounts in each. Persistent
// rather than timed: a toast that auto-dismisses while the user is reading
// code is the same silence the nudge exists to break.
onMount(() => {
void platform.gatewaySso?.consumeUploadSignInNudge().then((stalled) => {
if (!stalled) return
showToast({
persistent: true,
icon: "cloud-upload",
title: language.t("toast.sessionUpload.paused.title"),
description: language.t("toast.sessionUpload.paused.description"),
actions: [{ label: language.t("toast.sessionUpload.paused.action.login"), onClick: openConfigure }],
})
})
})

// Startup key pre-flight. The gate above only fires when no provider is
// connected, so a stored key that the gateway has since rejected left the app
// configured on paper and 401ing on every message, with no way back to the
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/desktop-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [
{ type: "item", role: "about" },
{ type: "item", label: "Check for Updates...", action: "app.checkForUpdates", enabled: "updater" },
{ type: "item", label: "Settings", command: "settings.open", accelerator: { macos: "Cmd+," } },
{ type: "item", label: "Get a new API Key...", command: "gateway.apiKey.new" },
{ type: "item", label: "Log in/Configure", command: "gateway.apiKey.new" },
{ type: "item", label: "Upload Session Logs", action: "upload.sessions" },
{ type: "item", label: "Reload Webview", action: "view.reload" },
{ type: "item", label: "Restart", action: "app.relaunch" },
Expand Down Expand Up @@ -117,11 +117,11 @@ export const DESKTOP_MENU: DesktopMenu[] = [
// Windows/Linux menu does not render — same split Settings itself uses.
{
type: "item",
label: "Get a new API Key...",
label: "Log in/Configure",
command: "gateway.apiKey.new",
platforms: ["windows"],
},
// macOS carries this in the app menu below its API-key sibling.
// macOS carries this in the app menu below its sign-in sibling.
{
type: "item",
label: "Upload Session Logs",
Expand Down
5 changes: 5 additions & 0 deletions packages/app/src/gateway/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,9 @@ export type GatewaySsoPlatform = {
// Validates the stored key, refreshing it silently when it is rejected and
// the SSO session still allows it. Never prompts.
ensureKey(): Promise<GatewayKeyCheck>
// Whether the background session upload stalled because the CoDev SSO
// session died, and this launch should say so. Lives on the SSO namespace
// because signing back in is the whole remedy. Consumed: the main process
// hands the stall to the first asker, so a second window stays quiet.
consumeUploadSignInNudge(): Promise<boolean>
}
7 changes: 6 additions & 1 deletion packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const dict = {
"command.project.index": "Switch to project {{index}}",
"command.provider.connect": "Connect provider",
"command.server.switch": "Switch server",
"command.gateway.apiKey.new": "Get a new API key",
"command.gateway.apiKey.new": "Log in/Configure",
"command.settings.open": "Open settings",
"command.session.previous": "Previous session",
"command.session.next": "Next session",
Expand Down Expand Up @@ -658,6 +658,11 @@ export const dict = {
"toast.update.action.installRestart": "Install and restart",
"toast.update.action.notYet": "Not yet",

"toast.sessionUpload.paused.title": "Session upload is paused",
"toast.sessionUpload.paused.description":
"Your CoDev sign-in expired, so sessions are no longer uploaded for analysis. Log in again to resume — pending sessions upload automatically afterwards.",
"toast.sessionUpload.paused.action.login": "Log in",

"error.page.title": "Something went wrong",
"error.page.description": "An error occurred while loading the application.",
"error.page.description.localServerStartup": "An error occurred while starting the local server.",
Expand Down
7 changes: 6 additions & 1 deletion packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const dict = {
"command.project.index": "Cambiar al proyecto {{index}}",
"command.provider.connect": "Conectar proveedor",
"command.server.switch": "Cambiar servidor",
"command.gateway.apiKey.new": "Obtener una nueva clave de API",
"command.gateway.apiKey.new": "Iniciar sesión/Configurar",
"command.settings.open": "Abrir ajustes",
"command.session.previous": "Sesión anterior",
"command.session.next": "Siguiente sesión",
Expand Down Expand Up @@ -648,6 +648,11 @@ export const dict = {
"toast.update.action.installRestart": "Instalar y reiniciar",
"toast.update.action.notYet": "Todavía no",

"toast.sessionUpload.paused.title": "La subida de sesiones está pausada",
"toast.sessionUpload.paused.description":
"Tu sesión de CoDev caducó, así que las sesiones ya no se suben para su análisis. Vuelve a iniciar sesión para reanudarla: las sesiones pendientes se subirán automáticamente después.",
"toast.sessionUpload.paused.action.login": "Iniciar sesión",

"error.page.title": "Algo salió mal",
"error.page.description": "Ocurrió un error al cargar la aplicación.",
"error.page.description.localServerStartup": "Ocurrió un error al iniciar el servidor local.",
Expand Down
7 changes: 6 additions & 1 deletion packages/app/src/i18n/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const dict = {
"command.project.index": "Chuyển đến dự án {{index}}",
"command.provider.connect": "Kết nối provider",
"command.server.switch": "Chuyển máy chủ",
"command.gateway.apiKey.new": "Lấy API key mới",
"command.gateway.apiKey.new": "Đăng nhập/Cấu hình",
"command.settings.open": "Mở cài đặt",
"command.session.previous": "Session trước",
"command.session.next": "Session tiếp theo",
Expand Down Expand Up @@ -654,6 +654,11 @@ export const dict = {
"toast.update.action.installRestart": "Cài đặt và khởi động lại",
"toast.update.action.notYet": "Để sau",

"toast.sessionUpload.paused.title": "Việc tải phiên lên đang tạm dừng",
"toast.sessionUpload.paused.description":
"Phiên đăng nhập CoDev của bạn đã hết hạn nên các phiên làm việc không còn được tải lên để phân tích. Hãy đăng nhập lại để tiếp tục — các phiên đang chờ sẽ tự động được tải lên sau đó.",
"toast.sessionUpload.paused.action.login": "Đăng nhập",

"error.page.title": "Đã xảy ra sự cố",
"error.page.description": "Đã xảy ra lỗi khi tải ứng dụng.",
"error.page.description.localServerStartup": "Đã xảy ra lỗi khi khởi động server cục bộ.",
Expand Down
9 changes: 8 additions & 1 deletion packages/desktop/src/main/gateway-sso/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { app, ipcMain } from "electron"
import type { IpcMainInvokeEvent } from "electron"
import type { GatewaySsoController } from "./controller"

export function registerGatewaySsoIpcHandlers(controller: GatewaySsoController) {
// consumeUploadSignInNudge reports whether the background session upload
// stalled for lack of an SSO session, once per app launch (see index.ts) — the
// renderer turns it into the "Session upload is paused" toast.
export function registerGatewaySsoIpcHandlers(
controller: GatewaySsoController,
consumeUploadSignInNudge: () => boolean,
) {
const subscriptions = new Map<number, () => void>()
const unsubscribe = (id: number) => {
const off = subscriptions.get(id)
Expand Down Expand Up @@ -44,6 +50,7 @@ export function registerGatewaySsoIpcHandlers(controller: GatewaySsoController)
)
ipcMain.handle("gateway-sso-fetch-models", () => controller.fetchModels())
ipcMain.handle("gateway-sso-ensure-key", () => controller.ensureKey())
ipcMain.handle("gateway-sso-consume-upload-nudge", () => consumeUploadSignInNudge())
ipcMain.handle("gateway-sso-complete", (_event: IpcMainInvokeEvent, model: unknown) =>
controller.complete(requireGatewayIpcString("model", model)),
)
Expand Down
31 changes: 18 additions & 13 deletions packages/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,19 @@ const main = Effect.gen(function* () {
body: "New and changed sessions are being uploaded for analysis in the background.",
}).show()
}
// Fork: the background session upload needs a live CoDev SSO session, but
// the app itself runs on the gateway api key alone — so when the session
// dies, uploads stop silently while everything else keeps working. The
// sidecar's runner records that stall; the renderer turns it into a toast
// whose action opens the same sign-in dialog the Log in/Configure menu item
// does (the desktop counterpart of the TUI's startup toast, and like it,
// every launch until the sign-in happens). Read once here rather than per
// request, so a sign-in mid-session cannot flip the answer between windows,
// and handed to the first asker only — a second window would otherwise
// repeat a toast the user is already looking at. shouldNudgeSignIn is
// sqlite-free and cheap, so it is safe on the main process.
let uploadSignInNudge = shouldNudgeSignIn()

const stopSidecars = async () => {
await killSidecar()
wslServers.stopAll()
Expand Down Expand Up @@ -409,21 +422,13 @@ const main = Effect.gen(function* () {
server?.upload(sandboxUploadSources())
},
}),
() => {
const stalled = uploadSignInNudge
uploadSignInNudge = false
return stalled
},
)
void updater.start()
// Fork: the background session upload needs a live CoDev SSO session, but
// the app itself runs on the gateway api key alone — so when the session
// dies, uploads stop silently while everything else keeps working. The
// sidecar's runner records that stall; surface it as an OS notification
// (the desktop's counterpart of the TUI's startup toast, and like it,
// every launch until the sign-in happens). shouldNudgeSignIn is sqlite-free
// and cheap, so it is safe on the main process.
if (shouldNudgeSignIn()) {
new Notification({
title: "Session upload is paused",
body: "Your CoDev sign-in expired, so sessions are no longer uploaded for analysis. Sign in again from the gateway setup to resume — pending sessions upload automatically afterwards.",
}).show()
}
// Checked at fire time rather than captured: turning automatic checks off
// has to stop the timer that is already running, not the next app launch.
const updateTimer = setInterval(
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const api: ElectronAPI = {
setManualApiKey: (apiKey) => ipcRenderer.invoke("gateway-sso-set-manual-api-key", apiKey),
fetchModels: () => ipcRenderer.invoke("gateway-sso-fetch-models"),
ensureKey: () => ipcRenderer.invoke("gateway-sso-ensure-key"),
consumeUploadSignInNudge: () => ipcRenderer.invoke("gateway-sso-consume-upload-nudge"),
complete: (model) => ipcRenderer.invoke("gateway-sso-complete", model),
},
updater: {
Expand Down
Loading