mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Prevent removed goal subcommands from replacing goals
The simplified /goal surface only supports setting a condition and clearing it. Removed subcommand names such as status were still valid free-form objectives, so a user trying the old query flow could overwrite the real goal with a goal named status and make the desktop state look stuck in progress. Constraint: /goal should stay as /goal <condition> and /goal clear for the prelaunch simplified UX Rejected: Reintroduce /goal status | it expands the command surface the product direction intentionally removed Confidence: high Scope-risk: narrow Directive: Do not add pseudo subcommands back to the desktop picker unless the CLI command surface is deliberately expanded again Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts src/server/__tests__/ws-memory-events.test.ts Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
This commit is contained in:
parent
5c5c5933f0
commit
7f74f1bde7
@ -169,8 +169,8 @@ describe('Content-only pages render without errors', () => {
|
||||
})
|
||||
|
||||
expect(await screen.findAllByText('/goal')).toHaveLength(2)
|
||||
expect(screen.getByText('[status|pause|resume|complete|clear|--tokens <budget>|<objective>]')).toBeInTheDocument()
|
||||
expect(screen.getByText('Create or manage an autonomous completion goal')).toBeInTheDocument()
|
||||
expect(screen.getByText('[<condition> | clear]')).toBeInTheDocument()
|
||||
expect(screen.getByText('Set a completion goal')).toBeInTheDocument()
|
||||
expect(screen.queryByText('/goal status')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('/goal --tokens')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -75,8 +75,8 @@ describe('composerUtils', () => {
|
||||
expect.arrayContaining([
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '[status|pause|resume|complete|clear|--tokens <budget>|<objective>]',
|
||||
description: 'Set a completion goal',
|
||||
argumentHint: '[<condition> | clear]',
|
||||
},
|
||||
]),
|
||||
)
|
||||
@ -89,8 +89,8 @@ describe('composerUtils', () => {
|
||||
commands.map((command) => command.name),
|
||||
).toEqual(['goal'])
|
||||
expect(commands[0]).toMatchObject({
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '[status|pause|resume|complete|clear|--tokens <budget>|<objective>]',
|
||||
description: 'Set a completion goal',
|
||||
argumentHint: '[<condition> | clear]',
|
||||
})
|
||||
expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal status')
|
||||
expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal --tokens')
|
||||
|
||||
@ -26,8 +26,8 @@ export const FALLBACK_SLASH_COMMANDS = [
|
||||
{ name: 'clear', description: 'Clear conversation history' },
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '[status|pause|resume|complete|clear|--tokens <budget>|<objective>]',
|
||||
description: 'Set a completion goal',
|
||||
argumentHint: '[<condition> | clear]',
|
||||
},
|
||||
{ name: 'review', description: 'Review code changes' },
|
||||
{ name: 'commit', description: 'Create a git commit' },
|
||||
|
||||
@ -79,6 +79,20 @@ describe('/goal command', () => {
|
||||
expect(result.options?.shouldQuery).toBeUndefined()
|
||||
})
|
||||
|
||||
test('does not treat removed subcommands as replacement goals', async () => {
|
||||
switchSession(`goal-command-${crypto.randomUUID()}` as SessionId)
|
||||
|
||||
const created = await runGoal('ship the smoke test')
|
||||
expect(created.result).toBe('Goal set: ship the smoke test')
|
||||
|
||||
const status = await runGoal('status')
|
||||
expect(status.result).toBe('Usage: /goal <condition> | clear')
|
||||
expect(status.options?.shouldQuery).toBeUndefined()
|
||||
|
||||
const cleared = await runGoal('clear')
|
||||
expect(cleared.result).toBe('Goal cleared: ship the smoke test')
|
||||
})
|
||||
|
||||
test('hydrates completed goal state from persisted slash command history', async () => {
|
||||
switchSession(`goal-command-${crypto.randomUUID()}` as SessionId)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ const goal = {
|
||||
type: 'local-jsx',
|
||||
supportsNonInteractive: true,
|
||||
name: 'goal',
|
||||
description: 'Set a completion goal that keeps the session working until it is met',
|
||||
description: 'Set a completion goal',
|
||||
argumentHint: '[<condition> | clear]',
|
||||
load: () => import('./goal.js'),
|
||||
} satisfies Command
|
||||
|
||||
@ -23,6 +23,11 @@ describe('goalState', () => {
|
||||
})
|
||||
expect(parseGoalCommand('clear')).toEqual({ type: 'clear' })
|
||||
expect(() => parseGoalCommand('')).toThrow('Usage: /goal <condition> | clear')
|
||||
expect(() => parseGoalCommand('status')).toThrow('Usage: /goal <condition> | clear')
|
||||
expect(() => parseGoalCommand('pause')).toThrow('Usage: /goal <condition> | clear')
|
||||
expect(() => parseGoalCommand('resume')).toThrow('Usage: /goal <condition> | clear')
|
||||
expect(() => parseGoalCommand('complete')).toThrow('Usage: /goal <condition> | clear')
|
||||
expect(() => parseGoalCommand('--tokens 100 ship it')).toThrow('Usage: /goal <condition> | clear')
|
||||
})
|
||||
|
||||
test('stores and formats the current thread goal', () => {
|
||||
|
||||
@ -25,11 +25,15 @@ export type ParsedGoalCommand =
|
||||
| { type: 'set'; objective: string }
|
||||
|
||||
const goalsByThread = new Map<string, ThreadGoal>()
|
||||
const RESERVED_GOAL_ARGS = new Set(['status', 'pause', 'resume', 'complete'])
|
||||
|
||||
export function parseGoalCommand(args: string): ParsedGoalCommand {
|
||||
const trimmed = args.trim()
|
||||
if (!trimmed) throw new Error('Usage: /goal <condition> | clear')
|
||||
if (trimmed === 'clear') return { type: 'clear' }
|
||||
if (RESERVED_GOAL_ARGS.has(trimmed) || trimmed.startsWith('--tokens')) {
|
||||
throw new Error('Usage: /goal <condition> | clear')
|
||||
}
|
||||
return { type: 'set', objective: trimmed }
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user