fix(ai): 处理 rebase 后 Codex 新增的三项审阅意见

- catalog CHANGELOG 删除 rebase 重放进已发布 [16.1.4] 段落的重复 Claude 4.6 条目,使该段落与上游 main 完全一致(已发布段不可变)
- Duo Agent finally 清理在最终 idle timeout(重试已耗尽)时也发送 stop PATCH,避免代理/LB 持续断连场景下服务端工作流残留
- auth-broker login 仅对 pasteCodeFlow provider 传入 onManualCodeInput,普通 loopback provider 不再让 readline 提示与 HTTP 回调竞争导致终端残留
This commit is contained in:
jiwangyihao
2026-06-23 01:05:13 +08:00
parent c509b839fe
commit 4fdf2f83a5
3 changed files with 19 additions and 11 deletions
@@ -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;
}
-1
View File
@@ -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
@@ -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<void> {
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<void> {
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 {