Skip to content

Commit e39bc8e

Browse files
committed
fix(tui): preserve recovered skill request order
1 parent 5511498 commit e39bc8e

2 files changed

Lines changed: 53 additions & 5 deletions

File tree

‎apps/kimi-code/src/tui/kimi-tui.ts‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,12 +1522,14 @@ export class KimiTUI {
15221522
return;
15231523
}
15241524
}
1525+
const hadQueuedMessages = this.state.queuedMessages.length > 0;
15251526
if (
15261527
this.deferUserMessages ||
15271528
this.skillSteerBatchInFlight ||
15281529
this.state.appState.goal?.status === 'active' ||
15291530
this.state.appState.streamingPhase !== 'idle' ||
1530-
this.state.appState.isCompacting
1531+
this.state.appState.isCompacting ||
1532+
hadQueuedMessages
15311533
) {
15321534
this.enqueueMessage(
15331535
text,
@@ -1541,6 +1543,9 @@ export class KimiTUI {
15411543
}
15421544
: { inlineSkillActivations: activations },
15431545
);
1546+
if (hadQueuedMessages && this.state.appState.goal?.status !== 'active') {
1547+
this.drainQueueIfIdle();
1548+
}
15441549
this.updateQueueDisplay();
15451550
this.state.ui.requestRender();
15461551
return;
@@ -2092,16 +2097,21 @@ export class KimiTUI {
20922097
this.steerQueuedMessagesIntoRunningTurn();
20932098
return;
20942099
}
2100+
const hadQueuedMessages = this.state.queuedMessages.length > 0;
20952101
if (
20962102
this.deferUserMessages ||
20972103
this.skillSteerBatchInFlight ||
20982104
this.state.appState.streamingPhase !== 'idle' ||
2099-
this.state.appState.isCompacting
2105+
this.state.appState.isCompacting ||
2106+
hadQueuedMessages
21002107
) {
21012108
// A queued message re-leases its staged media at dequeue dispatch; the
21022109
// pre-dispatch lease defers to the queue item's raw ids.
21032110
this.staging.defer(options?.lease);
21042111
this.enqueueMessage(input, options);
2112+
if (hadQueuedMessages && this.state.appState.goal?.status !== 'active') {
2113+
this.drainQueueIfIdle();
2114+
}
21052115
return;
21062116
}
21072117
this.sendMessageInternal(session, input, options);

‎apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,9 +1052,11 @@ describe('KimiTUI message flow', () => {
10521052
it('keeps a rejected skill input when a newer draft already exists', async () => {
10531053
let rejectSubmission!: (error: Error) => void;
10541054
const session = makeSession({
1055-
promptWithSkills: vi.fn(() => new Promise<void>((_resolve, reject) => {
1056-
rejectSubmission = reject;
1057-
})),
1055+
promptWithSkills: vi.fn()
1056+
.mockImplementationOnce(() => new Promise<void>((_resolve, reject) => {
1057+
rejectSubmission = reject;
1058+
}))
1059+
.mockResolvedValue(undefined),
10581060
});
10591061
const { driver } = await makeDriver(session);
10601062
driver.state.appState.model = 'k2';
@@ -1072,6 +1074,42 @@ describe('KimiTUI message flow', () => {
10721074
}));
10731075
});
10741076
expect(driver.state.editor.getText()).toBe('new draft');
1077+
1078+
driver.handleUserInput('new draft');
1079+
1080+
await vi.waitFor(() => expect(session.promptWithSkills).toHaveBeenCalledTimes(2));
1081+
expect(session.prompt).not.toHaveBeenCalled();
1082+
expect(driver.state.queuedMessages.map((item) => item.text)).toEqual(['new draft']);
1083+
});
1084+
1085+
it('keeps a newer skill message behind a recovered skill request', async () => {
1086+
let rejectSubmission!: (error: Error) => void;
1087+
const session = makeSession({
1088+
promptWithSkills: vi.fn()
1089+
.mockImplementationOnce(() => new Promise<void>((_resolve, reject) => {
1090+
rejectSubmission = reject;
1091+
}))
1092+
.mockResolvedValue(undefined),
1093+
});
1094+
const { driver } = await makeDriver(session);
1095+
driver.state.appState.model = 'k2';
1096+
driver.skillCommandMap.set('skill:review', 'review');
1097+
1098+
driver.handleUserInput('check /skill:review');
1099+
await vi.waitFor(() => expect(session.promptWithSkills).toHaveBeenCalledOnce());
1100+
driver.state.editor.setText('follow up /skill:review');
1101+
rejectSubmission(new Error('Skill missing'));
1102+
await vi.waitFor(() => expect(driver.state.queuedMessages).toHaveLength(1));
1103+
1104+
driver.handleUserInput('follow up /skill:review');
1105+
1106+
await vi.waitFor(() => expect(session.promptWithSkills).toHaveBeenCalledTimes(2));
1107+
expect(session.promptWithSkills).toHaveBeenNthCalledWith(2, 'check /skill:review', [
1108+
expect.objectContaining({ name: 'review' }),
1109+
]);
1110+
expect(driver.state.queuedMessages.map((item) => item.text)).toEqual([
1111+
'follow up /skill:review',
1112+
]);
10751113
});
10761114

10771115
it('renders a bundled replay submission as a single turn', async () => {

0 commit comments

Comments
 (0)