Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions apps/desktop/e2e/slash-command-menu.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,6 @@
* under the License.
*/

import { FAKE_HOLD_OPEN_PROMPT } from '@maka/runtime/test-only/fake-backend';
import { expect, test, COMPOSER_INPUT } from './fixtures';

test('shows only slash commands executable in the current session state', async ({
Expand DownExpand Up@@ -208,34 +207,6 @@ test('opens the slash menu after a DOM block break', async ({
await expect(menu).toBeVisible();
});

test('dispatches /side instead of steering it into a running turn', async ({
invocableSkillsWindow: page,
}) => {
const composer = page.locator(COMPOSER_INPUT);
const runningPrompt = FAKE_HOLD_OPEN_PROMPT;
await composer.fill(runningPrompt);
await composer.press('Enter');
await expect(page.locator('.maka-user-message', { hasText: runningPrompt })).toBeVisible();
await expect(page.getByRole('button', { name: '停止' })).toBeVisible();

await composer.click();
await composer.pressSequentially('/');
const menu = page.getByRole('listbox', { name: '命令和技能' });
const commands = menu.getByRole('group', { name: '命令' });
const side = commands.getByRole('option', { name: /打开侧聊.*\/side/ });
await expect(side).toBeVisible();
await expect(commands.getByRole('option', { name: /\/compact/ })).toHaveCount(0);
await side.click();
await expect.poll(() => composer.textContent()).toBe('/side ');
await expect(page.locator('.maka-quote-workbar-panel')).toHaveCount(0);

await composer.fill('/side discuss separately');
await composer.press('Enter');

await expect(page.locator('.maka-quote-workbar-panel')).toHaveCount(1);
await page.getByRole('button', { name: '停止' }).click();
});

test('an open menu keeps its container and skills group across projection refreshes', async ({
invocableSkillsWindow: page,
}) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/renderer-architecture.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -891,7 +891,7 @@
"react": 1
},
"importSpecifiers": 148,
"nonTriviaTokens": 15620
"nonTriviaTokens": 15617
},
"src/renderer/use-app-shell-composer-quotes.ts": {
"importDeclarations": 2,
Expand Down
23 changes: 23 additions & 0 deletions apps/desktop/src/main/__tests__/follow-up-submit-routing.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,13 +50,15 @@ describe('follow-up submit routing', () => {
assert.equal(
resolveFollowUpModeAtSubmit({
hasActiveTurn: true,
slashCommand: null,
}),
'queue',
);
assert.equal(
resolveFollowUpModeAtSubmit({
requestedMode: 'steer',
hasActiveTurn: true,
slashCommand: null,
}),
'steer',
);
Expand All@@ -66,6 +68,27 @@ describe('follow-up submit routing', () => {
assert.equal(
resolveFollowUpModeAtSubmit({
hasActiveTurn: false,
slashCommand: null,
}),
undefined,
);
});

it('dispatches a slash command mid-turn instead of steering it into the Turn', () => {
assert.equal(
resolveFollowUpModeAtSubmit({
hasActiveTurn: true,
slashCommand: { kind: 'side' },
}),
undefined,
);
// An explicit steer request loses to the command too: Shift+Enter on
// `/side` still opens the side chat.
assert.equal(
resolveFollowUpModeAtSubmit({
requestedMode: 'steer',
hasActiveTurn: true,
slashCommand: { kind: 'side' },
}),
undefined,
);
Expand Down
11 changes: 5 additions & 6 deletions apps/desktop/src/renderer/app-shell.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -1919,12 +1919,11 @@ function AppShellContent({
const runningTurnIds = sessionId
? sessionsRef.current.find((session) => session.id === sessionId)?.runningTurnIds
: undefined;
const followUpAtSubmit = !slashCommand
? resolveFollowUpModeAtSubmit({
requestedMode: metadata?.followUpMode,
hasActiveTurn: hasActiveTurnAtSubmit({ liveTurn, runningTurnIds }),
})
: undefined;
const followUpAtSubmit = resolveFollowUpModeAtSubmit({
requestedMode: metadata?.followUpMode,
hasActiveTurn: hasActiveTurnAtSubmit({ liveTurn, runningTurnIds }),
slashCommand,
});
if (sessionId && followUpAtSubmit) {
const queued = await enqueueFollowUp(sessionId, text, followUpAtSubmit, {
...metadata,
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/renderer/follow-up-submit-routing.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,12 @@ export function hasActiveTurnAtSubmit(input: {
export function resolveFollowUpModeAtSubmit(input: {
requestedMode?: FollowUpMode;
hasActiveTurn: boolean;
/** The parsed command, if the text was one. Only its presence matters here. */
slashCommand: object | null;
}): FollowUpMode | undefined {
// A slash command tells the app to do something; it is not text for the
// Turn that happens to be running. Dispatch it instead of queueing it.
if (input.slashCommand) return undefined;
if (input.requestedMode) return input.requestedMode;
// Mid-turn submits always queue; Shift+Enter carries the one-shot steer as
// the requested mode.
Expand Down