fix(tui): filter only connected workspaces in dialog; add warp synthetic message (#25915)
This commit is contained in:
@@ -33,6 +33,28 @@ export type WorkspaceSelection =
|
|||||||
type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" }
|
type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" }
|
||||||
type ExistingWorkspaceSelectValue = { workspace: Workspace }
|
type ExistingWorkspaceSelectValue = { workspace: Workspace }
|
||||||
|
|
||||||
|
export function recentConnectedWorkspaces<WorkspaceInfo extends { id: string }>(input: {
|
||||||
|
sessions: readonly { workspaceID?: string; time: { updated: number } }[]
|
||||||
|
get: (workspaceID: string) => WorkspaceInfo | undefined
|
||||||
|
status: (workspaceID: string) => string | undefined
|
||||||
|
limit?: number
|
||||||
|
}) {
|
||||||
|
const workspaces = input.sessions
|
||||||
|
.toSorted((a, b) => b.time.updated - a.time.updated)
|
||||||
|
.flatMap((session) => {
|
||||||
|
const workspace = session.workspaceID ? input.get(session.workspaceID) : undefined
|
||||||
|
return workspace && input.status(workspace.id) === "connected" ? [workspace] : []
|
||||||
|
})
|
||||||
|
.filter((workspace, index, list) => list.findIndex((item) => item.id === workspace.id) === index)
|
||||||
|
const recent = workspaces.slice(0, input.limit ?? 3)
|
||||||
|
|
||||||
|
return { recent, hasMore: recent.length < workspaces.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warpReminderText(dir: string) {
|
||||||
|
return `<system-reminder>The user has changed the current working directory to "${dir}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.</system-reminder>`
|
||||||
|
}
|
||||||
|
|
||||||
async function loadWorkspaceAdapters(input: {
|
async function loadWorkspaceAdapters(input: {
|
||||||
sdk: ReturnType<typeof useSDK>
|
sdk: ReturnType<typeof useSDK>
|
||||||
sync: ReturnType<typeof useSync>
|
sync: ReturnType<typeof useSync>
|
||||||
@@ -77,7 +99,7 @@ export async function warpWorkspaceSession(input: {
|
|||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
const result = await input.sdk.client.experimental.workspace
|
const result = await input.sdk.client.experimental.workspace
|
||||||
.warp({
|
.warp({
|
||||||
id: input.workspaceID ?? undefined,
|
id: input.workspaceID,
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
})
|
})
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
@@ -93,10 +115,30 @@ export async function warpWorkspaceSession(input: {
|
|||||||
|
|
||||||
await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
|
await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
|
||||||
|
|
||||||
|
const dir = input.project.instance.directory() || input.sync.path.directory
|
||||||
|
if (dir) {
|
||||||
|
await input.sdk.client.session
|
||||||
|
.promptAsync({
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
workspace: input.workspaceID ?? undefined,
|
||||||
|
noReply: true,
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: warpReminderText(dir),
|
||||||
|
synthetic: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()])
|
await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()])
|
||||||
|
|
||||||
input.done?.()
|
if (input.done) {
|
||||||
if (input.done) return true
|
input.done()
|
||||||
|
return true
|
||||||
|
}
|
||||||
input.dialog.clear()
|
input.dialog.clear()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -125,15 +167,11 @@ export function DialogWorkspaceSelect(props: {
|
|||||||
const options = createMemo<DialogSelectOption<WorkspaceSelectValue>[]>(() => {
|
const options = createMemo<DialogSelectOption<WorkspaceSelectValue>[]>(() => {
|
||||||
const list = adapters()
|
const list = adapters()
|
||||||
if (!list) return []
|
if (!list) return []
|
||||||
const recent = sync.data.session
|
const { recent, hasMore } = recentConnectedWorkspaces({
|
||||||
.toSorted((a, b) => b.time.updated - a.time.updated)
|
sessions: sync.data.session,
|
||||||
.flatMap((session) => (session.workspaceID ? [session.workspaceID] : []))
|
get: project.workspace.get,
|
||||||
.filter((workspaceID, index, list) => list.indexOf(workspaceID) === index)
|
status: project.workspace.status,
|
||||||
.flatMap((workspaceID) => {
|
})
|
||||||
const workspace = project.workspace.get(workspaceID)
|
|
||||||
return workspace && project.workspace.status(workspace.id) === "connected" ? [workspace] : []
|
|
||||||
})
|
|
||||||
.slice(0, 3)
|
|
||||||
return [
|
return [
|
||||||
...list.map((adapter) => ({
|
...list.map((adapter) => ({
|
||||||
title: adapter.name,
|
title: adapter.name,
|
||||||
@@ -158,12 +196,16 @@ export function DialogWorkspaceSelect(props: {
|
|||||||
},
|
},
|
||||||
category: "Choose workspace",
|
category: "Choose workspace",
|
||||||
})),
|
})),
|
||||||
{
|
...(hasMore
|
||||||
title: "View all workspaces",
|
? [
|
||||||
value: { type: "existing-list" as const },
|
{
|
||||||
description: "Choose from all workspaces",
|
title: "View all workspaces",
|
||||||
category: "Choose workspace",
|
value: { type: "existing-list" as const },
|
||||||
},
|
description: "Choose from all workspaces",
|
||||||
|
category: "Choose workspace",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { recentConnectedWorkspaces } from "../../../../src/cli/cmd/tui/component/dialog-workspace-create"
|
||||||
|
|
||||||
|
describe("recentConnectedWorkspaces", () => {
|
||||||
|
test("returns unique connected workspaces after filtering missing and inactive entries", () => {
|
||||||
|
const workspaces = [
|
||||||
|
{ id: "wrk_a", name: "alpha" },
|
||||||
|
{ id: "wrk_b", name: "beta" },
|
||||||
|
{ id: "wrk_c", name: "gamma" },
|
||||||
|
{ id: "wrk_d", name: "delta" },
|
||||||
|
{ id: "wrk_e", name: "epsilon" },
|
||||||
|
]
|
||||||
|
const status = {
|
||||||
|
wrk_a: "connected",
|
||||||
|
wrk_b: "disconnected",
|
||||||
|
wrk_c: "error",
|
||||||
|
wrk_d: "connected",
|
||||||
|
wrk_e: "connected",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const { recent } = recentConnectedWorkspaces({
|
||||||
|
sessions: [
|
||||||
|
{ time: { updated: 900 } },
|
||||||
|
{ workspaceID: "wrk_b", time: { updated: 800 } },
|
||||||
|
{ workspaceID: "wrk_a", time: { updated: 700 } },
|
||||||
|
{ workspaceID: "wrk_a", time: { updated: 600 } },
|
||||||
|
{ workspaceID: "wrk_missing", time: { updated: 500 } },
|
||||||
|
{ workspaceID: "wrk_c", time: { updated: 400 } },
|
||||||
|
{ workspaceID: "wrk_d", time: { updated: 300 } },
|
||||||
|
{ workspaceID: "wrk_e", time: { updated: 200 } },
|
||||||
|
],
|
||||||
|
get: (workspaceID) => workspaces.find((workspace) => workspace.id === workspaceID),
|
||||||
|
status: (workspaceID) => status[workspaceID as keyof typeof status],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(recent.map((workspace) => workspace.id)).toEqual(["wrk_a", "wrk_d", "wrk_e"])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user