feat(tui): add copy session ID command (#47064)

Co-authored-by: thdxr <826656+thdxr@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot] 2026-09-03 13:50:21 +00:00 committed by GitHub
parent 0f6393dab1
commit 5d8a01dedc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 0 deletions

View file

@ -103,6 +103,7 @@ export const Definitions = {
"session.export": keybind("<leader>x", "Export session to editor"),
"session.copy": keybind("none", "Copy session transcript"),
"session.copy.id": keybind("none", "Copy session ID"),
"session.move": keybind("none", "Move session"),
"session.new": keybind("<leader>n", "Create a new session"),
"session.list": keybind("<leader>l", "List all sessions"),

View file

@ -1182,6 +1182,18 @@ export function Session(props: {
dialog.clear()
},
},
{
title: "Copy session ID",
id: "session.copy.id",
group: "Session",
run: () => {
clipboard
.write(route.sessionID)
.then(() => toast.show({ message: "Session ID copied to clipboard!", variant: "success" }))
.catch(() => toast.show({ message: "Failed to copy session ID", variant: "error" }))
dialog.clear()
},
},
{
title: "Copy session transcript",
id: "session.copy",

View file

@ -0,0 +1,78 @@
import { expect, spyOn, test } from "bun:test"
import { InputRenderable, TextareaRenderable } from "@opentui/core"
import { createTestRenderer } from "@opentui/core/testing"
import { Effect, FileSystem } from "effect"
import { Global } from "@opencode-ai/util/global"
import { createEventStream, createFetch, directory, json } from "./fixture/tui-client"
import { tmpdir } from "./fixture/fixture"
test.each(["success", "failure", "home"])("Copy session ID from Ctrl+P (%s)", async (mode) => {
await using state = await tmpdir()
const setup = await createTestRenderer({ width: 100, height: 30, useThread: false, kittyKeyboard: true })
setup.renderer.start()
Object.defineProperty(setup.renderer, "capabilities", { get: () => null })
const copy = spyOn(setup.renderer, "copyToClipboardOSC52").mockReturnValue(mode === "success")
const sessionID = "ses_copy_id"
const events = createEventStream()
const calls = createFetch((url) => {
if (url.pathname === `/api/session/${sessionID}`)
return json({
data: {
id: sessionID,
projectID: "proj_test",
title: "Copy ID fixture",
location: { directory },
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
time: { created: 0, updated: 0 },
},
})
if (url.pathname === `/api/session/${sessionID}/message`) return json({ data: [], cursor: {} })
if (url.pathname === `/api/session/${sessionID}/inbox` || url.pathname === `/api/session/${sessionID}/permission`)
return json({ data: [] })
return undefined
}, events)
const server = Bun.serve({ port: 0, idleTimeout: 0, fetch: (request) => calls.fetch(request) })
const { run } = await import("../src/app")
const task = Effect.runPromise(
run({
app: { name: "test", version: "test", channel: "test" },
server: { endpoint: { url: server.url.toString() } },
config: { get: async () => ({ animations: false }), update: async () => ({}) },
packages: { prepare: async () => ({ directory: "" }) },
terminalHandoff: async () => ({ renderer: setup.renderer, mode: "dark", complete: () => {} }),
args: mode === "home" ? {} : { sessionID },
log: () => {},
}).pipe(Effect.provide(Global.layerWith({ state: state.path })), Effect.provide(FileSystem.layerNoop({}))),
)
try {
await setup.waitFor(() => setup.renderer.currentFocusedEditor instanceof TextareaRenderable)
setup.mockInput.pressKey("p", { ctrl: true })
await setup.waitFor(() => setup.renderer.currentFocusedEditor instanceof InputRenderable)
await setup.mockInput.typeText("Copy session ID")
if (mode === "home") {
await setup.waitForVisualIdle()
expect(setup.captureCharFrame()).not.toMatch(/Copy session ID\s+Session/)
expect(copy).not.toHaveBeenCalled()
return
}
await setup.waitForFrame((frame) => /Copy session ID\s+Session/.test(frame))
setup.mockInput.pressEnter()
const frame = await setup.waitForFrame((frame) =>
frame.includes(mode === "success" ? "Session ID copied to clipboard!" : "Failed to copy session ID"),
)
expect(copy).toHaveBeenCalledTimes(1)
expect(copy.mock.calls[0]?.[0]).toBe(sessionID)
expect(frame).not.toContain("Copy session ID")
await setup.waitFor(
() =>
setup.renderer.currentFocusedEditor instanceof TextareaRenderable &&
!(setup.renderer.currentFocusedEditor instanceof InputRenderable),
)
} finally {
copy.mockRestore()
setup.renderer.destroy()
await task
await server.stop()
}
})