feat: prepare Zhinian desktop client for pilot release

This commit is contained in:
inman
2026-04-29 10:23:20 +08:00
parent f9361e686a
commit 47b83b79fc
149 changed files with 15341 additions and 3590 deletions

View File

@@ -13,14 +13,19 @@ const proxyAwareFetchMock = vi.fn();
const saveChannelConfigMock = vi.fn();
const setChannelDefaultAccountMock = vi.fn();
const assignChannelAccountToAgentMock = vi.fn();
const clearAllBindingsForChannelMock = vi.fn();
const deleteAgentConfigMock = vi.fn();
const deleteChannelAccountConfigMock = vi.fn();
const deleteChannelConfigMock = vi.fn();
const ensureChannelAgentForAccountMock = vi.fn();
const clearChannelBindingMock = vi.fn();
const parseJsonBodyMock = vi.fn();
const testOpenClawConfigDir = join(tmpdir(), 'clawx-tests', 'channel-routes-openclaw');
vi.mock('@electron/utils/channel-config', () => ({
cleanupDanglingWeChatPluginState: vi.fn(),
deleteChannelAccountConfig: vi.fn(),
deleteChannelConfig: vi.fn(),
deleteChannelAccountConfig: (...args: unknown[]) => deleteChannelAccountConfigMock(...args),
deleteChannelConfig: (...args: unknown[]) => deleteChannelConfigMock(...args),
getChannelFormValues: vi.fn(),
listConfiguredChannelAccounts: (...args: unknown[]) => listConfiguredChannelAccountsMock(...args),
listConfiguredChannelAccountsFromConfig: (...args: unknown[]) => listConfiguredChannelAccountsMock(...args),
@@ -36,8 +41,10 @@ vi.mock('@electron/utils/channel-config', () => ({
vi.mock('@electron/utils/agent-config', () => ({
assignChannelAccountToAgent: (...args: unknown[]) => assignChannelAccountToAgentMock(...args),
clearAllBindingsForChannel: vi.fn(),
clearAllBindingsForChannel: (...args: unknown[]) => clearAllBindingsForChannelMock(...args),
clearChannelBinding: (...args: unknown[]) => clearChannelBindingMock(...args),
deleteAgentConfig: (...args: unknown[]) => deleteAgentConfigMock(...args),
ensureChannelAgentForAccount: (...args: unknown[]) => ensureChannelAgentForAccountMock(...args),
listAgentsSnapshot: (...args: unknown[]) => listAgentsSnapshotMock(...args),
listAgentsSnapshotFromConfig: (...args: unknown[]) => listAgentsSnapshotMock(...args),
}));
@@ -203,6 +210,193 @@ describe('handleChannelRoutes', () => {
);
});
it('auto-creates missing AgentBus channel agent bindings while listing accounts', async () => {
listConfiguredChannelsMock.mockResolvedValue(['agentbus']);
listConfiguredChannelAccountsMock.mockResolvedValue({
agentbus: {
defaultAccountId: 'acct-20260427-070043-65935ec0',
accountIds: ['acct-20260427-070043-65935ec0'],
},
});
readOpenClawConfigMock.mockResolvedValue({
bindings: [],
channels: {
agentbus: {
defaultAccount: 'acct-20260427-070043-65935ec0',
accounts: {
'acct-20260427-070043-65935ec0': {
enabled: true,
},
},
},
},
});
listAgentsSnapshotMock.mockResolvedValue({
agents: [],
channelOwners: {},
channelAccountOwners: {},
});
ensureChannelAgentForAccountMock.mockResolvedValue({
agents: [],
channelOwners: { agentbus: 'channel-agentbus-acct-20260427-070043-65935ec0' },
channelAccountOwners: {
'agentbus:acct-20260427-070043-65935ec0': 'channel-agentbus-acct-20260427-070043-65935ec0',
},
});
const rpc = vi.fn().mockResolvedValue({
channels: {
agentbus: {
configured: true,
},
},
channelAccounts: {
agentbus: [
{
accountId: 'acct-20260427-070043-65935ec0',
configured: true,
connected: false,
running: false,
linked: false,
},
],
},
channelDefaultAccountId: {
agentbus: 'acct-20260427-070043-65935ec0',
},
});
const { handleChannelRoutes } = await import('@electron/api/routes/channels');
await handleChannelRoutes(
{ method: 'GET' } as IncomingMessage,
{} as ServerResponse,
new URL('http://127.0.0.1:13210/api/channels/accounts'),
{
gatewayManager: {
rpc,
getStatus: () => ({ state: 'running' }),
getDiagnostics: () => ({ consecutiveHeartbeatMisses: 0, consecutiveRpcFailures: 0 }),
debouncedReload: vi.fn(),
debouncedRestart: vi.fn(),
},
} as never,
);
expect(ensureChannelAgentForAccountMock).toHaveBeenCalledWith(
'agentbus',
'acct-20260427-070043-65935ec0',
);
expect(sendJsonMock).toHaveBeenCalledWith(
expect.anything(),
200,
expect.objectContaining({
success: true,
channels: [
expect.objectContaining({
channelType: 'agentbus',
accounts: [
expect.objectContaining({
accountId: 'acct-20260427-070043-65935ec0',
agentId: 'channel-agentbus-acct-20260427-070043-65935ec0',
status: 'connected',
}),
],
}),
],
}),
);
});
it('deletes the managed channel agent when deleting a channel account', async () => {
readOpenClawConfigMock.mockResolvedValue({
bindings: [
{
agentId: 'channel-agentbus-acct-20260427-070043-65935ec0',
match: {
channel: 'agentbus',
accountId: 'acct-20260427-070043-65935ec0',
},
},
],
channels: {
agentbus: {
enabled: true,
defaultAccount: 'acct-20260427-070043-65935ec0',
accounts: {
'acct-20260427-070043-65935ec0': { enabled: true },
},
},
},
});
const { handleChannelRoutes } = await import('@electron/api/routes/channels');
await handleChannelRoutes(
{ method: 'DELETE' } as IncomingMessage,
{} as ServerResponse,
new URL('http://127.0.0.1:13210/api/channels/config/agentbus?accountId=acct-20260427-070043-65935ec0'),
{
gatewayManager: {
rpc: vi.fn(),
getStatus: () => ({ state: 'running' }),
debouncedReload: vi.fn(),
debouncedRestart: vi.fn(),
},
} as never,
);
expect(deleteAgentConfigMock).toHaveBeenCalledWith('channel-agentbus-acct-20260427-070043-65935ec0');
expect(deleteChannelAccountConfigMock).toHaveBeenCalledWith(
'agentbus',
'acct-20260427-070043-65935ec0',
);
expect(clearChannelBindingMock).toHaveBeenCalledWith('agentbus', 'acct-20260427-070043-65935ec0');
});
it('does not delete a manually selected non-managed agent when deleting a channel account', async () => {
readOpenClawConfigMock.mockResolvedValue({
bindings: [
{
agentId: 'sales-agent',
match: {
channel: 'agentbus',
accountId: 'acct-20260427-070043-65935ec0',
},
},
],
channels: {
agentbus: {
enabled: true,
defaultAccount: 'acct-20260427-070043-65935ec0',
accounts: {
'acct-20260427-070043-65935ec0': { enabled: true },
},
},
},
});
const { handleChannelRoutes } = await import('@electron/api/routes/channels');
await handleChannelRoutes(
{ method: 'DELETE' } as IncomingMessage,
{} as ServerResponse,
new URL('http://127.0.0.1:13210/api/channels/config/agentbus?accountId=acct-20260427-070043-65935ec0'),
{
gatewayManager: {
rpc: vi.fn(),
getStatus: () => ({ state: 'running' }),
debouncedReload: vi.fn(),
debouncedRestart: vi.fn(),
},
} as never,
);
expect(deleteAgentConfigMock).not.toHaveBeenCalled();
expect(deleteChannelAccountConfigMock).toHaveBeenCalledWith(
'agentbus',
'acct-20260427-070043-65935ec0',
);
expect(clearChannelBindingMock).toHaveBeenCalledWith('agentbus', 'acct-20260427-070043-65935ec0');
});
it('rejects non-canonical account ID on channel config save', async () => {
parseJsonBodyMock.mockResolvedValue({
channelType: 'feishu',
@@ -271,6 +465,7 @@ describe('handleChannelRoutes', () => {
{ botToken: 'token', allowedUsers: '123456' },
'Legacy_Account',
);
expect(ensureChannelAgentForAccountMock).toHaveBeenCalledWith('telegram', 'Legacy_Account');
expect(sendJsonMock).toHaveBeenCalledWith(
expect.anything(),
200,
@@ -760,6 +955,7 @@ describe('handleChannelRoutes', () => {
);
expect(assignChannelAccountToAgentMock).toHaveBeenCalledWith('main', 'telegram', 'default');
expect(clearChannelBindingMock).toHaveBeenCalledWith('telegram');
expect(ensureChannelAgentForAccountMock).toHaveBeenCalledWith('telegram', 'telegram-a1b2c3d4');
expect(assignChannelAccountToAgentMock).not.toHaveBeenCalledWith('main', 'telegram', 'telegram-a1b2c3d4');
});