mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-08-24 16:13:44 +00:00
269 lines
8.3 KiB
TypeScript
269 lines
8.3 KiB
TypeScript
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
|
|
import { fetchGet, fetchPost } from '@/api/http';
|
|
import { __remoteControlBridgeTestHooks } from '@/hooks/useRemoteControlBridge';
|
|
import {
|
|
createFollowUpRequest,
|
|
listPendingFollowUpRequests,
|
|
markFollowUpRequestAdmitted,
|
|
} from '@/service/followUpQueueApi';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const restoreQueuedMessage = vi.fn();
|
|
const removeQueuedMessage = vi.fn();
|
|
const startTask = vi.fn();
|
|
|
|
vi.mock('@/api/http', () => ({
|
|
fetchGet: vi.fn(),
|
|
fetchPost: vi.fn(),
|
|
getBaseURL: vi.fn(() => 'http://localhost:5001'),
|
|
getLocalControlCapability: vi.fn(() => 'capability'),
|
|
proxyFetchGet: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/service/followUpQueueApi', () => ({
|
|
createFollowUpRequest: vi.fn(),
|
|
getRemoteFollowUpByCommandId: vi.fn(),
|
|
listPendingFollowUpRequests: vi.fn(),
|
|
listPendingRemoteFollowUpRequests: vi.fn(),
|
|
markFollowUpRequestAdmitted: vi.fn(),
|
|
terminalContinuationAdmissionRejection: (error: any) => {
|
|
const detail = error?.response?.data?.detail;
|
|
return detail?.interaction_type === 'continuation_clarification' &&
|
|
detail?.code === 'continuation_clarification_required'
|
|
? detail
|
|
: null;
|
|
},
|
|
}));
|
|
|
|
vi.mock('@/store/projectStore', () => ({
|
|
useProjectStore: {
|
|
getState: () => ({
|
|
projects: { 'project-1': { chatStores: {} } },
|
|
getProjectById: () => ({ mode: 'single-agent' }),
|
|
getChatStore: () => ({ getState: () => ({ startTask }) }),
|
|
restoreQueuedMessage,
|
|
removeQueuedMessage,
|
|
}),
|
|
},
|
|
}));
|
|
|
|
vi.mock('@/store/spaceStore', () => ({
|
|
useSpaceStore: {
|
|
getState: () => ({
|
|
getProjectMeta: vi.fn(),
|
|
upsertSpaces: vi.fn(),
|
|
upsertProjectMetas: vi.fn(),
|
|
resetForUser: vi.fn(),
|
|
ensureLegacySpace: vi.fn(),
|
|
}),
|
|
},
|
|
projectMetaFromServer: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/store/authStore', () => ({
|
|
getAuthStore: () => ({ logout: vi.fn() }),
|
|
}));
|
|
|
|
describe('Remote Control durable follow-up admission', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(createFollowUpRequest).mockResolvedValue({
|
|
request_id: 'run-2',
|
|
project_id: 'project-1',
|
|
content: 'continue the report',
|
|
attachment_paths: [],
|
|
delivery_mode: 'wait',
|
|
status: 'pending',
|
|
source: 'remote_control',
|
|
source_command_id: 'command-1',
|
|
created_at: 1,
|
|
updated_at: 1,
|
|
});
|
|
vi.mocked(listPendingFollowUpRequests).mockResolvedValue([
|
|
{
|
|
request_id: 'run-2',
|
|
project_id: 'project-1',
|
|
content: 'continue the report',
|
|
attachment_paths: [],
|
|
delivery_mode: 'wait',
|
|
status: 'pending',
|
|
source: 'remote_control',
|
|
source_command_id: 'command-1',
|
|
created_at: 1,
|
|
updated_at: 1,
|
|
},
|
|
]);
|
|
vi.mocked(fetchGet).mockResolvedValue({
|
|
has_lock: true,
|
|
status: 'processing',
|
|
});
|
|
vi.mocked(markFollowUpRequestAdmitted).mockResolvedValue({} as never);
|
|
startTask.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it('persists an active-Run phone message instead of calling /chat directly', async () => {
|
|
const schedule = vi.fn();
|
|
const command = {
|
|
id: 'command-1',
|
|
session_id: 'session-1',
|
|
user_id: 1,
|
|
source_channel: 'remote_control',
|
|
type: 'user_message',
|
|
target_project_id: 'project-1',
|
|
next_task_id: 'run-2',
|
|
payload: { content: 'continue the report' },
|
|
};
|
|
|
|
const ack = await __remoteControlBridgeTestHooks.executeRemoteCommand(
|
|
command,
|
|
'token',
|
|
schedule
|
|
);
|
|
|
|
expect(createFollowUpRequest).toHaveBeenCalledWith({
|
|
projectId: 'project-1',
|
|
requestId: 'run-2',
|
|
content: 'continue the report',
|
|
attachmentPaths: [],
|
|
source: 'remote_control',
|
|
sourceCommandId: 'command-1',
|
|
});
|
|
expect(schedule).toHaveBeenCalledWith(command);
|
|
expect(fetchPost).not.toHaveBeenCalledWith(
|
|
'/chat/project-1',
|
|
expect.anything()
|
|
);
|
|
expect(ack).toMatchObject({
|
|
status: 'acknowledged',
|
|
result: { follow_up_request_id: 'run-2', queued: true },
|
|
});
|
|
});
|
|
|
|
it('reconstructs an enqueue acknowledgement from its durable queue row', () => {
|
|
expect(
|
|
__remoteControlBridgeTestHooks.ackFromDurableFollowUp('command-1', {
|
|
request_id: 'run-2',
|
|
project_id: 'project-1',
|
|
content: 'continue the report',
|
|
attachment_paths: [],
|
|
delivery_mode: 'wait',
|
|
status: 'admitted',
|
|
admitted_run_id: 'run-2',
|
|
source: 'remote_control',
|
|
source_command_id: 'command-1',
|
|
created_at: 1,
|
|
updated_at: 2,
|
|
})
|
|
).toMatchObject({
|
|
status: 'acknowledged',
|
|
replayed_from_cache: true,
|
|
result: {
|
|
follow_up_request_id: 'run-2',
|
|
queued: false,
|
|
follow_up_status: 'admitted',
|
|
admitted_run_id: 'run-2',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('marks an idle Project request admitted only after Brain accepts its stream', async () => {
|
|
vi.mocked(fetchGet).mockResolvedValue({ has_lock: false, status: 'idle' });
|
|
const command = {
|
|
id: 'command-1',
|
|
session_id: 'session-1',
|
|
user_id: 1,
|
|
source_channel: 'remote_control',
|
|
type: 'user_message',
|
|
target_project_id: 'project-1',
|
|
next_task_id: 'run-2',
|
|
payload: { content: 'continue the report' },
|
|
};
|
|
|
|
await __remoteControlBridgeTestHooks.executeRemoteCommand(command, 'token');
|
|
|
|
expect(startTask).toHaveBeenCalledWith(
|
|
'run-2',
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
'continue the report',
|
|
[],
|
|
undefined,
|
|
'project-1',
|
|
'single-agent',
|
|
expect.objectContaining({ preserveTaskId: true, awaitAdmission: true })
|
|
);
|
|
expect(markFollowUpRequestAdmitted).toHaveBeenCalledWith(
|
|
'project-1',
|
|
'run-2',
|
|
'run-2'
|
|
);
|
|
expect(removeQueuedMessage).toHaveBeenCalledWith('project-1', 'run-2');
|
|
});
|
|
|
|
it('keeps the durable row pending when idle admission is rejected', async () => {
|
|
vi.mocked(fetchGet).mockResolvedValue({ has_lock: false, status: 'idle' });
|
|
startTask.mockRejectedValueOnce(new Error('admission rejected'));
|
|
const command = {
|
|
id: 'command-1',
|
|
session_id: 'session-1',
|
|
user_id: 1,
|
|
source_channel: 'remote_control',
|
|
type: 'user_message',
|
|
target_project_id: 'project-1',
|
|
next_task_id: 'run-2',
|
|
payload: { content: 'continue the report' },
|
|
};
|
|
|
|
await expect(
|
|
__remoteControlBridgeTestHooks.executeRemoteCommand(command, 'token')
|
|
).rejects.toThrow('admission rejected');
|
|
expect(markFollowUpRequestAdmitted).not.toHaveBeenCalled();
|
|
expect(removeQueuedMessage).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('removes the renderer row after a durable continuation clarification', async () => {
|
|
vi.mocked(fetchGet).mockResolvedValue({ has_lock: false, status: 'idle' });
|
|
const error: any = new Error('Say what should continue');
|
|
error.response = {
|
|
data: {
|
|
detail: {
|
|
code: 'continuation_clarification_required',
|
|
message: 'Say what should continue',
|
|
interaction_type: 'continuation_clarification',
|
|
project_state_version: 2,
|
|
},
|
|
},
|
|
};
|
|
startTask.mockRejectedValueOnce(error);
|
|
const command = {
|
|
id: 'command-1',
|
|
session_id: 'session-1',
|
|
user_id: 1,
|
|
source_channel: 'remote_control',
|
|
type: 'user_message',
|
|
target_project_id: 'project-1',
|
|
next_task_id: 'run-2',
|
|
payload: { content: 'continue the report' },
|
|
};
|
|
|
|
await expect(
|
|
__remoteControlBridgeTestHooks.executeRemoteCommand(command, 'token')
|
|
).rejects.toThrow('Say what should continue');
|
|
expect(markFollowUpRequestAdmitted).not.toHaveBeenCalled();
|
|
expect(removeQueuedMessage).toHaveBeenCalledWith('project-1', 'run-2');
|
|
});
|
|
});
|