Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
896e333
feat(runtime): add GPT-compatible ApplyPatch editing projection
sunheyi6 Jul 28, 2026
bf9bad1
fix(ci): format ApplyPatch sources and exhaust worker mocks
sunheyi6 Jul 28, 2026
0f861f6
fix(runtime): address ApplyPatch review — shared engine, permissions,…
sunheyi6 Jul 29, 2026
ff84a98
fix(ci): format ApplyPatch sources and update surface identity tests
sunheyi6 Jul 29, 2026
4273188
fix(ci): expect editingProtocol in headless product-tool identity sna…
sunheyi6 Jul 29, 2026
1879417
fix(runtime): harden ApplyPatch against review P1 regressions
sunheyi6 Jul 30, 2026
9f27a6c
fix(ci): format ApplyPatch artifact test for biome
sunheyi6 Jul 30, 2026
2bf388a
fix ApplyPatch product wiring and filesystem edges
sunheyi6 Jul 30, 2026
1aefe98
fix latest ApplyPatch review findings
sunheyi6 Jul 30, 2026
766b680
fix Desktop tool policy CI assertion
sunheyi6 Jul 30, 2026
796e793
refactor(runtime): centralize apply patch transaction planning
sunheyi6 Jul 30, 2026
6da3471
fix(desktop): prevent startup white screen
sunheyi6 Aug 1, 2026
1dcff3f
fix(ui): redesign keyboard shortcuts dialog
sunheyi6 Aug 1, 2026
1174e7d
Merge PR #1734: fix(desktop) prevent startup white screen
sunheyi6 Aug 1, 2026
9db9b70
Merge PR #1735: fix(ui) redesign keyboard shortcuts dialog
sunheyi6 Aug 1, 2026
85623f5
Merge branch 'main' of https://github.com/sunheyi6/maka-agent
sunheyi6 Aug 1, 2026
4c86288
Merge branch 'main' of https://github.com/sunheyi6/maka-agent
sunheyi6 Aug 2, 2026
b36c316
Merge remote-tracking branch 'origin/main' into pr-1556-work
sunheyi6 Aug 4, 2026
95e1455
fix(runtime): address ApplyPatch review findings and resolve main merge
sunheyi6 Aug 4, 2026
81f5a7e
merge upstream main: resolve workspace-containment refactor (#2059) c…
sunheyi6 Aug 4, 2026
fee4682
adapt worker tests to #2059 follow-final semantics; restore editingPr…
sunheyi6 Aug 4, 2026
e29ce4d
fix CI: worker pin gating, replace-mode follow, symlink targetType, s…
sunheyi6 Aug 4, 2026
c6c5583
chore: biome format touched worker sources
sunheyi6 Aug 4, 2026
03338c8
Merge remote-tracking branch 'upstream/main' into pr-1556-work
sunheyi6 Aug 4, 2026
9c8359e
chore: biome format headless tools test, subagent-tools test, agent-c…
sunheyi6 Aug 4, 2026
d3ace75
fix(runtime): replace-mode writes resolve through the followed target…
sunheyi6 Aug 4, 2026
776e651
merge upstream main: execution-boundary file-path authority (#2087)
sunheyi6 Aug 4, 2026
92aa5f7
fix(runtime): worker revalidation follows replace-mode like the client
sunheyi6 Aug 4, 2026
8ed7bd4
refactor(runtime): converge ApplyPatch on one filesystem authority pe…
sunheyi6 Aug 4, 2026
09615bb
merge upstream main: resolve e2e conflict (main-side version)
sunheyi6 Aug 4, 2026
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
196 changes: 192 additions & 4 deletions apps/desktop/e2e/floating-layers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,114 @@
import { expect, test, COMPOSER_INPUT } from './fixtures.js';
import { expect, test } from './fixtures.js';

/**
* #1565 PR 5 — Astryx owns Tooltip and Popover behavior. These journeys lock
* the public user contract: surfaces open, dismiss, and restore focus without
* Maka inspecting Astryx's native layer implementation.
*/

test('tooltip opens on hover and dismisses on Escape', async ({
window: page,
}) => {
const trigger = page.getByRole('button', { name: '搜索对话' });
await expect(trigger).toBeVisible();

await trigger.hover();
const tooltip = page.getByRole('tooltip');
await expect(tooltip).toBeVisible();
await expect(tooltip).toContainText('搜索对话');

// WCAG 1.4.13: hover content must be dismissible without moving the pointer,
// and an Escape-dismissed tooltip must not reappear until the pointer leaves
// and re-enters. Wait past the 200ms Astryx show delay before concluding —
// an immediate assertion would pass vacuously while a re-show is pending.
await page.keyboard.press('Escape');
await expect(tooltip).toBeHidden();
await page.waitForTimeout(350);
await expect(tooltip).toBeHidden();

// And it must not linger once the pointer leaves.
await page.mouse.move(10, 300);
await trigger.hover();
await expect(tooltip).toBeVisible();
await page.mouse.move(10, 300);
await expect(tooltip).toBeHidden();
});

test('daily review uses the canonical time field and persists its value', async ({
window: page,
}) => {
await page.getByRole('button', { name: '展开侧边栏' }).click();
await page.getByRole('button', { name: '设置' }).click();
const settingsNavigation = page.getByRole('navigation', { name: '设置分组' });
const settings = page.getByRole('main', { name: '设置内容' });
await settingsNavigation.getByRole('button', { name: '每日回顾', exact: true }).click();

const time = settings.getByRole('textbox', { name: '每日回顾执行时间' });
await expect(time).toHaveValue('08:00');
await time.fill('08:05');
await time.blur();
await expect(time).toHaveValue('08:05');

await settingsNavigation.getByRole('button', { name: '通用', exact: true }).click();
await settingsNavigation.getByRole('button', { name: '每日回顾', exact: true }).click();
const persistedTime = settings.getByRole('textbox', {
name: '每日回顾执行时间',
});
await expect(persistedTime).toHaveValue('08:05');

await persistedTime.fill('24:00');
await persistedTime.blur();
await expect(persistedTime).toHaveAttribute('aria-invalid', 'true');
await expect(
settings.getByText('请输入 24 小时制时间,例如 08:00。'),
).toBeVisible();

await settingsNavigation.getByRole('button', { name: '通用', exact: true }).click();
await settingsNavigation.getByRole('button', { name: '每日回顾', exact: true }).click();
await expect(
settings.getByRole('textbox', { name: '每日回顾执行时间' }),
).toHaveValue('08:05');
});

test('model picker only exposes a rendered active descendant', async ({
window: page,
}) => {
await page.getByRole('button', { name: /选择新对话模型/ }).click();

const search = page.getByPlaceholder('搜索模型');
await expect(search).toBeFocused();

await search.fill('no-such-model');
await expect(page.getByRole('listbox').getByText('No results found')).toBeVisible();
await expect(search).not.toHaveAttribute('aria-activedescendant');

await search.fill('sonnet');
await search.press('ArrowDown');
const activeDescendant = await search.getAttribute('aria-activedescendant');
expect(activeDescendant).not.toBeNull();
await expect(page.locator(`#${activeDescendant}`)).toHaveRole('option');
});

// Model-picker mark geometry / label ellipsis: CSS contract
// (chat-shell-layout-contract). Listbox scroll-into-view is Astryx-owned.
// Keep focus restore + real session model/thinking persistence below.

test('model Selector restores focus to its opener on Escape', async ({
window: page,
}) => {
const trigger = page.getByRole('button', { name: /选择新对话模型/ });
await trigger.click();

const search = page.getByPlaceholder('搜索模型');
const listbox = page.getByRole('listbox');
const popup = listbox.locator('xpath=ancestor::*[@popover][1]');
await expect(search).toBeFocused();
await expect(listbox.getByRole('option').first()).toBeVisible();

await page.keyboard.press('Escape');
await expect(popup).toBeHidden();
await expect(trigger).toBeFocused();
});

test('model and adjacent thinking Selectors persist one real Electron journey', async ({
modelPickerLongWindow: page,
Expand All @@ -8,7 +118,7 @@ test('model and adjacent thinking Selectors persist one real Electron journey',
await page.getByRole('option', { name: '关', exact: true }).click();
await expect(thinkingTrigger).toContainText('关');

const composer = page.locator(COMPOSER_INPUT);
const composer = page.locator('.maka-composer-textarea');
await composer.fill('model selector persistence journey');
await composer.press('Enter');
await expect(
Expand All @@ -18,7 +128,7 @@ test('model and adjacent thinking Selectors persist one real Electron journey',
const activeThinkingTrigger = page.getByRole('combobox', { name: '思考级别' });
await expect(activeThinkingTrigger).toContainText('关');
await page.reload();
await expect(page.locator(COMPOSER_INPUT)).toBeVisible();
await expect(page.locator('.maka-composer-textarea')).toBeVisible();
await expect(page.getByRole('combobox', { name: '思考级别' })).toContainText('关');

const modelTrigger = page.getByRole('button', { name: '切换当前会话模型' });
Expand All @@ -30,11 +140,89 @@ test('model and adjacent thinking Selectors persist one real Electron journey',
await expect(modelTrigger).toContainText('claude-e2e-1');
});

test('keyboard help keeps its shortcut grid and lets Astryx restore its opener on close', async ({
window: page,
}) => {
const opener = page.getByRole('button', { name: '搜索对话' });
await opener.focus();
await page.keyboard.press('Control+/');

const dialog = page.getByRole('dialog', { name: '键盘快捷键' });
await expect(dialog).toBeVisible();

const helpBody = dialog.locator('.maka-help-body');
const firstSection = helpBody.locator('.maka-help-section').first();
const firstShortcutList = firstSection.locator('dl');
await expect(helpBody).toHaveCSS('column-count', '2');
await expect(firstShortcutList).toHaveCSS('display', 'grid');
await expect(firstShortcutList).toHaveCSS(
'grid-template-columns',
/\d+(\.\d+)?px \d+(\.\d+)?px/,
);
await expect(firstSection.locator('dd').first()).toHaveCSS(
'justify-self',
'end',
);
await expect(dialog).toHaveCSS('border-radius', '16px');
await expect(dialog).not.toHaveCSS('box-shadow', 'none');
await expect(firstSection).toHaveCSS('border-radius', '12px');
await expect(
dialog.getByRole('heading', { name: '键盘快捷键' }),
).toHaveCSS('box-shadow', 'none');
await expect(
dialog.getByRole('img', { name: /(Command|Control) \+ K/ }),
).toBeVisible();
await expect(
dialog.getByRole('img', { name: 'Left arrow' }).first(),
).toBeVisible();
await expect(
dialog.getByRole('img', { name: 'Right arrow' }).first(),
).toBeVisible();
await page.setViewportSize({ width: 640, height: 800 });
await expect(helpBody).toHaveCSS('column-count', '1');
expect((await dialog.boundingBox())?.width).toBeLessThanOrEqual(608);

await page.keyboard.press('Escape');

await expect(dialog).toBeHidden();
await expect(opener).toBeFocused();
});

test('a title-only search result restores focus to the opener', async ({
sidebarLongSessionsWindow: page,
}) => {
const opener = page.getByRole('button', { name: '搜索对话' });
await opener.click();
const dialog = page.getByRole('dialog', { name: '搜索' });
await dialog
.getByRole('combobox', { name: '搜索会话' })
.fill('会话 01');
await dialog.getByRole('option', { name: /会话 01/ }).click();

await expect(dialog).toBeHidden();
await expect(page.getByText('示例对话 01')).toBeVisible();
await expect(opener).toBeFocused();
});

test('search dialog lets Astryx restore its opener on ordinary close', async ({
window: page,
}) => {
const opener = page.getByRole('button', { name: '搜索对话' });
await opener.click();

const dialog = page.getByRole('dialog', { name: '搜索' });
await expect(dialog).toBeVisible();
await page.keyboard.press('Escape');

await expect(dialog).toBeHidden();
await expect(opener).toBeFocused();
});

test('search closes before navigating and focusing the matched turn', async ({
window: page,
}) => {
const needle = 'search ownership needle 7319';
const composer = page.locator(COMPOSER_INPUT);
const composer = page.locator('.maka-composer-textarea');
await composer.fill(needle);
await composer.press('Enter');
await expect(page.getByText(`Fake backend received: ${needle}`)).toBeVisible();
Expand Down
29 changes: 28 additions & 1 deletion apps/desktop/src/main/__tests__/create-session-input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { describe, it } from 'node:test';
import type { AppSettings, ChatDefaultPermissionMode } from '@maka/core';
import { DEEP_RESEARCH_SESSION_LABEL, DEFAULT_SESSION_NAME } from '@maka/core';

import { type CreateSessionRequest, resolveCreateSessionInput } from '../create-session-input.js';
import {
type CreateSessionRequest,
resolveCreateSessionInput,
resolveEditingProtocolEnv,
} from '../create-session-input.js';

function settings(permissionMode: ChatDefaultPermissionMode) {
return async () => ({ chatDefaults: { permissionMode } }) as AppSettings;
Expand Down Expand Up @@ -126,4 +130,27 @@ describe('resolveCreateSessionInput', () => {
assert.equal(resolved.collaborationMode, 'plan');
assert.equal(resolved.orchestrationMode, 'swarm');
});

it('normalizes the editing protocol per session request', async () => {
assert.equal((await resolve({ editingProtocol: 'apply_patch' })).editingProtocol, 'apply_patch');
assert.equal((await resolve({})).editingProtocol, 'edit_write');
await assert.rejects(() => resolve({ editingProtocol: 'all' }), TypeError);
});

it('uses the process setting only as the default for an individual session', async () => {
const readSettings = settings('ask');
const configured = await resolveCreateSessionInput(undefined, {
readSettings,
defaultEditingProtocol: 'apply_patch',
});
const overridden = await resolveCreateSessionInput(
{ editingProtocol: 'edit_write' },
{ readSettings, defaultEditingProtocol: 'apply_patch' },
);

assert.equal(configured.editingProtocol, 'apply_patch');
assert.equal(overridden.editingProtocol, 'edit_write');
assert.equal(resolveEditingProtocolEnv('apply_patch'), 'apply_patch');
assert.throws(() => resolveEditingProtocolEnv('all'));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {

const readTool = tool('Read', 'read');
const writeTool = tool('Write', 'file_write');
const applyPatchTool = tool('ApplyPatch', 'file_write');
const computerTool = tool('maka_computer', 'computer_use');
const availability: ToolAvailabilityConfig = {
economy: true,
Expand Down Expand Up @@ -135,6 +136,25 @@ describe('Desktop backend tool surface', () => {
assert.equal(surface.skillHost.toolNames.has('Write'), true);
});

it('projects ApplyPatch through the standard Desktop backend policy', async () => {
const surface = await resolveDesktopBackendToolSurface(
makeDeps({
builtinTools: [readTool, writeTool, applyPatchTool],
}),
{
...inputFor('claude-sonnet-4-5-20250929'),
header: {
...inputFor('claude-sonnet-4-5-20250929').header,
editingProtocol: 'apply_patch',
},
},
);

assert.equal(surface.skillHost.toolNames.has('ApplyPatch'), true);
assert.equal(surface.skillHost.toolNames.has('Write'), false);
assert.equal(surface.selectedTools.some((tool) => tool.name === 'Edit'), false);
});

it('keeps scoped child tools ahead of root-only computer-use and Plan controls', async () => {
const deps = makeDeps({ isComputerUseRealModelE2e: true });
const input = inputFor('claude-sonnet-4-5-20250929', 'plan');
Expand Down
15 changes: 14 additions & 1 deletion apps/desktop/src/main/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
SessionActivityRegistry,
listInvocableSkills,
prepareSkillInvocationMessage,
projectEffectiveProductToolSurface,
resolveSkillDiscoveryPaths,
} from '@maka/runtime';
import type {
Expand Down Expand Up @@ -85,6 +86,7 @@ import {
requireReadyConnection,
} from './chat-readiness.js';
import { assertDesktopExecutionBoundary } from './desktop-execution-admission.js';
import { resolveEditingProtocolEnv } from './create-session-input.js';
import { createFileCredentialStore } from './credential-store.js';
import { bindOnboardingDeps, createOnboardingService } from './onboarding-service.js';
import { createDailyReviewArchiveStore } from './daily-review-archive-store.js';
Expand Down Expand Up @@ -886,7 +888,17 @@ const runtime = new SessionManager({
inspectContinuationSafety: createLocalContinuationSafetyInspector({
readSessionCwd: async (sessionId) => (await store.readHeader(sessionId)).cwd,
resolveWorkspaceIdentity: async (cwd) => resolveWorkspaceIdentity({ path: cwd }),
listAvailableToolNames: async () => builtinTools.map((tool) => tool.name),
listAvailableToolNames: async (sessionId) => {
const header = await store.readHeader(sessionId);
return projectEffectiveProductToolSurface({
host: 'desktop',
tools: builtinTools,
policy: {
...desktopProductToolSurface.identity.policy,
editingProtocol: header.editingProtocol ?? 'edit_write',
},
}).tools.map((tool) => tool.name);
},
hasPendingBackgroundOperations: async (sessionId) => {
const [shellUpdates, runs] = await Promise.all([
shellRuns.listSessionUpdates(sessionId),
Expand Down Expand Up @@ -1127,6 +1139,7 @@ function registerIpc(): void {
streamEvents,
getWorkspacePrivacyContext,
canCreateFakeSession: canCreateFakeSessionFromRenderer,
defaultEditingProtocol: resolveEditingProtocolEnv(process.env.MAKA_EDITING_PROTOCOL),
consumeNativeAudioOperation: (input) =>
voiceIpcService.consumeNativeAudioOperation(input),
});
Expand Down
Loading