Skip to content

Commit 1179aaa

Browse files
allquixoticclaude
andcommitted
feat(chat): add /compact and /compact-and slash commands (3.53.6)
Adds two user-triggered slash commands to the webview chat input: - /compact — manually runs the same context-condensing flow used when auto-condense fires near the context window limit. - /compact-and <message> — condenses first, then dispatches <message> as the next user turn once the condense response is received. Both commands are registered as built-in slash commands so they appear in autocomplete with descriptions (compact-and shows an argument hint). The webview intercepts submission in handleSendMessage before posting to the backend, so the empty content field of these built-ins is never expanded server-side. Implementation details: - src/services/command/built-in-commands.ts: register compact and compact-and entries. - webview-ui/src/components/chat/ChatView.tsx: - Hoist handleCondenseContext into a useCallback above handleSendMessage so the interception can live at the top of the send path. - Add pendingPostCompactRef to stash the follow-up message for /compact-and. - Regex-match /compact and /compact-and at the top of handleSendMessage, fire the existing condenseTaskContextRequest message, and clear the input. - useEffect watches isCondensing and sendingDisabled; once both fall back to false, any stashed follow-up is dispatched through handleSendMessage. - src/services/command/__tests__/built-in-commands.spec.ts: bump the expected built-in command count from 1 to 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a804eb commit 1179aaa

4 files changed

Lines changed: 69 additions & 14 deletions

File tree

src/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "%extension.displayName%",
44
"description": "%extension.description%",
55
"publisher": "allquixotic",
6-
"version": "3.53.5",
6+
"version": "3.53.6",
77
"icon": "assets/icons/icon.png",
88
"galleryBanner": {
99
"color": "#617A91",

src/services/command/__tests__/built-in-commands.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ describe("Built-in Commands", () => {
55
it("should return all built-in commands", async () => {
66
const commands = await getBuiltInCommands()
77

8-
expect(commands).toHaveLength(1)
9-
expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init"]))
8+
expect(commands).toHaveLength(3)
9+
expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init", "compact", "compact-and"]))
1010

1111
// Verify all commands have required properties
1212
commands.forEach((command) => {
@@ -63,10 +63,10 @@ describe("Built-in Commands", () => {
6363
it("should return all built-in command names", async () => {
6464
const names = await getBuiltInCommandNames()
6565

66-
expect(names).toHaveLength(1)
67-
expect(names).toEqual(expect.arrayContaining(["init"]))
66+
expect(names).toHaveLength(3)
67+
expect(names).toEqual(expect.arrayContaining(["init", "compact", "compact-and"]))
6868
// Order doesn't matter since it's based on filesystem order
69-
expect(names.sort()).toEqual(["init"])
69+
expect(names.sort()).toEqual(["compact", "compact-and", "init"])
7070
})
7171

7272
it("should return array of strings", async () => {

src/services/command/built-in-commands.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ interface BuiltInCommandDefinition {
88
}
99

1010
const BUILT_IN_COMMANDS: Record<string, BuiltInCommandDefinition> = {
11+
compact: {
12+
name: "compact",
13+
description: "Manually condense the current task context to free up tokens",
14+
content: "",
15+
},
16+
"compact-and": {
17+
name: "compact-and",
18+
description: "Condense context, then send the following message",
19+
argumentHint: "<message to send after condensing>",
20+
content: "",
21+
},
1122
init: {
1223
name: "init",
1324
description: "Analyze codebase and create concise AGENTS.md files for AI assistants",

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
228228
)
229229
const autoApproveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
230230
const userRespondedRef = useRef<boolean>(false)
231+
const pendingPostCompactRef = useRef<{ text: string; images: string[] } | null>(null)
231232
const [currentFollowUpTs, setCurrentFollowUpTs] = useState<number | null>(null)
232233
const [aggregatedCostsMap, setAggregatedCostsMap] = useState<
233234
Map<
@@ -667,6 +668,18 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
667668
// setSecondaryButtonText(undefined)
668669
}, [])
669670

671+
const handleCondenseContext = useCallback(
672+
(taskId: string) => {
673+
if (isCondensing || sendingDisabled) {
674+
return
675+
}
676+
setIsCondensing(true)
677+
setSendingDisabled(true)
678+
vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId })
679+
},
680+
[isCondensing, sendingDisabled],
681+
)
682+
670683
/**
671684
* Handles sending messages to the extension
672685
* @param text - The message text to send
@@ -680,6 +693,30 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
680693
if (submissionDisabled) {
681694
return
682695
}
696+
697+
// Intercept /compact and /compact-and before any other processing.
698+
// /compact triggers the same context-condensing used by auto-condense.
699+
// /compact-and <msg> condenses, then sends <msg> once condensing completes.
700+
const compactMatch = /^\/compact(-and)?(?:\s+([\s\S]+))?$/.exec(text)
701+
if (compactMatch) {
702+
const taskId = currentTaskItem?.id
703+
if (!taskId || messagesRef.current.length === 0 || isCondensing || sendingDisabled) {
704+
// Nothing to condense, or a condense is already in flight.
705+
setInputValue("")
706+
setSelectedImages([])
707+
return
708+
}
709+
const followUp = (compactMatch[2] ?? "").trim()
710+
if (compactMatch[1] && followUp) {
711+
pendingPostCompactRef.current = { text: followUp, images }
712+
}
713+
setIsCondensing(true)
714+
setSendingDisabled(true)
715+
vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId })
716+
setInputValue("")
717+
setSelectedImages([])
718+
return
719+
}
683720
// Intercept when the active provider is retired — show a
684721
// WarningRow instead of sending anything to the backend.
685722
if (apiConfiguration?.apiProvider && isRetiredProvider(apiConfiguration.apiProvider)) {
@@ -757,9 +794,24 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
757794
apiConfiguration?.apiProvider,
758795
submissionDisabled,
759796
selectedDraftId,
797+
currentTaskItem?.id,
760798
], // messagesRef and clineAskRef are stable
761799
)
762800

801+
// After /compact-and completes, dispatch the follow-up message stashed
802+
// when the user submitted the command.
803+
useEffect(() => {
804+
if (isCondensing || sendingDisabled) {
805+
return
806+
}
807+
const pending = pendingPostCompactRef.current
808+
if (!pending) {
809+
return
810+
}
811+
pendingPostCompactRef.current = null
812+
handleSendMessage(pending.text, pending.images)
813+
}, [isCondensing, sendingDisabled, handleSendMessage])
814+
763815
const handleSetChatBoxMessage = useCallback(
764816
(text: string, images: string[]) => {
765817
// Avoid nested template literals by breaking down the logic
@@ -1628,14 +1680,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16281680
},
16291681
}))
16301682

1631-
const handleCondenseContext = (taskId: string) => {
1632-
if (isCondensing || sendingDisabled) {
1633-
return
1634-
}
1635-
setIsCondensing(true)
1636-
setSendingDisabled(true)
1637-
vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId })
1638-
}
16391683
const areButtonsVisible = showScrollToBottom || displayedPrimaryButtonText || displayedSecondaryButtonText
16401684

16411685
return (

0 commit comments

Comments
 (0)