diff --git a/packages/ai/src/providers/gitlab-duo-workflow.ts b/packages/ai/src/providers/gitlab-duo-workflow.ts index 57a48ff3b..b4520953a 100644 --- a/packages/ai/src/providers/gitlab-duo-workflow.ts +++ b/packages/ai/src/providers/gitlab-duo-workflow.ts @@ -1229,15 +1229,16 @@ async function runGitLabDuoWorkflow( // The socket loop can exit several ways that leave the remote workflow running // and `active` referencing a dead socket: a user abort; `runGitLabDuoWorkflowSocket` // rejecting (e.g. `ws.onerror`) so the settle block never ran (`settledNormally` - // stays false); or the socket closing before any terminal status arrived - // (`lastSocketResult === "closed"` — a proxy/server drop). In all of these the + // stays false); or the socket reached a half-open/stuck terminal state with no + // real completion — `lastSocketResult === "closed"` (proxy/server drop) or + // `"timeout"` (idle deadline, retry already exhausted). In all of these the // local stream is finalized but the server workflow has no explicit stop, so drop // the resumable session and stop it with a FRESH signal (the request's own signal // may be aborted, which would cancel the PATCH before it is sent). The happy path // that intentionally keeps `active` for an `action`/`pause` resume reaches a real - // terminal status, never "closed", so it is not affected. + // terminal status, never "closed"/"timeout", so it is not affected. const aborted = options.signal?.aborted ?? false; - if (aborted || !settledNormally || lastSocketResult === "closed") { + if (aborted || !settledNormally || lastSocketResult === "closed" || lastSocketResult === "timeout") { if (providerSessionState) { providerSessionState.active = undefined; } diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 58e963d51..090c66354 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -98,7 +98,6 @@ - Fixed Claude 4.6 routing on the `google-antigravity` (and `google-gemini-cli`) Cloud Code Assist providers, whose backend exposes the models asymmetrically: `claude-sonnet-4-6` has no `-thinking` twin and `claude-opus-4-6` has only the `-thinking` twin. The shared `thinkingPair` family was routing thinking efforts on `claude-sonnet-4-6` to a non-existent `claude-sonnet-4-6-thinking` wire id (404 `Requested entity was not found`); replaced both 4.6 entries with bespoke single-wire families that declare the dead ids as `retiredMembers` so `reconcileRetiredRouting` re-points stale bundled-catalog and SQLite-cache rows away from the 404 wire id. Refreshed the bundled `models.json` Sonnet 4.6 entry whose stored `effortRouting` still targeted the dead `-thinking` id. Added `claude-sonnet-4-6` and `claude-opus-4-6-thinking` entries to `ANTIGRAVITY_MODEL_WIRE_PROFILES` capped at the backend's 64000-output-token limit (over-cap requests 400'd with `Request contains an invalid argument`); `modelEnum` is now optional on `AntigravityModelWireProfile` since the Claude wire ids are accepted without a captured `labels.model_enum`. ([#3067](https://github.com/can1357/oh-my-pi/issues/3067)) -- Fixed Claude 4.6 routing on the `google-antigravity` (and `google-gemini-cli`) Cloud Code Assist providers, whose backend exposes the models asymmetrically: `claude-sonnet-4-6` has no `-thinking` twin and `claude-opus-4-6` has only the `-thinking` twin. The shared `thinkingPair` family was routing thinking efforts on `claude-sonnet-4-6` to a non-existent `claude-sonnet-4-6-thinking` wire id (404 `Requested entity was not found`); replaced both 4.6 entries with bespoke single-wire families so every effort and off resolve to the live wire id. Added `claude-sonnet-4-6` and `claude-opus-4-6-thinking` entries to `ANTIGRAVITY_MODEL_WIRE_PROFILES` capped at the backend's 64000-output-token limit (over-cap requests 400'd with `Request contains an invalid argument`); `modelEnum` is now optional on `AntigravityModelWireProfile` since the Claude wire ids are accepted without a captured `labels.model_enum`. ([#3067](https://github.com/can1357/oh-my-pi/issues/3067)) ## [16.1.3] - 2026-06-19 ### Fixed diff --git a/packages/coding-agent/src/cli/auth-broker-cli.ts b/packages/coding-agent/src/cli/auth-broker-cli.ts index 3c2c295cb..881e44723 100644 --- a/packages/coding-agent/src/cli/auth-broker-cli.ts +++ b/packages/coding-agent/src/cli/auth-broker-cli.ts @@ -28,6 +28,7 @@ import { type OAuthCredential, type OAuthProvider, type OAuthProviderInfo, + PASTE_CODE_LOGIN_PROVIDERS, PROVIDER_REGISTRY, SqliteAuthCredentialStore, } from "@oh-my-pi/pi-ai"; @@ -211,6 +212,12 @@ async function runLocalLogin(provider: OAuthProvider): Promise { const storage = new AuthStorage(store); await storage.reload(); try { + // Only paste-code providers (fixed non-loopback redirect, e.g. GitLab Duo + // Agent's vscode:// URI) get the manual paste fallback. For normal loopback + // providers `onManualCodeInput` would make OAuthCallbackFlow race a readline + // prompt against the HTTP callback; if the callback wins, the outstanding + // prompt is never cancelled and leaves the terminal in a dirty/blocked state. + const usesManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(provider); await storage.login(provider, { onAuth({ url, instructions }) { process.stdout.write(`\nOpen this URL in your browser:\n${url}\n`); @@ -223,12 +230,13 @@ async function runLocalLogin(provider: OAuthProvider): Promise { onPrompt(p) { return ask(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`); }, - onManualCodeInput() { - // Providers with a fixed non-loopback redirect (e.g. GitLab Duo Agent's - // vscode:// URI) never hit the local callback server, so offer the same - // paste-the-redirect fallback the interactive TUI sign-in uses. - return ask("Paste the authorization code (or full redirect URL):"); - }, + ...(usesManualInput + ? { + onManualCodeInput() { + return ask("Paste the authorization code (or full redirect URL):"); + }, + } + : undefined), }); process.stdout.write(`\nCredentials saved to ${getAgentDbPath()}\n`); } finally {