fix(auth): routed setup copy shortcut through prompt
Paste-code provider prompts now preserve the Alt+C authentication URL retry while their input owns TUI focus.\n\nFixes #2908
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed provider setup sign-in URLs to attempt clipboard/OSC 52 copy and expose an Alt+C retry shortcut, so authentication is not blocked when TUI selection is unavailable ([#2908](https://github.com/can1357/oh-my-pi/issues/2908)).
|
||||
|
||||
## [16.0.5] - 2026-06-17
|
||||
|
||||
### Added
|
||||
@@ -23,7 +27,6 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed provider setup sign-in URLs to attempt clipboard/OSC 52 copy and expose an Alt+C retry shortcut, so authentication is not blocked when TUI selection is unavailable ([#2908](https://github.com/can1357/oh-my-pi/issues/2908)).
|
||||
- Fixed Matplotlib figure display to emit PNG output immediately when `display(fig)` is called, even if the figure is closed before the end-of-cell flush
|
||||
- Fixed persisted tool-result image payloads in `details.images` to externalize and resolve through the session blob store, so generated-image details survive resume without stale blob refs or truncation
|
||||
- Fixed duplicate Matplotlib image output by skipping the automatic end-of-request figure flush for figures that were already displayed through `display(fig)`
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
||||
import { PASTE_CODE_LOGIN_PROVIDERS } from "@oh-my-pi/pi-ai";
|
||||
import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
|
||||
import { Input, matchesKey, type SgrMouseEvent, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
||||
import {
|
||||
type Component,
|
||||
type Focusable,
|
||||
Input,
|
||||
matchesKey,
|
||||
type SgrMouseEvent,
|
||||
wrapTextWithAnsi,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { getAgentDbPath } from "@oh-my-pi/pi-utils";
|
||||
import { copyToClipboard } from "../../../utils/clipboard";
|
||||
import { OAuthSelectorComponent } from "../../components/oauth-selector";
|
||||
@@ -16,10 +23,48 @@ function loginCopyHint(): string {
|
||||
return theme.fg("dim", "(clipboard copy attempted; Alt+C retries)");
|
||||
}
|
||||
|
||||
class CopyablePromptInput implements Component, Focusable {
|
||||
#input: Input;
|
||||
#onCopy: () => void;
|
||||
|
||||
constructor(input: Input, onCopy: () => void) {
|
||||
this.#input = input;
|
||||
this.#onCopy = onCopy;
|
||||
}
|
||||
|
||||
get focused(): boolean {
|
||||
return this.#input.focused;
|
||||
}
|
||||
|
||||
set focused(value: boolean) {
|
||||
this.#input.focused = value;
|
||||
}
|
||||
|
||||
setUseTerminalCursor(useTerminalCursor: boolean): void {
|
||||
this.#input.setUseTerminalCursor(useTerminalCursor);
|
||||
}
|
||||
|
||||
render(width: number): readonly string[] {
|
||||
return this.#input.render(width);
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
if (matchesKey(data, "alt+c")) {
|
||||
this.#onCopy();
|
||||
return;
|
||||
}
|
||||
this.#input.handleInput(data);
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.#input.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
interface PromptState {
|
||||
message: string;
|
||||
placeholder?: string;
|
||||
input: Input;
|
||||
input: CopyablePromptInput;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,9 +256,12 @@ export class SignInTab implements SetupTab {
|
||||
#showPrompt(prompt: { message: string; placeholder?: string }): Promise<string> {
|
||||
this.#resolvePrompt("");
|
||||
const input = new Input();
|
||||
const focusInput = new CopyablePromptInput(input, () => {
|
||||
void this.#copyAuthUrl();
|
||||
});
|
||||
const pending = Promise.withResolvers<string>();
|
||||
this.#promptResolve = pending.resolve;
|
||||
this.#prompt = { message: prompt.message, placeholder: prompt.placeholder, input };
|
||||
this.#prompt = { message: prompt.message, placeholder: prompt.placeholder, input: focusInput };
|
||||
input.onSubmit = value => {
|
||||
this.#resolvePrompt(value);
|
||||
};
|
||||
@@ -221,7 +269,7 @@ export class SignInTab implements SetupTab {
|
||||
this.#loginAbort?.abort();
|
||||
this.#resolvePrompt("");
|
||||
};
|
||||
this.host.setFocus(input);
|
||||
this.host.setFocus(focusInput);
|
||||
this.host.requestRender();
|
||||
return pending.promise;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SignInTab } from "@oh-my-pi/pi-coding-agent/modes/setup-wizard/scenes/s
|
||||
import type { SetupSceneHost } from "@oh-my-pi/pi-coding-agent/modes/setup-wizard/scenes/types";
|
||||
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import * as clipboard from "@oh-my-pi/pi-coding-agent/utils/clipboard";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
|
||||
beforeAll(async () => {
|
||||
await initTheme();
|
||||
@@ -19,6 +20,7 @@ describe("SignInTab", () => {
|
||||
const url = `https://example.com/oauth/authorize?client_id=omp&redirect_uri=http%3A%2F%2Flocalhost%3A45454%2Fcallback&state=${"a".repeat(96)}`;
|
||||
const loginGate = Promise.withResolvers<void>();
|
||||
const copySpy = vi.spyOn(clipboard, "copyToClipboard").mockResolvedValue(undefined);
|
||||
let focusTarget: Component | undefined;
|
||||
const openedUrls: string[] = [];
|
||||
|
||||
const authStorage = {
|
||||
@@ -47,7 +49,9 @@ describe("SignInTab", () => {
|
||||
},
|
||||
requestRender(): void {},
|
||||
finish(): void {},
|
||||
setFocus(): void {},
|
||||
setFocus(component: Component | null): void {
|
||||
focusTarget = component ?? undefined;
|
||||
},
|
||||
restoreFocus(): void {},
|
||||
} as unknown as SetupSceneHost;
|
||||
|
||||
@@ -64,7 +68,10 @@ describe("SignInTab", () => {
|
||||
expect(compact).not.toContain("…");
|
||||
expect(rendered.join("\n")).toContain(`\x1b]8;;${url}\x07Open login URL\x1b]8;;\x07`);
|
||||
expect(openedUrls).toEqual([url]);
|
||||
expect(copySpy).toHaveBeenCalledWith(url);
|
||||
expect(focusTarget).toBeDefined();
|
||||
focusTarget?.handleInput?.("\x1bc");
|
||||
expect(copySpy).toHaveBeenCalledTimes(2);
|
||||
expect(copySpy).toHaveBeenLastCalledWith(url);
|
||||
|
||||
// On a ~24-row terminal the wizard body ends up ~8 rows; the OSC8
|
||||
// link, a plain URL row, and the focused input must survive that clip.
|
||||
|
||||
Reference in New Issue
Block a user