Skip to content

Commit ea1c60a

Browse files
committed
fix(cli): address #10383 R2 Critical findings in slash-dispatch
- abort race: already-aborted signal now skips command.action entirely (result = undefined) instead of eagerly evaluating it as a Promise.race argument — the action's side effects (clear, persist, addItem) must not run on a cancelled submission - parent command telemetry: logEvent (slash_command SUCCESS) is now called before the early return for parent commands with subCommands (help listing) and bare handled — matching ink's finally-block logging
1 parent b46565c commit ea1c60a

1 file changed

Lines changed: 31 additions & 23 deletions

File tree

packages/cli/src/ui/opentui/slash-dispatch.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,10 @@ export async function executeSlashCommand(
432432
}
433433

434434
const { command, args } = resolution;
435-
if (!command.action) {
436-
if (command.subCommands && command.subCommands.length > 0) {
437-
const helpText = `Command '/${command.name}' requires a subcommand. Available:\n${command.subCommands
438-
.map((sc) => ` - ${sc.name}: ${sc.description || ''}`)
439-
.join('\n')}`;
440-
return { kind: 'message', messageType: 'info', content: helpText };
441-
}
442-
return { kind: 'handled' };
443-
}
444435

445-
// Telemetry parity (ink slashCommandProcessor): skill invocations feed
446-
// /stats skills via recordSkillInvocation + recordAutoSkillCommandUsage,
447-
// and every executed command logs a SUCCESS/ERROR slash-command event.
448-
const isSkillCommand = command.kind === CommandKind.SKILL;
449-
const skillName = command.skillDetail?.name ?? command.name;
436+
// Telemetry parity (ink slashCommandProcessor): every executed command
437+
// logs a SUCCESS/ERROR slash-command event, including parent commands
438+
// that return early (help listing / bare handled).
450439
const subcommand =
451440
resolution.canonicalPath.length > 1
452441
? resolution.canonicalPath.slice(1).join(' ')
@@ -462,6 +451,23 @@ export async function executeSlashCommand(
462451
}),
463452
);
464453
};
454+
455+
if (!command.action) {
456+
if (command.subCommands && command.subCommands.length > 0) {
457+
const helpText = `Command '/${command.name}' requires a subcommand. Available:\n${command.subCommands
458+
.map((sc) => ` - ${sc.name}: ${sc.description || ''}`)
459+
.join('\n')}`;
460+
logEvent(SlashCommandStatus.SUCCESS);
461+
return { kind: 'message', messageType: 'info', content: helpText };
462+
}
463+
logEvent(SlashCommandStatus.SUCCESS);
464+
return { kind: 'handled' };
465+
}
466+
467+
// Skill-specific telemetry: skill invocations feed /stats skills via
468+
// recordSkillInvocation + recordAutoSkillCommandUsage.
469+
const isSkillCommand = command.kind === CommandKind.SKILL;
470+
const skillName = command.skillDetail?.name ?? command.name;
465471
const recordSkill = (success: boolean) => {
466472
if (env.config && isSkillCommand) {
467473
recordSkillInvocation(env.config, { skillName, success });
@@ -542,16 +548,18 @@ export async function executeSlashCommand(
542548
let result: SlashCommandActionReturn | void;
543549
if (env.abortSignal) {
544550
const signal = env.abortSignal;
545-
// An already-aborted signal never fires addEventListener('abort'),
546-
// so resolve immediately in that case instead of hanging.
547-
const aborted = signal.aborted
548-
? Promise.resolve(undefined)
549-
: new Promise<undefined>((resolve) => {
550-
signal.addEventListener('abort', () => resolve(undefined), {
551-
once: true,
552-
});
551+
if (signal.aborted) {
552+
// Already aborted: skip the action entirely — its side effects
553+
// (clear, persist, addItem) must not run on a cancelled submission.
554+
result = undefined;
555+
} else {
556+
const aborted = new Promise<undefined>((resolve) => {
557+
signal.addEventListener('abort', () => resolve(undefined), {
558+
once: true,
553559
});
554-
result = await Promise.race([command.action(context, args), aborted]);
560+
});
561+
result = await Promise.race([command.action(context, args), aborted]);
562+
}
555563
} else {
556564
result = await command.action(context, args);
557565
}

0 commit comments

Comments
 (0)