mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
Limit slash command help fix to desktop
The /goal command already exposes description and argument hints in the CLI. Revert the broader SDK, system/init, and server metadata changes so the remaining behavior change is only the desktop fallback and rendering path. Constraint: Desktop help should not require changing core CLI or SDK wire contracts. Rejected: Add slash_commands_metadata to system/init | unnecessary for the reported desktop-only display issue. Confidence: high Scope-risk: narrow Directive: Fix missing desktop slash command descriptions in desktop fallback/rendering unless the CLI wire contract is explicitly being redesigned. Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx src/components/chat/ChatInput.test.tsx Tested: bun test src/commands/headless.test.ts src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts
This commit is contained in:
parent
49b7198da0
commit
30d9cc8254
@ -4,9 +4,8 @@ const goal = {
|
||||
type: 'local-jsx',
|
||||
supportsNonInteractive: true,
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear|complete',
|
||||
whenToUse: 'Use when you want the session to keep iterating on a concrete objective until it is complete.',
|
||||
description: 'Set or manage a completion goal that keeps the session working until it is met',
|
||||
argumentHint: '[status|clear|pause|resume|--tokens <budget> <objective>|<objective>]',
|
||||
load: () => import('./goal.js'),
|
||||
} satisfies Command
|
||||
|
||||
|
||||
@ -1473,16 +1473,6 @@ export const SDKSystemMessageSchema = lazySchema(() =>
|
||||
model: z.string(),
|
||||
permissionMode: PermissionModeSchema(),
|
||||
slash_commands: z.array(z.string()),
|
||||
slash_commands_metadata: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
argumentHint: z.string().optional(),
|
||||
whenToUse: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
output_style: z.string(),
|
||||
skills: z.array(z.string()),
|
||||
plugins: z.array(
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
closeSessionConnection,
|
||||
getActiveSessionIds,
|
||||
handleWebSocket,
|
||||
normalizeSlashCommandMetadata,
|
||||
type WebSocketData,
|
||||
} from '../ws/handler.js'
|
||||
import { conversationService } from '../services/conversationService.js'
|
||||
@ -70,27 +69,4 @@ describe('WebSocket handler session isolation', () => {
|
||||
expect(clearCallbacks).toHaveBeenCalledWith(sessionId)
|
||||
expect(cancelComputerUse).toHaveBeenCalledWith(sessionId)
|
||||
})
|
||||
|
||||
it('normalizes slash command metadata while preserving legacy string payloads', () => {
|
||||
expect(normalizeSlashCommandMetadata([
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear|complete',
|
||||
whenToUse: 'Use when the session should keep iterating until done.',
|
||||
},
|
||||
'clear',
|
||||
{ command: 'compact', description: 'Compact context' },
|
||||
{ name: ' ' },
|
||||
])).toEqual([
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear|complete',
|
||||
whenToUse: 'Use when the session should keep iterating until done.',
|
||||
},
|
||||
{ name: 'clear', description: '' },
|
||||
{ name: 'compact', description: 'Compact context' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -32,14 +32,7 @@ const providerService = new ProviderService()
|
||||
/**
|
||||
* Cache slash commands from CLI init messages, keyed by sessionId.
|
||||
*/
|
||||
type SlashCommandMetadata = {
|
||||
name: string
|
||||
description: string
|
||||
argumentHint?: string
|
||||
whenToUse?: string
|
||||
}
|
||||
|
||||
const sessionSlashCommands = new Map<string, SlashCommandMetadata[]>()
|
||||
const sessionSlashCommands = new Map<string, Array<{ name: string; description: string }>>()
|
||||
|
||||
/**
|
||||
* Timers for delayed session cleanup after client disconnect.
|
||||
@ -93,7 +86,7 @@ async function sendRepositoryStartupStatus(
|
||||
}
|
||||
}
|
||||
|
||||
export function getSlashCommands(sessionId: string): SlashCommandMetadata[] {
|
||||
export function getSlashCommands(sessionId: string): Array<{ name: string; description: string }> {
|
||||
return sessionSlashCommands.get(sessionId) || []
|
||||
}
|
||||
|
||||
@ -791,41 +784,14 @@ function cacheSessionInitMetadata(sessionId: string, cliMsg: any) {
|
||||
await sessionService.deletePlaceholderSessionFiles(sessionId, cliMsg.cwd)
|
||||
})()
|
||||
}
|
||||
const slashCommandMetadata = normalizeSlashCommandMetadata(cliMsg.slash_commands_metadata)
|
||||
const legacySlashCommands = normalizeSlashCommandMetadata(cliMsg.slash_commands)
|
||||
const commands = slashCommandMetadata.length > 0 ? slashCommandMetadata : legacySlashCommands
|
||||
if (commands.length > 0) {
|
||||
sessionSlashCommands.set(sessionId, commands)
|
||||
if (cliMsg.slash_commands && Array.isArray(cliMsg.slash_commands)) {
|
||||
sessionSlashCommands.set(sessionId, cliMsg.slash_commands.map((cmd: any) => ({
|
||||
name: typeof cmd === 'string' ? cmd : (cmd.name || cmd.command || ''),
|
||||
description: typeof cmd === 'string' ? '' : (cmd.description || ''),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeSlashCommandMetadata(value: unknown): SlashCommandMetadata[] {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.flatMap((cmd): SlashCommandMetadata[] => {
|
||||
if (typeof cmd === 'string') {
|
||||
return cmd.trim() ? [{ name: cmd, description: '' }] : []
|
||||
}
|
||||
if (!cmd || typeof cmd !== 'object') return []
|
||||
const record = cmd as Record<string, unknown>
|
||||
const rawName = typeof record.name === 'string'
|
||||
? record.name
|
||||
: typeof record.command === 'string'
|
||||
? record.command
|
||||
: ''
|
||||
const name = rawName.trim()
|
||||
if (!name) return []
|
||||
const description = typeof record.description === 'string' ? record.description.trim() : ''
|
||||
const argumentHint = typeof record.argumentHint === 'string' ? record.argumentHint.trim() : ''
|
||||
const whenToUse = typeof record.whenToUse === 'string' ? record.whenToUse.trim() : ''
|
||||
return [{
|
||||
name,
|
||||
description,
|
||||
...(argumentHint && { argumentHint }),
|
||||
...(whenToUse && { whenToUse }),
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
function extractAssistantText(cliMsg: any): string {
|
||||
const content = cliMsg?.message?.content
|
||||
if (!Array.isArray(content)) return ''
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { buildSystemInitMessage } from './systemInit'
|
||||
|
||||
describe('buildSystemInitMessage', () => {
|
||||
const previousAnthropicApiKey = process.env.ANTHROPIC_API_KEY
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-key'
|
||||
;(globalThis as typeof globalThis & { MACRO?: { VERSION: string } }).MACRO = { VERSION: 'test-version' }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (previousAnthropicApiKey === undefined) {
|
||||
delete process.env.ANTHROPIC_API_KEY
|
||||
} else {
|
||||
process.env.ANTHROPIC_API_KEY = previousAnthropicApiKey
|
||||
}
|
||||
})
|
||||
|
||||
const baseInputs = {
|
||||
tools: [],
|
||||
mcpClients: [],
|
||||
model: 'test-model',
|
||||
permissionMode: 'default' as const,
|
||||
agents: [],
|
||||
skills: [],
|
||||
plugins: [],
|
||||
fastMode: false,
|
||||
}
|
||||
|
||||
it('keeps slash command names compatible while exposing command descriptions and usage hints', () => {
|
||||
const message = buildSystemInitMessage({
|
||||
...baseInputs,
|
||||
commands: [
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear',
|
||||
whenToUse: 'Use when the session should keep iterating until a concrete outcome is complete.',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(message.slash_commands).toEqual(['goal'])
|
||||
expect(message.slash_commands_metadata).toEqual([
|
||||
{
|
||||
name: 'goal',
|
||||
description: 'Create or manage an autonomous completion goal',
|
||||
argumentHint: '<objective>|status|pause|resume|clear',
|
||||
whenToUse: 'Use when the session should keep iterating until a concrete outcome is complete.',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@ -24,20 +24,7 @@ export function sdkCompatToolName(name: string): string {
|
||||
return name === AGENT_TOOL_NAME ? LEGACY_AGENT_TOOL_NAME : name
|
||||
}
|
||||
|
||||
type CommandLike = {
|
||||
name: string
|
||||
description?: string
|
||||
argumentHint?: string
|
||||
whenToUse?: string
|
||||
userInvocable?: boolean
|
||||
}
|
||||
|
||||
type SlashCommandMetadata = {
|
||||
name: string
|
||||
description: string
|
||||
argumentHint?: string
|
||||
whenToUse?: string
|
||||
}
|
||||
type CommandLike = { name: string; userInvocable?: boolean }
|
||||
|
||||
export type SystemInitInputs = {
|
||||
tools: ReadonlyArray<{ name: string }>
|
||||
@ -66,7 +53,6 @@ export type SystemInitInputs = {
|
||||
export function buildSystemInitMessage(inputs: SystemInitInputs): SDKMessage {
|
||||
const settings = getSettings_DEPRECATED()
|
||||
const outputStyle = settings?.outputStyle ?? DEFAULT_OUTPUT_STYLE_NAME
|
||||
const userInvocableCommands = inputs.commands.filter(c => c.userInvocable !== false)
|
||||
|
||||
const initMessage: SDKMessage = {
|
||||
type: 'system',
|
||||
@ -80,17 +66,9 @@ export function buildSystemInitMessage(inputs: SystemInitInputs): SDKMessage {
|
||||
})),
|
||||
model: inputs.model,
|
||||
permissionMode: inputs.permissionMode,
|
||||
slash_commands: userInvocableCommands.map(c => c.name),
|
||||
slash_commands_metadata: userInvocableCommands.map(command => ({
|
||||
name: command.name,
|
||||
description: command.description?.trim() ?? '',
|
||||
...(command.argumentHint?.trim() && {
|
||||
argumentHint: command.argumentHint.trim(),
|
||||
}),
|
||||
...(command.whenToUse?.trim() && {
|
||||
whenToUse: command.whenToUse.trim(),
|
||||
}),
|
||||
} satisfies SlashCommandMetadata)),
|
||||
slash_commands: inputs.commands
|
||||
.filter(c => c.userInvocable !== false)
|
||||
.map(c => c.name),
|
||||
apiKeySource: getAnthropicApiKeyWithSource().source as ApiKeySource,
|
||||
betas: getSdkBetas(),
|
||||
claude_code_version: MACRO.VERSION,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user