fix(tui): keep compact slash editable

This commit is contained in:
Shoubhit Dash 2026-05-21 15:45:20 +05:30
parent fbce48f597
commit 25f92bba8d
3 changed files with 21 additions and 90 deletions

View file

@ -18,7 +18,6 @@ const optimistic: Array<{
const optimisticSeeded: boolean[] = []
const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
const promoted: Array<{ directory: string; sessionID: string }> = []
const sentSummaries: Array<{ sessionID: string; instructions?: string }> = []
const sentShell: string[] = []
const syncedDirectories: string[] = []
@ -26,7 +25,7 @@ let params: { id?: string } = {}
let selected = "/repo/worktree-a"
let variant: string | undefined
let promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
const promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
const clientFor = (directory: string) => {
createdClients.push(directory)
@ -48,10 +47,6 @@ const clientFor = (directory: string) => {
prompt: async () => ({ data: undefined }),
promptAsync: async () => ({ data: undefined }),
command: async () => ({ data: undefined }),
summarize: async (input: { sessionID: string; instructions?: string }) => {
sentSummaries.push({ sessionID: input.sessionID, instructions: input.instructions })
return { data: undefined }
},
abort: async () => ({ data: undefined }),
},
worktree: {
@ -213,8 +208,6 @@ beforeEach(() => {
optimistic.length = 0
optimisticSeeded.length = 0
promoted.length = 0
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
sentSummaries.length = 0
params = {}
sentShell.length = 0
syncedDirectories.length = 0
@ -350,30 +343,4 @@ describe("prompt submit worktree selection", () => {
expect(optimisticSeeded).toEqual([true])
})
test("submits typed compact slash arguments as summarize instructions", async () => {
params = { id: "session-1" }
promptValue = [{ type: "text", content: "/compact keep unresolved TODOs", start: 0, end: 30 }]
const submit = createPromptSubmit({
info: () => ({ id: "session-1" }),
imageAttachments: () => [],
commentCount: () => 0,
autoAccept: () => false,
mode: () => "normal",
working: () => false,
editor: () => undefined,
queueScroll: () => undefined,
promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
addToHistory: () => undefined,
resetHistoryNavigation: () => undefined,
setMode: () => undefined,
setPopover: () => undefined,
onSubmit: () => undefined,
})
await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event)
expect(sentSummaries).toEqual([{ sessionID: "session-1", instructions: "keep unresolved TODOs" }])
expect(optimistic).toHaveLength(0)
})
})

View file

@ -50,21 +50,6 @@ const draftText = (prompt: Prompt) => prompt.map((part) => ("content" in part ?
const draftImages = (prompt: Prompt) => prompt.filter((part): part is ImageAttachmentPart => part.type === "image")
const slashCommand = (text: string) => {
if (!text.startsWith("/")) return undefined
return text.split("\n")[0].split(" ")[0].slice(1)
}
const slashArguments = (text: string) => {
const firstLineEnd = text.indexOf("\n")
const firstLine = firstLineEnd === -1 ? text : text.slice(0, firstLineEnd)
const [, ...firstLineArgs] = firstLine.split(" ")
const restOfInput = firstLineEnd === -1 ? "" : text.slice(firstLineEnd + 1)
return firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
}
const isCompactSlash = (command: string | undefined) => command === "compact" || command === "summarize"
export async function sendFollowupDraft(input: FollowupSendInput) {
const text = draftText(input.draft.prompt)
const images = draftImages(input.draft.prompt)
@ -86,27 +71,8 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
return true
}
const cmd = slashCommand(text)
if (isCompactSlash(cmd)) {
setBusy()
try {
if (!(await wait())) {
setIdle()
return false
}
const instructions = slashArguments(text).trim()
const payload = instructions
? { sessionID: input.draft.sessionID, ...input.draft.model, instructions }
: { sessionID: input.draft.sessionID, ...input.draft.model }
await input.client.session.summarize(payload)
return true
} catch (err) {
setIdle()
throw err
}
}
const [head, ...tail] = text.split(" ")
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
if (cmd && input.sync.data.command.find((item) => item.name === cmd)) {
setBusy()
try {
@ -118,7 +84,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
await input.client.session.command({
sessionID: input.draft.sessionID,
command: cmd,
arguments: slashArguments(text),
arguments: tail.join(" "),
agent: input.draft.agent,
model: `${input.draft.model.providerID}/${input.draft.model.modelID}`,
variant: input.draft.variant,
@ -487,7 +453,8 @@ export function createPromptSubmit(input: PromptSubmitInput) {
}
if (text.startsWith("/")) {
const commandName = slashCommand(text) ?? ""
const [cmdName, ...args] = text.split(" ")
const commandName = cmdName.slice(1)
const customCommand = sync.data.command.find((c) => c.name === commandName)
if (customCommand) {
clearInput()
@ -495,7 +462,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
.command({
sessionID: session.id,
command: commandName,
arguments: slashArguments(text),
arguments: args.join(" "),
agent,
model: `${model.providerID}/${model.modelID}`,
variant,
@ -513,22 +480,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
description: formatServerError(err, language.t, language.t("common.requestFailed")),
})
restoreInput()
})
return
}
if (isCompactSlash(commandName)) {
clearInput()
const instructions = slashArguments(text).trim()
const payload = instructions
? { sessionID: session.id, ...model, instructions }
: { sessionID: session.id, ...model }
client.session.summarize(payload).catch((err) => {
showToast({
title: language.t("prompt.toast.promptSendFailed.title"),
description: errorMessage(err),
})
restoreInput()
})
return
}

View file

@ -544,7 +544,20 @@ export function Autocomplete(props: {
)
const commands = createMemo((): AutocompleteOption[] => {
const results: AutocompleteOption[] = [...slashes()]
const results: AutocompleteOption[] = slashes().map((command) => {
const name = command.display.trim().slice(1)
if (name !== "compact") return command
return {
...command,
onSelect: () => {
const newText = `/${name} `
const cursor = props.input().logicalCursor
props.input().deleteRange(0, 0, cursor.row, cursor.col)
props.input().insertText(newText)
props.input().cursorOffset = Bun.stringWidth(newText)
},
}
})
for (const serverCommand of sync.data.command) {
if (serverCommand.source === "skill") continue