diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index ab3d0968925..65e1077d6f0 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -770,6 +770,7 @@ function ErrorComponent(props: { renderer.destroy() win32FlushInputBuffer() await props.onExit() + setTimeout(() => process.exit(0), 100) } useKeyboard((evt) => { diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index d63c248fb83..f76c29e8760 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -125,6 +125,7 @@ export function Prompt(props: PromptProps) { mode: "normal" | "shell" extmarkToPartIndex: Map interrupt: number + appExitArmed: number placeholder: number }>({ placeholder: Math.floor(Math.random() * PLACEHOLDERS.length), @@ -135,6 +136,7 @@ export function Prompt(props: PromptProps) { mode: "normal", extmarkToPartIndex: new Map(), interrupt: 0, + appExitArmed: 0, }) createEffect( @@ -865,8 +867,17 @@ export function Prompt(props: PromptProps) { } if (keybind.match("app_exit", e)) { if (store.prompt.input === "") { - await exit() - // Don't preventDefault - let textarea potentially handle the event + const now = Date.now() + if (now - store.appExitArmed < 2000) { + await exit() + } else { + setStore("appExitArmed", now) + toast.show({ + message: "Press Ctrl+C again to exit", + variant: "warning", + duration: 2000, + }) + } e.preventDefault() return } @@ -967,7 +978,9 @@ export function Prompt(props: PromptProps) { !sync.data.config.experimental?.disable_paste_summary ) { event.preventDefault() - pasteText(pastedContent, `[Pasted ~${lineCount} lines]`) + // Temporarily insert text directly without extmark to avoid the "can not paste certain string" bug + // which causes UI corruption and data loss on multi-line text + input.insertText(pastedContent) return } diff --git a/packages/opencode/src/cli/cmd/tui/context/exit.tsx b/packages/opencode/src/cli/cmd/tui/context/exit.tsx index a6f775913a4..9f18c7365b1 100644 --- a/packages/opencode/src/cli/cmd/tui/context/exit.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/exit.tsx @@ -43,6 +43,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({ const text = store.get() if (text) process.stdout.write(text + "\n") await input.onExit?.() + setTimeout(() => process.exit(0), 100) }, { message: store, diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 9af79278c06..d2d3327940c 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -46,6 +46,11 @@ process.on("uncaughtException", (e) => { }) }) +process.on("SIGHUP", () => { + Log.Default.info("SIGHUP received, exiting...") + process.exit(0) +}) + let cli = yargs(hideBin(process.argv)) .parserConfiguration({ "populate--": true }) .scriptName("opencode")