fix(coding-agent): routed non-bracketed paste payloads to hook components

- Added a `pasteText` handler to `HookEditorComponent` to forward non-bracketed OSC 5522 paste text into its inner editor.
- Added a `pasteText` handler to `HookInputComponent` that forwards text to the input and resets the interaction timeout.
- Extended hook editor and timeout tests to cover enhanced-paste payload absorption and behavior retention.
This commit is contained in:
can1357
2026-06-10 04:25:01 +02:00
parent a707e2daf9
commit 037aa6b345
5 changed files with 73 additions and 0 deletions
+2
View File
@@ -9,6 +9,7 @@
- npm installs now execute a prebundled single-file entry: `bin.omp` points at `dist/cli.js` (built by `scripts/bundle-dist.ts` during `prepack`, ~18MB minified, natives/transformers/mupdf external), cutting npm-install cold start by roughly 3x versus transpiling the raw TypeScript graph per launch; `src/**` stays published for SDK consumers and worker fallbacks
- Plain interactive TTY launches print a dim two-line startup splash (`omp <version>` / `Initializing session…`) before session construction so first pixels appear immediately; suppressed for resume/fork/continue flows, quiet mode, `PI_TIMING`, and non-TTY stdio
- Added `/stats` to launch the local stats dashboard from an active session, syncing session files first and opening the same browser dashboard as `omp stats`.
- `/settings` now supports type-to-search filtering on setting labels, paths, descriptions, and values; Escape clears an active search before closing the panel.
### Changed
@@ -42,6 +43,7 @@
### Fixed
- Fixed pasting into the ask tool's "Other (type your own)" text box (and hook input/editor dialogs) on terminals with OSC 5522 enhanced paste (kitty protocol): the enhanced-paste focus routing only targets components exposing a `pasteText` hook, and the dialog wrappers had none, so the payload was stuffed into the main prompt editor hidden behind the dialog. `HookEditorComponent` and `HookInputComponent` now forward `pasteText` to their inner editor/input (pasting also resets the input dialog's timeout countdown like any keystroke).
- Fixed auto-retry giving up after one attempt ("Provider requested Xms wait, exceeds retry.maxDelayMs") on a usage-limit 429 when every sibling account was only momentarily blocked: the retry delay now waits for the earliest sibling unblock when that comes sooner than the provider's multi-hour retry-after, so the next attempt picks up the recovered account instead of failing fast.
- Fixed Hindsight `per-project-tagged` mental-model seeding so each project gets its own conventions/decisions models and session context only injects active-project or untagged models ([#2218](https://github.com/can1357/oh-my-pi/issues/2218)).
@@ -86,6 +86,14 @@ export class HookEditorComponent extends Container {
this.#onSubmitCallback(this.#editor.getExpandedText());
}
/** Route non-bracketed paste transports (e.g. kitty's OSC 5522 enhanced clipboard)
* into the inner editor, mirroring bracketed-paste semantics. Without this hook,
* enhanced-paste routing falls back to the main prompt editor hidden behind the
* dialog (#2127 routing contract). */
pasteText(text: string): void {
this.#editor.pasteText(text);
}
/** Prompt-style: raw Enter submits; Editor owns newline-producing sequences. */
#handlePromptStyleInput(keyData: string): void {
// Prompt-style keeps Escape as an explicit cancel key and also honors app.interrupt remaps.
@@ -73,6 +73,14 @@ export class HookInputComponent extends Container {
}
}
/** Route non-bracketed paste transports (e.g. kitty's OSC 5522 enhanced clipboard)
* into the inner input, mirroring bracketed-paste semantics. Pasting counts as
* interaction, so the timeout countdown resets like any keystroke. */
pasteText(text: string): void {
this.#countdown?.reset();
this.#input.pasteText(text);
}
dispose(): void {
this.#countdown?.dispose();
}
@@ -218,6 +218,29 @@ describe("HookEditorComponent prompt-style mode", () => {
expect(onCancel).not.toHaveBeenCalled();
});
it("absorbs enhanced-paste payloads delivered via pasteText (kitty OSC 5522 routing)", () => {
// Regression: pasting into the ask tool's "Other" editor on OSC 5522
// terminals routed the payload to the hidden main prompt, because the
// enhanced-paste focus routing only targets components exposing a
// `pasteText` hook and the dialog wrapper had none (#2127 contract).
const onSubmit = vi.fn();
const onCancel = vi.fn();
const component = new HookEditorComponent(createTui(), "Prompt", undefined, onSubmit, onCancel, {
promptStyle: true,
});
const pasted = largePasteText();
component.pasteText(pasted);
expect(renderText(component)).toContain("[Paste #1, +11 lines]");
component.handleInput("\r");
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith(pasted);
expect(onCancel).not.toHaveBeenCalled();
});
it("expands large paste markers when submitting on Enter", () => {
const onSubmit = vi.fn();
const onCancel = vi.fn();
@@ -72,4 +72,36 @@ describe("HookInputComponent timeout", () => {
component.dispose();
});
it("absorbs enhanced-paste payloads via pasteText and resets the timeout", () => {
// Regression: enhanced-paste (kitty OSC 5522) focus routing only targets
// components exposing a `pasteText` hook; without one the payload landed
// in the hidden main prompt behind the dialog (#2127 contract).
vi.useFakeTimers();
const onSubmit = vi.fn();
const onCancel = vi.fn();
const onTimeout = vi.fn();
const tui = { requestRender: vi.fn() } as unknown as TUI;
const component = new HookInputComponent("Prompt", undefined, onSubmit, onCancel, {
timeout: 1_000,
tui,
onTimeout,
});
vi.advanceTimersByTime(900);
component.pasteText("sk-line1\nsk-line2");
vi.advanceTimersByTime(900);
expect(onTimeout).not.toHaveBeenCalled();
expect(onCancel).not.toHaveBeenCalled();
component.handleInput("\n");
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith("sk-line1sk-line2");
component.dispose();
});
});