diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index d99516e591..50168562f0 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -18,7 +18,6 @@ const optimistic: Array<{ const optimisticSeeded: boolean[] = [] const storedSessions: Record> = {} 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) - }) }) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index d127fccbe0..f870117c87 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -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 } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 2bda73cff7..88d3e58938 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -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