feat: update desktop workflows and app center

This commit is contained in:
inman
2026-05-13 19:14:56 +08:00
parent 20b5aff4ad
commit 7c8781a6e3
160 changed files with 55492 additions and 1423 deletions

View File

@@ -6,6 +6,9 @@ const chatStateMock = vi.hoisted(() => ({
state: {
currentSessionKey: 'session-1',
sessions: [] as Array<{ key: string }>,
messages: [] as Array<{ id?: string; role: string; content: unknown; timestamp?: number }>,
sessionLabels: {} as Record<string, string>,
sessionLastActivity: {} as Record<string, number>,
sending: true,
activeRunId: 'run-1' as string | null,
pendingFinal: true,
@@ -28,8 +31,8 @@ vi.mock('@/lib/host-events', () => ({
vi.mock('@/stores/chat', () => ({
useChatStore: {
getState: () => chatStateMock.state,
setState: (patch: Record<string, unknown>) => {
Object.assign(chatStateMock.state, patch);
setState: (patch: Record<string, unknown> | ((state: typeof chatStateMock.state) => Record<string, unknown>)) => {
Object.assign(chatStateMock.state, typeof patch === 'function' ? patch(chatStateMock.state) : patch);
},
},
}));
@@ -41,6 +44,9 @@ describe('gateway store event wiring', () => {
Object.assign(chatStateMock.state, {
currentSessionKey: 'session-1',
sessions: [],
messages: [],
sessionLabels: {},
sessionLastActivity: {},
sending: true,
activeRunId: 'run-1',
pendingFinal: true,
@@ -138,6 +144,120 @@ describe('gateway store event wiring', () => {
expect(chatStateMock.state.pendingFinal).toBe(true);
});
it('makes newly started background sessions visible before sessions.list refreshes', async () => {
const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1773300000000);
hostApiFetchMock.mockResolvedValueOnce({ state: 'running', port: 18789 });
const handlers = new Map<string, (payload: unknown) => void>();
subscribeHostEventMock.mockImplementation((eventName: string, handler: (payload: unknown) => void) => {
handlers.set(eventName, handler);
return () => {};
});
const { useGatewayStore } = await import('@/stores/gateway');
await useGatewayStore.getState().init();
handlers.get('gateway:notification')?.({
method: 'agent',
params: {
phase: 'started',
runId: 'run-cron-1',
sessionKey: 'agent:main:cron:job-1',
},
});
await vi.waitFor(() => {
expect(chatStateMock.state.sessions).toContainEqual(expect.objectContaining({
key: 'agent:main:cron:job-1',
updatedAt: 1773300000000,
}));
});
expect(chatStateMock.state.sessionLastActivity['agent:main:cron:job-1']).toBe(1773300000000);
nowSpy.mockRestore();
});
it('folds isolated cron run events back into the fixed task conversation', async () => {
const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1773300000000);
hostApiFetchMock.mockResolvedValueOnce({ state: 'running', port: 18789 });
Object.assign(chatStateMock.state, {
currentSessionKey: 'agent:main:cron:job-1',
activeRunId: null,
activeRunSessionKey: 'agent:main:cron:job-1',
sending: true,
});
const handlers = new Map<string, (payload: unknown) => void>();
subscribeHostEventMock.mockImplementation((eventName: string, handler: (payload: unknown) => void) => {
handlers.set(eventName, handler);
return () => {};
});
const { useGatewayStore } = await import('@/stores/gateway');
const { useCronStore } = await import('@/stores/cron');
useCronStore.setState({
jobs: [{
id: 'job-1',
name: '每日经营日报',
message: '生成日报',
schedule: '0 9 * * *',
enabled: true,
createdAt: '2026-05-13T00:00:00.000Z',
updatedAt: '2026-05-13T00:00:00.000Z',
agentId: 'main',
}],
loading: false,
error: null,
});
await useGatewayStore.getState().init();
handlers.get('gateway:notification')?.({
method: 'agent',
params: {
phase: 'started',
runId: 'run-cron-1',
sessionKey: 'agent:main:cron:job-1:run:session-a',
},
});
await vi.waitFor(() => {
expect(chatStateMock.state.sessions).toContainEqual(expect.objectContaining({
key: 'agent:main:cron:job-1',
updatedAt: 1773300000000,
}));
});
expect(chatStateMock.state.sessions.find((session) => session.key === 'agent:main:cron:job-1:run:session-a')).toBeUndefined();
expect(chatStateMock.state.handleChatEvent).toHaveBeenCalledWith(expect.objectContaining({
state: 'started',
runId: 'run-cron-1',
sessionKey: 'agent:main:cron:job-1',
}));
expect(chatStateMock.state.messages).toContainEqual(expect.objectContaining({
role: 'user',
content: '生成日报',
timestamp: 1773300000000,
}));
expect(chatStateMock.state.messages).toContainEqual(expect.objectContaining({
role: 'assistant',
content: '任务已开始执行,完成后会自动在这里显示结果。',
}));
handlers.get('gateway:notification')?.({
method: 'agent',
params: {
phase: 'completed',
runId: 'run-cron-1',
sessionKey: 'agent:main:cron:job-1:run:session-a',
},
});
await vi.waitFor(() => {
expect(chatStateMock.state.loadHistory).toHaveBeenCalledWith(false);
});
expect(chatStateMock.state.sending).toBe(false);
expect(chatStateMock.state.activeRunSessionKey).toBeNull();
nowSpy.mockRestore();
});
it('clears sending state on terminal agent completion notifications', async () => {
hostApiFetchMock.mockResolvedValueOnce({ state: 'running', port: 18789 });