(inputString: string, precedingInputBlocks: ContentBlockParam[], imageContentBlocks: ContentBlockParam[], attachmentMessages: AttachmentMessage[], context: ProcessUserInputContext, setToolJSX: SetToolJSXFn, uuid?: string, isAlreadyProcessing?: boolean, canUseTool?: CanUseToolFn)
| 307 | return !/[^a-zA-Z0-9:\-_]/.test(commandName); |
| 308 | } |
| 309 | export async function processSlashCommand(inputString: string, precedingInputBlocks: ContentBlockParam[], imageContentBlocks: ContentBlockParam[], attachmentMessages: AttachmentMessage[], context: ProcessUserInputContext, setToolJSX: SetToolJSXFn, uuid?: string, isAlreadyProcessing?: boolean, canUseTool?: CanUseToolFn): Promise<ProcessUserInputBaseResult> { |
| 310 | const parsed = parseSlashCommand(inputString); |
| 311 | if (!parsed) { |
| 312 | logEvent('tengu_input_slash_missing', {}); |
| 313 | const errorMessage = 'Commands are in the form `/command [args]`'; |
| 314 | return { |
| 315 | messages: [createSyntheticUserCaveatMessage(), ...attachmentMessages, createUserMessage({ |
| 316 | content: prepareUserContent({ |
| 317 | inputString: errorMessage, |
| 318 | precedingInputBlocks |
| 319 | }) |
| 320 | })], |
| 321 | shouldQuery: false, |
| 322 | resultText: errorMessage |
| 323 | }; |
| 324 | } |
| 325 | const { |
| 326 | commandName, |
| 327 | args: parsedArgs, |
| 328 | isMcp |
| 329 | } = parsed; |
| 330 | const sanitizedCommandName = isMcp ? 'mcp' : !builtInCommandNames().has(commandName) ? 'custom' : commandName; |
| 331 | |
| 332 | // Check if it's a real command before processing |
| 333 | if (!hasCommand(commandName, context.options.commands)) { |
| 334 | // Check if this looks like a command name vs a file path or other input |
| 335 | // Also check if it's an actual file path that exists |
| 336 | let isFilePath = false; |
| 337 | try { |
| 338 | await getFsImplementation().stat(`/${commandName}`); |
| 339 | isFilePath = true; |
| 340 | } catch { |
| 341 | // Not a file path — treat as command name |
| 342 | } |
| 343 | if (looksLikeCommand(commandName) && !isFilePath) { |
| 344 | logEvent('tengu_input_slash_invalid', { |
| 345 | input: commandName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS |
| 346 | }); |
| 347 | const unknownMessage = `Unknown skill: ${commandName}`; |
| 348 | return { |
| 349 | messages: [createSyntheticUserCaveatMessage(), ...attachmentMessages, createUserMessage({ |
| 350 | content: prepareUserContent({ |
| 351 | inputString: unknownMessage, |
| 352 | precedingInputBlocks |
| 353 | }) |
| 354 | }), |
| 355 | // gh-32591: preserve args so the user can copy/resubmit without |
| 356 | // retyping. System warning is UI-only (filtered before API). |
| 357 | ...(parsedArgs ? [createSystemMessage(`Args from unknown skill: ${parsedArgs}`, 'warning')] : [])], |
| 358 | shouldQuery: false, |
| 359 | resultText: unknownMessage |
| 360 | }; |
| 361 | } |
| 362 | const promptId = randomUUID(); |
| 363 | setPromptId(promptId); |
| 364 | logEvent('tengu_input_prompt', {}); |
| 365 | // Log user prompt event for OTLP |
| 366 | void logOTelEvent('user_prompt', { |
no test coverage detected