mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Expose goal subcommands in desktop composer
The CLI supports /goal status, pause, resume, complete, clear, and --tokens as arguments, but the desktop composer only showed the top-level /goal entry and closed completion after the space. Users could run the commands if they knew them, yet the UI made them look unavailable. Constraint: Keep CLI semantics unchanged; this is a desktop discovery and completion fix only Rejected: Add separate CLI slash commands for every goal subcommand | would diverge from the actual command surface Confidence: high Scope-risk: narrow Directive: Keep /goal subcommands as argument completions unless the CLI command model gains first-class nested slash commands Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/components/chat/ChatInput.test.tsx Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx Tested: cd desktop && bun run lint Not-tested: live desktop manual interaction after restarting the app
This commit is contained in:
parent
5ed1081252
commit
e77dd6d3cb
@ -18,6 +18,12 @@ describe('composerUtils', () => {
|
||||
expect(findSlashToken('/ review', 8)).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps /goal argument completion active for one subcommand token', () => {
|
||||
expect(findSlashToken('/goal ', 6)).toEqual({ start: 0, filter: 'goal ' })
|
||||
expect(findSlashToken('/goal sta', 9)).toEqual({ start: 0, filter: 'goal sta' })
|
||||
expect(findSlashToken('/goal build app', 15)).toBeNull()
|
||||
})
|
||||
|
||||
it('inserts a slash trigger without appending a trailing space', () => {
|
||||
expect(insertSlashTrigger('', 0)).toEqual({ value: '/', cursorPos: 1 })
|
||||
expect(insertSlashTrigger('hello', 5)).toEqual({ value: 'hello /', cursorPos: 7 })
|
||||
@ -70,12 +76,36 @@ describe('composerUtils', () => {
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear|complete',
|
||||
argumentHint: '[status|pause|resume|complete|clear|--tokens <budget>|<objective>]',
|
||||
},
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('suggests /goal subcommands after the /goal argument separator', () => {
|
||||
expect(
|
||||
filterSlashCommands(mergeSlashCommands([]), 'goal ').map((command) => command.name),
|
||||
).toEqual([
|
||||
'goal status',
|
||||
'goal pause',
|
||||
'goal resume',
|
||||
'goal complete',
|
||||
'goal clear',
|
||||
'goal --tokens',
|
||||
])
|
||||
|
||||
expect(
|
||||
filterSlashCommands(mergeSlashCommands([]), 'goal s').map((command) => command.name),
|
||||
).toEqual(['goal status'])
|
||||
})
|
||||
|
||||
it('replaces a /goal subcommand fragment with the selected subcommand', () => {
|
||||
expect(replaceSlashCommand('/goal sta', 9, 'goal status')).toEqual({
|
||||
value: '/goal status ',
|
||||
cursorPos: 13,
|
||||
})
|
||||
})
|
||||
|
||||
it('ranks slash command name matches before broad description matches', () => {
|
||||
expect(
|
||||
filterSlashCommands([
|
||||
|
||||
@ -19,6 +19,19 @@ export const SLASH_COMMAND_ALIASES = [
|
||||
{ name: 'plugins', target: 'plugin' },
|
||||
] as const
|
||||
|
||||
export const GOAL_SLASH_SUBCOMMANDS = [
|
||||
{ name: 'goal status', description: 'Show the current goal status' },
|
||||
{ name: 'goal pause', description: 'Pause the active goal loop' },
|
||||
{ name: 'goal resume', description: 'Resume the active goal loop' },
|
||||
{ name: 'goal complete', description: 'Mark the active goal complete' },
|
||||
{ name: 'goal clear', description: 'Clear the active goal' },
|
||||
{
|
||||
name: 'goal --tokens',
|
||||
description: 'Create a goal with a token budget',
|
||||
argumentHint: '<budget> <objective>',
|
||||
},
|
||||
] as const
|
||||
|
||||
export const FALLBACK_SLASH_COMMANDS = [
|
||||
...PANEL_SLASH_COMMANDS,
|
||||
...SETTINGS_SLASH_COMMANDS.map(({ name, description }) => ({ name, description })),
|
||||
@ -27,8 +40,9 @@ export const FALLBACK_SLASH_COMMANDS = [
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear|complete',
|
||||
argumentHint: '[status|pause|resume|complete|clear|--tokens <budget>|<objective>]',
|
||||
},
|
||||
...GOAL_SLASH_SUBCOMMANDS,
|
||||
{ name: 'review', description: 'Review code changes' },
|
||||
{ name: 'commit', description: 'Create a git commit' },
|
||||
{ name: 'pr', description: 'Create a pull request' },
|
||||
@ -127,8 +141,8 @@ export function filterSlashCommands(
|
||||
commands: ReadonlyArray<SlashCommandOption>,
|
||||
filter: string,
|
||||
): SlashCommandOption[] {
|
||||
const normalized = filter.trim().toLowerCase()
|
||||
if (!normalized) return [...commands]
|
||||
const normalized = filter.toLowerCase()
|
||||
if (!normalized.trim()) return [...commands]
|
||||
|
||||
return commands
|
||||
.map((command, index) => ({
|
||||
@ -148,26 +162,13 @@ export type SlashTrigger = {
|
||||
|
||||
export function findSlashTrigger(value: string, cursorPos: number): SlashTrigger | null {
|
||||
const textBeforeCursor = value.slice(0, cursorPos)
|
||||
let slashPos = -1
|
||||
|
||||
for (let i = textBeforeCursor.length - 1; i >= 0; i--) {
|
||||
const ch = textBeforeCursor[i]!
|
||||
if (ch === '/') {
|
||||
if (i === 0 || /\s/.test(textBeforeCursor[i - 1]!)) {
|
||||
slashPos = i
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
if (/\s/.test(ch)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const slashPos = textBeforeCursor.lastIndexOf('/')
|
||||
if (slashPos < 0) return null
|
||||
if (slashPos > 0 && !/\s/.test(textBeforeCursor[slashPos - 1]!)) return null
|
||||
|
||||
const filter = textBeforeCursor.slice(slashPos + 1)
|
||||
if (/\s/.test(filter)) return null
|
||||
if (filter.includes('\n')) return null
|
||||
if (/\s/.test(filter) && !/^goal\s+[^\s]*$/i.test(filter)) return null
|
||||
|
||||
return { slashPos, filter }
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user