mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 10:02:04 +08:00
fix: session list on tui
This commit is contained in:
@@ -14,7 +14,6 @@ import { createDebouncedSignal } from "../util/signal"
|
||||
import { useToast } from "../ui/toast"
|
||||
import { openWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } from "./dialog-workspace-create"
|
||||
import { Spinner } from "./spinner"
|
||||
import path from "path" // kilocode_change
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed"
|
||||
import { WorkspaceLabel } from "./workspace-label"
|
||||
@@ -31,7 +30,7 @@ export function DialogSessionList() {
|
||||
const toast = useToast()
|
||||
const [toDelete, setToDelete] = createSignal<string>()
|
||||
const [search, setSearch] = createDebouncedSignal("", 150)
|
||||
const [global, setGlobal] = createSignal(true) // kilocode_change - show all worktrees by default
|
||||
const [global, setGlobal] = createSignal(false) // kilocode_change - show current worktree by default
|
||||
const deleteHint = useCommandShortcut("session.delete")
|
||||
const quickSwitch1 = useCommandShortcut("session.quick_switch.1")
|
||||
const quickSwitch9 = useCommandShortcut("session.quick_switch.9")
|
||||
@@ -39,15 +38,17 @@ export function DialogSessionList() {
|
||||
// kilocode_change start - always fetch from experimental endpoint (returns GlobalSession with worktree info)
|
||||
// TODO: extend /experimental/session to accept `scope`/`path` so this dialog can respect the
|
||||
// upstream `session_directory_filter_enabled` KV toggle (via sync.session.query()) while
|
||||
// keeping worktree grouping. Currently the toggle has no effect here.
|
||||
// keeping worktree grouping.
|
||||
const [searchResults, searchActions] = createResource(
|
||||
() => search(),
|
||||
async (query) => {
|
||||
() => ({ query: search(), global: global(), directory: project.instance.directory() }), // kilocode_change
|
||||
async (input) => {
|
||||
const result = await sdk.client.experimental.session.list(
|
||||
{
|
||||
search: query || undefined,
|
||||
search: input.query || undefined,
|
||||
roots: true,
|
||||
worktrees: true,
|
||||
current: input.global ? undefined : "true",
|
||||
directory: input.global ? undefined : input.directory || undefined,
|
||||
limit: 30,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
@@ -59,15 +60,7 @@ export function DialogSessionList() {
|
||||
|
||||
const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
|
||||
|
||||
// kilocode_change start - client-side worktree filtering when global is off
|
||||
const sessions = createMemo(() => {
|
||||
const all = searchResults() ?? []
|
||||
if (global()) return all
|
||||
const root = project.instance.path().worktree
|
||||
if (!root || root === "/") return all
|
||||
return all.filter((s) => s.directory === root || s.directory.startsWith(root + path.sep))
|
||||
})
|
||||
// kilocode_change end
|
||||
const sessions = createMemo(() => searchResults() ?? []) // kilocode_change - endpoint applies worktree scope
|
||||
|
||||
function recover(session: NonNullable<ReturnType<typeof sessions>[number]>) {
|
||||
const workspace = project.workspace.get(session.workspaceID!)
|
||||
@@ -154,8 +147,6 @@ export function DialogSessionList() {
|
||||
.map((x) => x.id)
|
||||
}
|
||||
|
||||
const [browseOrder] = createSignal<string[]>(orderByRecency(sync.data.session))
|
||||
|
||||
const quickSwitchHint = createMemo(() => {
|
||||
const first = quickSwitch1()
|
||||
const last = quickSwitch9()
|
||||
@@ -176,8 +167,7 @@ export function DialogSessionList() {
|
||||
.map((x) => [x.id, x]),
|
||||
)
|
||||
|
||||
const searchResult = searchResults()
|
||||
const displayOrder = searchResult ? orderByRecency(searchResult) : browseOrder()
|
||||
const displayOrder = orderByRecency(sessions()) // kilocode_change - respect current scope
|
||||
|
||||
const pinned = local.session.pinned().filter((id) => sessionMap.has(id))
|
||||
const pinnedSet = new Set(pinned)
|
||||
@@ -335,6 +325,7 @@ export function DialogSessionList() {
|
||||
{
|
||||
command: "session.scope.toggle",
|
||||
title: global() ? "current" : "all",
|
||||
requiresSelection: false,
|
||||
onTrigger: async () => {
|
||||
setToDelete(undefined)
|
||||
setGlobal((v) => !v)
|
||||
|
||||
@@ -84,12 +84,13 @@ async function input(value?: string) {
|
||||
}
|
||||
|
||||
export function resolveThreadDirectory(project?: string, envPWD = process.env.PWD, cwd = process.cwd()) {
|
||||
// kilocode_change start - ignore stale PWD from wrappers such as `bun --cwd`
|
||||
// kilocode_change start - ignore stale PWD from wrappers such as `bun --cwd`, except kilo-dev's caller cwd
|
||||
const dev = process.env.KILO_DEV_CWD
|
||||
const real = Filesystem.resolve(cwd)
|
||||
const root = envPWD && Filesystem.resolve(envPWD) === real ? Filesystem.resolve(envPWD) : real
|
||||
const root = dev ? Filesystem.resolve(dev) : envPWD && Filesystem.resolve(envPWD) === real ? Filesystem.resolve(envPWD) : real
|
||||
// kilocode_change end
|
||||
if (project) return Filesystem.resolve(path.isAbsolute(project) ? project : path.join(root, project))
|
||||
return real // kilocode_change
|
||||
return dev ? root : real // kilocode_change
|
||||
}
|
||||
|
||||
export const TuiThreadCommand = cmd({
|
||||
|
||||
@@ -31,13 +31,7 @@ export interface DialogSelectProps<T> {
|
||||
onSelect?: (option: DialogSelectOption<T>) => void
|
||||
skipFilter?: boolean
|
||||
renderFilter?: boolean
|
||||
actions?: {
|
||||
command: string
|
||||
title: string
|
||||
side?: "left" | "right"
|
||||
disabled?: boolean
|
||||
onTrigger: (option: DialogSelectOption<T>) => void
|
||||
}[]
|
||||
actions?: DialogSelectAction<T>[] // kilocode_change - supports actions without a selected option
|
||||
footerHints?: {
|
||||
title: string
|
||||
label: string
|
||||
@@ -47,6 +41,27 @@ export interface DialogSelectProps<T> {
|
||||
current?: T
|
||||
}
|
||||
|
||||
// kilocode_change start - support list-level actions when no option is selected
|
||||
type DialogSelectActionBase = {
|
||||
command: string
|
||||
title: string
|
||||
side?: "left" | "right"
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
type DialogSelectAction<T> = DialogSelectActionBase &
|
||||
(
|
||||
| {
|
||||
requiresSelection?: true
|
||||
onTrigger: (option: DialogSelectOption<T>) => void
|
||||
}
|
||||
| {
|
||||
requiresSelection: false
|
||||
onTrigger: () => void
|
||||
}
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
export interface DialogSelectOption<T = any> {
|
||||
title: string
|
||||
value: T
|
||||
@@ -303,6 +318,12 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
category: "Dialog",
|
||||
run() {
|
||||
setStore("input", "keyboard")
|
||||
// kilocode_change start - allow actions such as scope toggles on empty lists
|
||||
if (item.requiresSelection === false) {
|
||||
item.onTrigger()
|
||||
return
|
||||
}
|
||||
// kilocode_change end
|
||||
const option = selected()
|
||||
if (!option) return
|
||||
item.onTrigger(option)
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { Provider } from "@/provider/provider"
|
||||
import { zod as toZod } from "@opencode-ai/core/effect-zod"
|
||||
import { ENV_FEATURE } from "@kilocode/kilo-gateway"
|
||||
import { fn } from "@/kilocode/fn"
|
||||
import path from "path"
|
||||
|
||||
export namespace KiloSession {
|
||||
const log = Log.create({ service: "session.kilo" })
|
||||
@@ -316,6 +317,7 @@ export namespace KiloSession {
|
||||
projectID?: string
|
||||
directory?: string
|
||||
directories?: string[]
|
||||
currentDirectory?: string
|
||||
roots?: boolean
|
||||
start?: number
|
||||
cursor?: number
|
||||
@@ -360,6 +362,19 @@ export namespace KiloSession {
|
||||
|
||||
const limit = input.limit ?? 100
|
||||
const dirs = [...new Set((input.directories ?? []).map((dir) => Filesystem.resolve(dir)))]
|
||||
const sorted = [...dirs].sort((a, b) => b.length - a.length)
|
||||
const worktree = (dir: string) => {
|
||||
for (const root of sorted) {
|
||||
if (!Filesystem.contains(root, dir)) continue
|
||||
const rel = path.relative(root, dir)
|
||||
const parts = rel.split(path.sep)
|
||||
if ((parts[0] === ".kilo" || parts[0] === ".kilocode") && parts[1] === "worktrees" && parts[2]) {
|
||||
return path.join(root, parts[0], parts[1], parts[2])
|
||||
}
|
||||
return root
|
||||
}
|
||||
}
|
||||
const current = input.currentDirectory ? worktree(Filesystem.resolve(input.currentDirectory)) : undefined
|
||||
|
||||
const rows = Database.use((db) => {
|
||||
const query =
|
||||
@@ -377,7 +392,10 @@ export namespace KiloSession {
|
||||
dirs.length > 0
|
||||
? rows.filter((row) => {
|
||||
const dir = Filesystem.resolve(row.directory)
|
||||
return dirs.some((root) => Filesystem.contains(root, dir))
|
||||
const root = worktree(dir)
|
||||
if (!root) return false
|
||||
if (input.currentDirectory) return root === current
|
||||
return true
|
||||
})
|
||||
: rows
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ export const SessionListQuery = Schema.Struct({
|
||||
// kilocode_change start
|
||||
projectID: Schema.optional(Schema.String),
|
||||
worktrees: Schema.optional(QueryBoolean),
|
||||
current: Schema.optional(QueryBoolean),
|
||||
// kilocode_change end
|
||||
roots: Schema.optional(QueryBoolean),
|
||||
start: Schema.optional(Schema.NumberFromString),
|
||||
|
||||
@@ -195,14 +195,18 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
// kilocode_change start
|
||||
const state = yield* InstanceState.context
|
||||
const projectID = ctx.query.worktrees && !ctx.query.projectID ? state.project.id : ctx.query.projectID
|
||||
const directories = ctx.query.worktrees ? yield* WorktreeFamily.list() : undefined
|
||||
const sorted = directories ? [...directories].sort((a, b) => b.length - a.length) : undefined
|
||||
const roots = ctx.query.worktrees ? yield* WorktreeFamily.list() : undefined
|
||||
const directory = ctx.query.current ? ctx.query.directory : undefined
|
||||
const sorted = roots ? [...roots].sort((a, b) => b.length - a.length) : undefined
|
||||
const current = sorted && directory ? sorted.find((dir) => Filesystem.contains(dir, directory)) : undefined
|
||||
// kilocode_change end
|
||||
if (roots && directory && !current) return HttpServerResponse.jsonUnsafe([]) // kilocode_change
|
||||
const sessions = Array.from(
|
||||
Session.listGlobal({
|
||||
projectID, // kilocode_change
|
||||
directory: ctx.query.worktrees ? undefined : ctx.query.directory, // kilocode_change
|
||||
directories, // kilocode_change
|
||||
directories: roots, // kilocode_change
|
||||
currentDirectory: directory, // kilocode_change
|
||||
roots: ctx.query.roots,
|
||||
start: ctx.query.start,
|
||||
cursor: ctx.query.cursor,
|
||||
|
||||
@@ -1027,6 +1027,7 @@ export function* listGlobal(input?: {
|
||||
projectID?: string
|
||||
directory?: string
|
||||
directories?: string[]
|
||||
currentDirectory?: string
|
||||
roots?: boolean
|
||||
start?: number
|
||||
cursor?: number
|
||||
|
||||
@@ -30,9 +30,9 @@ const readRuntimeFlags = () =>
|
||||
|
||||
export function getChannelPath(flags: Pick<DatabaseFlags, "disableChannelDb"> = readRuntimeFlags()) {
|
||||
if (["latest", "beta", "prod"].includes(InstallationChannel) || flags.disableChannelDb)
|
||||
return path.join(Global.Path.data, "kilo.db")
|
||||
return path.join(Global.Path.data, "kilo.db") // kilocode_change
|
||||
const safe = InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||
return path.join(Global.Path.data, `opencode-${safe}.db`)
|
||||
return path.join(Global.Path.data, `kilo-${safe}.db`) // kilocode_change
|
||||
}
|
||||
|
||||
export const getPath = (flags?: Pick<DatabaseFlags, "disableChannelDb">) => {
|
||||
|
||||
Reference in New Issue
Block a user