Skip to content

Commit ce7a4d2

Browse files
committed
feat: add session archive/unarchive support to TUI
1 parent 6b02165 commit ce7a4d2

File tree

4 files changed

+103
-20
lines changed

4 files changed

+103
-20
lines changed

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,20 @@ function App() {
703703
}
704704
})
705705

706+
sdk.event.on(SessionApi.Event.Updated.type, (evt) => {
707+
if (
708+
route.data.type === "session" &&
709+
route.data.sessionID === evt.properties.info.id &&
710+
evt.properties.info.time.archived
711+
) {
712+
route.navigate({ type: "home" })
713+
toast.show({
714+
variant: "info",
715+
message: "The current session was archived",
716+
})
717+
}
718+
})
719+
706720
sdk.event.on(SessionApi.Event.Error.type, (evt) => {
707721
const error = evt.properties.error
708722
if (error && typeof error === "object" && error.name === "MessageAbortedError") return

packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { DialogSessionRename } from "./dialog-session-rename"
1111
import { useKV } from "../context/kv"
1212
import { createDebouncedSignal } from "../util/signal"
1313
import { Spinner } from "./spinner"
14+
import { Keybind } from "@/util/keybind"
1415

1516
export function DialogSessionList() {
1617
const dialog = useDialog()
@@ -22,6 +23,7 @@ export function DialogSessionList() {
2223
const kv = useKV()
2324

2425
const [toDelete, setToDelete] = createSignal<string>()
26+
const [showArchived, setShowArchived] = createSignal(false)
2527
const [search, setSearch] = createDebouncedSignal("", 150)
2628

2729
const [searchResults] = createResource(search, async (query) => {
@@ -37,7 +39,11 @@ export function DialogSessionList() {
3739
const options = createMemo(() => {
3840
const today = new Date().toDateString()
3941
return sessions()
40-
.filter((x) => x.parentID === undefined)
42+
.filter((x) => {
43+
if (x.parentID !== undefined) return false
44+
if (showArchived()) return !!x.time.archived
45+
return !x.time.archived
46+
})
4147
.toSorted((a, b) => b.time.updated - a.time.updated)
4248
.map((x) => {
4349
const date = new Date(x.time.updated)
@@ -65,7 +71,7 @@ export function DialogSessionList() {
6571

6672
return (
6773
<DialogSelect
68-
title="Sessions"
74+
title={showArchived() ? "Archived Sessions" : "Sessions"}
6975
options={options()}
7076
skipFilter={true}
7177
current={currentSessionID()}
@@ -74,32 +80,75 @@ export function DialogSessionList() {
7480
setToDelete(undefined)
7581
}}
7682
onSelect={(option) => {
83+
if (showArchived()) {
84+
sdk.client.session.update({
85+
sessionID: option.value,
86+
time: { archived: 0 },
87+
})
88+
return
89+
}
7790
route.navigate({
7891
type: "session",
7992
sessionID: option.value,
8093
})
8194
dialog.clear()
8295
}}
8396
keybind={[
97+
...(showArchived()
98+
? []
99+
: [
100+
{
101+
keybind: keybind.all.session_delete?.[0],
102+
title: "delete",
103+
onTrigger: async (option: { value: string }) => {
104+
if (toDelete() === option.value) {
105+
sdk.client.session.delete({
106+
sessionID: option.value,
107+
})
108+
setToDelete(undefined)
109+
return
110+
}
111+
setToDelete(option.value)
112+
},
113+
},
114+
{
115+
keybind: keybind.all.session_archive?.[0],
116+
title: "archive",
117+
onTrigger: async (option: { value: string }) => {
118+
sdk.client.session.update({
119+
sessionID: option.value,
120+
time: { archived: Date.now() },
121+
})
122+
},
123+
},
124+
{
125+
keybind: keybind.all.session_rename?.[0],
126+
title: "rename",
127+
onTrigger: async (option: { value: string }) => {
128+
dialog.replace(() => <DialogSessionRename session={option.value} />)
129+
},
130+
},
131+
]),
132+
...(showArchived()
133+
? [
134+
{
135+
keybind: keybind.all.session_archive?.[0],
136+
title: "unarchive",
137+
onTrigger: async (option: { value: string }) => {
138+
sdk.client.session.update({
139+
sessionID: option.value,
140+
time: { archived: 0 },
141+
})
142+
},
143+
},
144+
]
145+
: []),
84146
{
85-
keybind: keybind.all.session_delete?.[0],
86-
title: "delete",
87-
onTrigger: async (option) => {
88-
if (toDelete() === option.value) {
89-
sdk.client.session.delete({
90-
sessionID: option.value,
91-
})
92-
setToDelete(undefined)
93-
return
94-
}
95-
setToDelete(option.value)
96-
},
97-
},
98-
{
99-
keybind: keybind.all.session_rename?.[0],
100-
title: "rename",
101-
onTrigger: async (option) => {
102-
dialog.replace(() => <DialogSessionRename session={option.value} />)
147+
keybind: Keybind.parse("tab")[0],
148+
title: showArchived() ? "active" : "archived",
149+
onTrigger: async () => {
150+
setShowArchived((prev) => !prev)
151+
setToDelete(undefined)
103152
},
104153
},
105154
]}

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,25 @@ export function Session() {
460460
dialog.clear()
461461
},
462462
},
463+
{
464+
title: "Archive session",
465+
value: "session.archive",
466+
keybind: "session_archive",
467+
category: "Session",
468+
slash: {
469+
name: "archive",
470+
},
471+
onSelect: async (dialog) => {
472+
await sdk.client.session
473+
.update({
474+
sessionID: route.sessionID,
475+
time: { archived: Date.now() },
476+
})
477+
.then(() => toast.show({ message: "Session archived", variant: "success" }))
478+
.catch(() => toast.show({ message: "Failed to archive session", variant: "error" }))
479+
dialog.clear()
480+
},
481+
},
463482
{
464483
title: "Undo previous message",
465484
value: "session.undo",

packages/opencode/src/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ export namespace Config {
768768
session_fork: z.string().optional().default("none").describe("Fork session from message"),
769769
session_rename: z.string().optional().default("ctrl+r").describe("Rename session"),
770770
session_delete: z.string().optional().default("ctrl+d").describe("Delete session"),
771+
session_archive: z.string().optional().default("none").describe("Archive session"),
771772
stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
772773
model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
773774
model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),

0 commit comments

Comments
 (0)