refactor(adapters): brand btmsg/bttask/groups bridge interfaces with GroupId/AgentId
Apply branded types to all IPC bridge interfaces and function parameters. Update test mock data with branded constructors.
This commit is contained in:
parent
f928abd6ce
commit
0742309595
5 changed files with 82 additions and 77 deletions
|
|
@ -29,6 +29,7 @@ import {
|
||||||
type BtmsgChannel,
|
type BtmsgChannel,
|
||||||
type BtmsgChannelMessage,
|
type BtmsgChannelMessage,
|
||||||
} from './btmsg-bridge';
|
} from './btmsg-bridge';
|
||||||
|
import { GroupId, AgentId } from '../types/ids';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
@ -42,10 +43,10 @@ describe('btmsg-bridge', () => {
|
||||||
describe('BtmsgAgent camelCase fields', () => {
|
describe('BtmsgAgent camelCase fields', () => {
|
||||||
it('receives camelCase fields from Rust backend', async () => {
|
it('receives camelCase fields from Rust backend', async () => {
|
||||||
const agent: BtmsgAgent = {
|
const agent: BtmsgAgent = {
|
||||||
id: 'a1',
|
id: AgentId('a1'),
|
||||||
name: 'Coder',
|
name: 'Coder',
|
||||||
role: 'developer',
|
role: 'developer',
|
||||||
groupId: 'g1', // was: group_id
|
groupId: GroupId('g1'), // was: group_id
|
||||||
tier: 1,
|
tier: 1,
|
||||||
model: 'claude-4',
|
model: 'claude-4',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
|
|
@ -53,7 +54,7 @@ describe('btmsg-bridge', () => {
|
||||||
};
|
};
|
||||||
mockInvoke.mockResolvedValue([agent]);
|
mockInvoke.mockResolvedValue([agent]);
|
||||||
|
|
||||||
const result = await getGroupAgents('g1');
|
const result = await getGroupAgents(GroupId('g1'));
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].groupId).toBe('g1');
|
expect(result[0].groupId).toBe('g1');
|
||||||
|
|
@ -65,7 +66,7 @@ describe('btmsg-bridge', () => {
|
||||||
|
|
||||||
it('invokes btmsg_get_agents with groupId', async () => {
|
it('invokes btmsg_get_agents with groupId', async () => {
|
||||||
mockInvoke.mockResolvedValue([]);
|
mockInvoke.mockResolvedValue([]);
|
||||||
await getGroupAgents('g1');
|
await getGroupAgents(GroupId('g1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_get_agents', { groupId: 'g1' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_get_agents', { groupId: 'g1' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -74,8 +75,8 @@ describe('btmsg-bridge', () => {
|
||||||
it('receives camelCase fields from Rust backend', async () => {
|
it('receives camelCase fields from Rust backend', async () => {
|
||||||
const msg: BtmsgMessage = {
|
const msg: BtmsgMessage = {
|
||||||
id: 'm1',
|
id: 'm1',
|
||||||
fromAgent: 'a1', // was: from_agent
|
fromAgent: AgentId('a1'), // was: from_agent
|
||||||
toAgent: 'a2', // was: to_agent
|
toAgent: AgentId('a2'), // was: to_agent
|
||||||
content: 'hello',
|
content: 'hello',
|
||||||
read: false,
|
read: false,
|
||||||
replyTo: null, // was: reply_to
|
replyTo: null, // was: reply_to
|
||||||
|
|
@ -85,7 +86,7 @@ describe('btmsg-bridge', () => {
|
||||||
};
|
};
|
||||||
mockInvoke.mockResolvedValue([msg]);
|
mockInvoke.mockResolvedValue([msg]);
|
||||||
|
|
||||||
const result = await getUnreadMessages('a2');
|
const result = await getUnreadMessages(AgentId('a2'));
|
||||||
|
|
||||||
expect(result[0].fromAgent).toBe('a1');
|
expect(result[0].fromAgent).toBe('a1');
|
||||||
expect(result[0].toAgent).toBe('a2');
|
expect(result[0].toAgent).toBe('a2');
|
||||||
|
|
@ -100,8 +101,8 @@ describe('btmsg-bridge', () => {
|
||||||
it('receives camelCase fields including recipient info', async () => {
|
it('receives camelCase fields including recipient info', async () => {
|
||||||
const feed: BtmsgFeedMessage = {
|
const feed: BtmsgFeedMessage = {
|
||||||
id: 'm1',
|
id: 'm1',
|
||||||
fromAgent: 'a1',
|
fromAgent: AgentId('a1'),
|
||||||
toAgent: 'a2',
|
toAgent: AgentId('a2'),
|
||||||
content: 'review this',
|
content: 'review this',
|
||||||
createdAt: '2026-01-01',
|
createdAt: '2026-01-01',
|
||||||
replyTo: null,
|
replyTo: null,
|
||||||
|
|
@ -112,7 +113,7 @@ describe('btmsg-bridge', () => {
|
||||||
};
|
};
|
||||||
mockInvoke.mockResolvedValue([feed]);
|
mockInvoke.mockResolvedValue([feed]);
|
||||||
|
|
||||||
const result = await getAllFeed('g1');
|
const result = await getAllFeed(GroupId('g1'));
|
||||||
|
|
||||||
expect(result[0].senderName).toBe('Coder');
|
expect(result[0].senderName).toBe('Coder');
|
||||||
expect(result[0].recipientName).toBe('Reviewer');
|
expect(result[0].recipientName).toBe('Reviewer');
|
||||||
|
|
@ -125,14 +126,14 @@ describe('btmsg-bridge', () => {
|
||||||
const channel: BtmsgChannel = {
|
const channel: BtmsgChannel = {
|
||||||
id: 'ch1',
|
id: 'ch1',
|
||||||
name: 'general',
|
name: 'general',
|
||||||
groupId: 'g1', // was: group_id
|
groupId: GroupId('g1'), // was: group_id
|
||||||
createdBy: 'admin', // was: created_by
|
createdBy: AgentId('admin'), // was: created_by
|
||||||
memberCount: 5, // was: member_count
|
memberCount: 5, // was: member_count
|
||||||
createdAt: '2026-01-01',
|
createdAt: '2026-01-01',
|
||||||
};
|
};
|
||||||
mockInvoke.mockResolvedValue([channel]);
|
mockInvoke.mockResolvedValue([channel]);
|
||||||
|
|
||||||
const result = await getChannels('g1');
|
const result = await getChannels(GroupId('g1'));
|
||||||
|
|
||||||
expect(result[0].groupId).toBe('g1');
|
expect(result[0].groupId).toBe('g1');
|
||||||
expect(result[0].createdBy).toBe('admin');
|
expect(result[0].createdBy).toBe('admin');
|
||||||
|
|
@ -145,7 +146,7 @@ describe('btmsg-bridge', () => {
|
||||||
const msg: BtmsgChannelMessage = {
|
const msg: BtmsgChannelMessage = {
|
||||||
id: 'cm1',
|
id: 'cm1',
|
||||||
channelId: 'ch1', // was: channel_id
|
channelId: 'ch1', // was: channel_id
|
||||||
fromAgent: 'a1',
|
fromAgent: AgentId('a1'),
|
||||||
content: 'hello',
|
content: 'hello',
|
||||||
createdAt: '2026-01-01',
|
createdAt: '2026-01-01',
|
||||||
senderName: 'Coder',
|
senderName: 'Coder',
|
||||||
|
|
@ -166,65 +167,65 @@ describe('btmsg-bridge', () => {
|
||||||
describe('IPC commands', () => {
|
describe('IPC commands', () => {
|
||||||
it('getUnreadCount invokes btmsg_unread_count', async () => {
|
it('getUnreadCount invokes btmsg_unread_count', async () => {
|
||||||
mockInvoke.mockResolvedValue(5);
|
mockInvoke.mockResolvedValue(5);
|
||||||
const result = await getUnreadCount('a1');
|
const result = await getUnreadCount(AgentId('a1'));
|
||||||
expect(result).toBe(5);
|
expect(result).toBe(5);
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_unread_count', { agentId: 'a1' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_unread_count', { agentId: 'a1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getHistory invokes btmsg_history', async () => {
|
it('getHistory invokes btmsg_history', async () => {
|
||||||
mockInvoke.mockResolvedValue([]);
|
mockInvoke.mockResolvedValue([]);
|
||||||
await getHistory('a1', 'a2', 50);
|
await getHistory(AgentId('a1'), AgentId('a2'), 50);
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_history', { agentId: 'a1', otherId: 'a2', limit: 50 });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_history', { agentId: 'a1', otherId: 'a2', limit: 50 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getHistory defaults limit to 20', async () => {
|
it('getHistory defaults limit to 20', async () => {
|
||||||
mockInvoke.mockResolvedValue([]);
|
mockInvoke.mockResolvedValue([]);
|
||||||
await getHistory('a1', 'a2');
|
await getHistory(AgentId('a1'), AgentId('a2'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_history', { agentId: 'a1', otherId: 'a2', limit: 20 });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_history', { agentId: 'a1', otherId: 'a2', limit: 20 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sendMessage invokes btmsg_send', async () => {
|
it('sendMessage invokes btmsg_send', async () => {
|
||||||
mockInvoke.mockResolvedValue('msg-id');
|
mockInvoke.mockResolvedValue('msg-id');
|
||||||
const result = await sendMessage('a1', 'a2', 'hello');
|
const result = await sendMessage(AgentId('a1'), AgentId('a2'), 'hello');
|
||||||
expect(result).toBe('msg-id');
|
expect(result).toBe('msg-id');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_send', { fromAgent: 'a1', toAgent: 'a2', content: 'hello' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_send', { fromAgent: 'a1', toAgent: 'a2', content: 'hello' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('setAgentStatus invokes btmsg_set_status', async () => {
|
it('setAgentStatus invokes btmsg_set_status', async () => {
|
||||||
mockInvoke.mockResolvedValue(undefined);
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
await setAgentStatus('a1', 'active');
|
await setAgentStatus(AgentId('a1'), 'active');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_set_status', { agentId: 'a1', status: 'active' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_set_status', { agentId: 'a1', status: 'active' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ensureAdmin invokes btmsg_ensure_admin', async () => {
|
it('ensureAdmin invokes btmsg_ensure_admin', async () => {
|
||||||
mockInvoke.mockResolvedValue(undefined);
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
await ensureAdmin('g1');
|
await ensureAdmin(GroupId('g1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_ensure_admin', { groupId: 'g1' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_ensure_admin', { groupId: 'g1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('markRead invokes btmsg_mark_read', async () => {
|
it('markRead invokes btmsg_mark_read', async () => {
|
||||||
mockInvoke.mockResolvedValue(undefined);
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
await markRead('a2', 'a1');
|
await markRead(AgentId('a2'), AgentId('a1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_mark_read', { readerId: 'a2', senderId: 'a1' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_mark_read', { readerId: 'a2', senderId: 'a1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sendChannelMessage invokes btmsg_channel_send', async () => {
|
it('sendChannelMessage invokes btmsg_channel_send', async () => {
|
||||||
mockInvoke.mockResolvedValue('cm-id');
|
mockInvoke.mockResolvedValue('cm-id');
|
||||||
const result = await sendChannelMessage('ch1', 'a1', 'hello channel');
|
const result = await sendChannelMessage('ch1', AgentId('a1'), 'hello channel');
|
||||||
expect(result).toBe('cm-id');
|
expect(result).toBe('cm-id');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_channel_send', { channelId: 'ch1', fromAgent: 'a1', content: 'hello channel' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_channel_send', { channelId: 'ch1', fromAgent: 'a1', content: 'hello channel' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createChannel invokes btmsg_create_channel', async () => {
|
it('createChannel invokes btmsg_create_channel', async () => {
|
||||||
mockInvoke.mockResolvedValue('ch-id');
|
mockInvoke.mockResolvedValue('ch-id');
|
||||||
const result = await createChannel('general', 'g1', 'admin');
|
const result = await createChannel('general', GroupId('g1'), AgentId('admin'));
|
||||||
expect(result).toBe('ch-id');
|
expect(result).toBe('ch-id');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_create_channel', { name: 'general', groupId: 'g1', createdBy: 'admin' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_create_channel', { name: 'general', groupId: 'g1', createdBy: 'admin' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addChannelMember invokes btmsg_add_channel_member', async () => {
|
it('addChannelMember invokes btmsg_add_channel_member', async () => {
|
||||||
mockInvoke.mockResolvedValue(undefined);
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
await addChannelMember('ch1', 'a1');
|
await addChannelMember('ch1', AgentId('a1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('btmsg_add_channel_member', { channelId: 'ch1', agentId: 'a1' });
|
expect(mockInvoke).toHaveBeenCalledWith('btmsg_add_channel_member', { channelId: 'ch1', agentId: 'a1' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -232,7 +233,7 @@ describe('btmsg-bridge', () => {
|
||||||
describe('error propagation', () => {
|
describe('error propagation', () => {
|
||||||
it('propagates invoke errors', async () => {
|
it('propagates invoke errors', async () => {
|
||||||
mockInvoke.mockRejectedValue(new Error('btmsg database not found'));
|
mockInvoke.mockRejectedValue(new Error('btmsg database not found'));
|
||||||
await expect(getGroupAgents('g1')).rejects.toThrow('btmsg database not found');
|
await expect(getGroupAgents(GroupId('g1'))).rejects.toThrow('btmsg database not found');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import type { GroupId, AgentId } from '../types/ids';
|
||||||
|
|
||||||
export interface BtmsgAgent {
|
export interface BtmsgAgent {
|
||||||
id: string;
|
id: AgentId;
|
||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
groupId: string;
|
groupId: GroupId;
|
||||||
tier: number;
|
tier: number;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
@ -19,8 +20,8 @@ export interface BtmsgAgent {
|
||||||
|
|
||||||
export interface BtmsgMessage {
|
export interface BtmsgMessage {
|
||||||
id: string;
|
id: string;
|
||||||
fromAgent: string;
|
fromAgent: AgentId;
|
||||||
toAgent: string;
|
toAgent: AgentId;
|
||||||
content: string;
|
content: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
replyTo: string | null;
|
replyTo: string | null;
|
||||||
|
|
@ -31,8 +32,8 @@ export interface BtmsgMessage {
|
||||||
|
|
||||||
export interface BtmsgFeedMessage {
|
export interface BtmsgFeedMessage {
|
||||||
id: string;
|
id: string;
|
||||||
fromAgent: string;
|
fromAgent: AgentId;
|
||||||
toAgent: string;
|
toAgent: AgentId;
|
||||||
content: string;
|
content: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
replyTo: string | null;
|
replyTo: string | null;
|
||||||
|
|
@ -45,8 +46,8 @@ export interface BtmsgFeedMessage {
|
||||||
export interface BtmsgChannel {
|
export interface BtmsgChannel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
groupId: string;
|
groupId: GroupId;
|
||||||
createdBy: string;
|
createdBy: AgentId;
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +55,7 @@ export interface BtmsgChannel {
|
||||||
export interface BtmsgChannelMessage {
|
export interface BtmsgChannelMessage {
|
||||||
id: string;
|
id: string;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
fromAgent: string;
|
fromAgent: AgentId;
|
||||||
content: string;
|
content: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
senderName: string;
|
senderName: string;
|
||||||
|
|
@ -64,70 +65,70 @@ export interface BtmsgChannelMessage {
|
||||||
/**
|
/**
|
||||||
* Get all agents in a group with their unread counts.
|
* Get all agents in a group with their unread counts.
|
||||||
*/
|
*/
|
||||||
export async function getGroupAgents(groupId: string): Promise<BtmsgAgent[]> {
|
export async function getGroupAgents(groupId: GroupId): Promise<BtmsgAgent[]> {
|
||||||
return invoke('btmsg_get_agents', { groupId });
|
return invoke('btmsg_get_agents', { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get unread message count for an agent.
|
* Get unread message count for an agent.
|
||||||
*/
|
*/
|
||||||
export async function getUnreadCount(agentId: string): Promise<number> {
|
export async function getUnreadCount(agentId: AgentId): Promise<number> {
|
||||||
return invoke('btmsg_unread_count', { agentId });
|
return invoke('btmsg_unread_count', { agentId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get unread messages for an agent.
|
* Get unread messages for an agent.
|
||||||
*/
|
*/
|
||||||
export async function getUnreadMessages(agentId: string): Promise<BtmsgMessage[]> {
|
export async function getUnreadMessages(agentId: AgentId): Promise<BtmsgMessage[]> {
|
||||||
return invoke('btmsg_unread_messages', { agentId });
|
return invoke('btmsg_unread_messages', { agentId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get conversation history between two agents.
|
* Get conversation history between two agents.
|
||||||
*/
|
*/
|
||||||
export async function getHistory(agentId: string, otherId: string, limit: number = 20): Promise<BtmsgMessage[]> {
|
export async function getHistory(agentId: AgentId, otherId: AgentId, limit: number = 20): Promise<BtmsgMessage[]> {
|
||||||
return invoke('btmsg_history', { agentId, otherId, limit });
|
return invoke('btmsg_history', { agentId, otherId, limit });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message from one agent to another.
|
* Send a message from one agent to another.
|
||||||
*/
|
*/
|
||||||
export async function sendMessage(fromAgent: string, toAgent: string, content: string): Promise<string> {
|
export async function sendMessage(fromAgent: AgentId, toAgent: AgentId, content: string): Promise<string> {
|
||||||
return invoke('btmsg_send', { fromAgent, toAgent, content });
|
return invoke('btmsg_send', { fromAgent, toAgent, content });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update agent status (active/sleeping/stopped).
|
* Update agent status (active/sleeping/stopped).
|
||||||
*/
|
*/
|
||||||
export async function setAgentStatus(agentId: string, status: string): Promise<void> {
|
export async function setAgentStatus(agentId: AgentId, status: string): Promise<void> {
|
||||||
return invoke('btmsg_set_status', { agentId, status });
|
return invoke('btmsg_set_status', { agentId, status });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure admin agent exists with contacts to all agents.
|
* Ensure admin agent exists with contacts to all agents.
|
||||||
*/
|
*/
|
||||||
export async function ensureAdmin(groupId: string): Promise<void> {
|
export async function ensureAdmin(groupId: GroupId): Promise<void> {
|
||||||
return invoke('btmsg_ensure_admin', { groupId });
|
return invoke('btmsg_ensure_admin', { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all messages in group (admin global feed).
|
* Get all messages in group (admin global feed).
|
||||||
*/
|
*/
|
||||||
export async function getAllFeed(groupId: string, limit: number = 100): Promise<BtmsgFeedMessage[]> {
|
export async function getAllFeed(groupId: GroupId, limit: number = 100): Promise<BtmsgFeedMessage[]> {
|
||||||
return invoke('btmsg_all_feed', { groupId, limit });
|
return invoke('btmsg_all_feed', { groupId, limit });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all messages from sender to reader as read.
|
* Mark all messages from sender to reader as read.
|
||||||
*/
|
*/
|
||||||
export async function markRead(readerId: string, senderId: string): Promise<void> {
|
export async function markRead(readerId: AgentId, senderId: AgentId): Promise<void> {
|
||||||
return invoke('btmsg_mark_read', { readerId, senderId });
|
return invoke('btmsg_mark_read', { readerId, senderId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get channels in a group.
|
* Get channels in a group.
|
||||||
*/
|
*/
|
||||||
export async function getChannels(groupId: string): Promise<BtmsgChannel[]> {
|
export async function getChannels(groupId: GroupId): Promise<BtmsgChannel[]> {
|
||||||
return invoke('btmsg_get_channels', { groupId });
|
return invoke('btmsg_get_channels', { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,20 +142,20 @@ export async function getChannelMessages(channelId: string, limit: number = 100)
|
||||||
/**
|
/**
|
||||||
* Send a message to a channel.
|
* Send a message to a channel.
|
||||||
*/
|
*/
|
||||||
export async function sendChannelMessage(channelId: string, fromAgent: string, content: string): Promise<string> {
|
export async function sendChannelMessage(channelId: string, fromAgent: AgentId, content: string): Promise<string> {
|
||||||
return invoke('btmsg_channel_send', { channelId, fromAgent, content });
|
return invoke('btmsg_channel_send', { channelId, fromAgent, content });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new channel.
|
* Create a new channel.
|
||||||
*/
|
*/
|
||||||
export async function createChannel(name: string, groupId: string, createdBy: string): Promise<string> {
|
export async function createChannel(name: string, groupId: GroupId, createdBy: AgentId): Promise<string> {
|
||||||
return invoke('btmsg_create_channel', { name, groupId, createdBy });
|
return invoke('btmsg_create_channel', { name, groupId, createdBy });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a member to a channel.
|
* Add a member to a channel.
|
||||||
*/
|
*/
|
||||||
export async function addChannelMember(channelId: string, agentId: string): Promise<void> {
|
export async function addChannelMember(channelId: string, agentId: AgentId): Promise<void> {
|
||||||
return invoke('btmsg_add_channel_member', { channelId, agentId });
|
return invoke('btmsg_add_channel_member', { channelId, agentId });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
type Task,
|
type Task,
|
||||||
type TaskComment,
|
type TaskComment,
|
||||||
} from './bttask-bridge';
|
} from './bttask-bridge';
|
||||||
|
import { GroupId, AgentId } from '../types/ids';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
@ -34,9 +35,9 @@ describe('bttask-bridge', () => {
|
||||||
description: 'Critical fix',
|
description: 'Critical fix',
|
||||||
status: 'progress',
|
status: 'progress',
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
assignedTo: 'a1', // was: assigned_to
|
assignedTo: AgentId('a1'), // was: assigned_to
|
||||||
createdBy: 'admin', // was: created_by
|
createdBy: AgentId('admin'), // was: created_by
|
||||||
groupId: 'g1', // was: group_id
|
groupId: GroupId('g1'), // was: group_id
|
||||||
parentTaskId: null, // was: parent_task_id
|
parentTaskId: null, // was: parent_task_id
|
||||||
sortOrder: 1, // was: sort_order
|
sortOrder: 1, // was: sort_order
|
||||||
createdAt: '2026-01-01', // was: created_at
|
createdAt: '2026-01-01', // was: created_at
|
||||||
|
|
@ -44,7 +45,7 @@ describe('bttask-bridge', () => {
|
||||||
};
|
};
|
||||||
mockInvoke.mockResolvedValue([task]);
|
mockInvoke.mockResolvedValue([task]);
|
||||||
|
|
||||||
const result = await listTasks('g1');
|
const result = await listTasks(GroupId('g1'));
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].assignedTo).toBe('a1');
|
expect(result[0].assignedTo).toBe('a1');
|
||||||
|
|
@ -64,7 +65,7 @@ describe('bttask-bridge', () => {
|
||||||
const comment: TaskComment = {
|
const comment: TaskComment = {
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
taskId: 't1', // was: task_id
|
taskId: 't1', // was: task_id
|
||||||
agentId: 'a1', // was: agent_id
|
agentId: AgentId('a1'), // was: agent_id
|
||||||
content: 'Working on it',
|
content: 'Working on it',
|
||||||
createdAt: '2026-01-01',
|
createdAt: '2026-01-01',
|
||||||
};
|
};
|
||||||
|
|
@ -84,7 +85,7 @@ describe('bttask-bridge', () => {
|
||||||
describe('IPC commands', () => {
|
describe('IPC commands', () => {
|
||||||
it('listTasks invokes bttask_list', async () => {
|
it('listTasks invokes bttask_list', async () => {
|
||||||
mockInvoke.mockResolvedValue([]);
|
mockInvoke.mockResolvedValue([]);
|
||||||
await listTasks('g1');
|
await listTasks(GroupId('g1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('bttask_list', { groupId: 'g1' });
|
expect(mockInvoke).toHaveBeenCalledWith('bttask_list', { groupId: 'g1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -102,14 +103,14 @@ describe('bttask-bridge', () => {
|
||||||
|
|
||||||
it('addTaskComment invokes bttask_add_comment', async () => {
|
it('addTaskComment invokes bttask_add_comment', async () => {
|
||||||
mockInvoke.mockResolvedValue('c-id');
|
mockInvoke.mockResolvedValue('c-id');
|
||||||
const result = await addTaskComment('t1', 'a1', 'Done!');
|
const result = await addTaskComment('t1', AgentId('a1'), 'Done!');
|
||||||
expect(result).toBe('c-id');
|
expect(result).toBe('c-id');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('bttask_add_comment', { taskId: 't1', agentId: 'a1', content: 'Done!' });
|
expect(mockInvoke).toHaveBeenCalledWith('bttask_add_comment', { taskId: 't1', agentId: 'a1', content: 'Done!' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createTask invokes bttask_create with all fields', async () => {
|
it('createTask invokes bttask_create with all fields', async () => {
|
||||||
mockInvoke.mockResolvedValue('t-id');
|
mockInvoke.mockResolvedValue('t-id');
|
||||||
const result = await createTask('Fix bug', 'desc', 'high', 'g1', 'admin', 'a1');
|
const result = await createTask('Fix bug', 'desc', 'high', GroupId('g1'), AgentId('admin'), AgentId('a1'));
|
||||||
expect(result).toBe('t-id');
|
expect(result).toBe('t-id');
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
|
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
|
||||||
title: 'Fix bug',
|
title: 'Fix bug',
|
||||||
|
|
@ -123,7 +124,7 @@ describe('bttask-bridge', () => {
|
||||||
|
|
||||||
it('createTask invokes bttask_create without assignedTo', async () => {
|
it('createTask invokes bttask_create without assignedTo', async () => {
|
||||||
mockInvoke.mockResolvedValue('t-id');
|
mockInvoke.mockResolvedValue('t-id');
|
||||||
await createTask('Add tests', '', 'medium', 'g1', 'a1');
|
await createTask('Add tests', '', 'medium', GroupId('g1'), AgentId('a1'));
|
||||||
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
|
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
|
||||||
title: 'Add tests',
|
title: 'Add tests',
|
||||||
description: '',
|
description: '',
|
||||||
|
|
@ -144,7 +145,7 @@ describe('bttask-bridge', () => {
|
||||||
describe('error propagation', () => {
|
describe('error propagation', () => {
|
||||||
it('propagates invoke errors', async () => {
|
it('propagates invoke errors', async () => {
|
||||||
mockInvoke.mockRejectedValue(new Error('btmsg database not found'));
|
mockInvoke.mockRejectedValue(new Error('btmsg database not found'));
|
||||||
await expect(listTasks('g1')).rejects.toThrow('btmsg database not found');
|
await expect(listTasks(GroupId('g1'))).rejects.toThrow('btmsg database not found');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// bttask Bridge — Tauri IPC adapter for task board
|
// bttask Bridge — Tauri IPC adapter for task board
|
||||||
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import type { GroupId, AgentId } from '../types/ids';
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -8,9 +9,9 @@ export interface Task {
|
||||||
description: string;
|
description: string;
|
||||||
status: 'todo' | 'progress' | 'review' | 'done' | 'blocked';
|
status: 'todo' | 'progress' | 'review' | 'done' | 'blocked';
|
||||||
priority: 'low' | 'medium' | 'high' | 'critical';
|
priority: 'low' | 'medium' | 'high' | 'critical';
|
||||||
assignedTo: string | null;
|
assignedTo: AgentId | null;
|
||||||
createdBy: string;
|
createdBy: AgentId;
|
||||||
groupId: string;
|
groupId: GroupId;
|
||||||
parentTaskId: string | null;
|
parentTaskId: string | null;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|
@ -20,12 +21,12 @@ export interface Task {
|
||||||
export interface TaskComment {
|
export interface TaskComment {
|
||||||
id: string;
|
id: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
agentId: string;
|
agentId: AgentId;
|
||||||
content: string;
|
content: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listTasks(groupId: string): Promise<Task[]> {
|
export async function listTasks(groupId: GroupId): Promise<Task[]> {
|
||||||
return invoke<Task[]>('bttask_list', { groupId });
|
return invoke<Task[]>('bttask_list', { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +38,7 @@ export async function updateTaskStatus(taskId: string, status: string): Promise<
|
||||||
return invoke('bttask_update_status', { taskId, status });
|
return invoke('bttask_update_status', { taskId, status });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addTaskComment(taskId: string, agentId: string, content: string): Promise<string> {
|
export async function addTaskComment(taskId: string, agentId: AgentId, content: string): Promise<string> {
|
||||||
return invoke<string>('bttask_add_comment', { taskId, agentId, content });
|
return invoke<string>('bttask_add_comment', { taskId, agentId, content });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,9 +46,9 @@ export async function createTask(
|
||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
priority: string,
|
priority: string,
|
||||||
groupId: string,
|
groupId: GroupId,
|
||||||
createdBy: string,
|
createdBy: AgentId,
|
||||||
assignedTo?: string,
|
assignedTo?: AgentId,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return invoke<string>('bttask_create', { title, description, priority, groupId, createdBy, assignedTo });
|
return invoke<string>('bttask_create', { title, description, priority, groupId, createdBy, assignedTo });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import type { GroupsFile, ProjectConfig, GroupConfig } from '../types/groups';
|
import type { GroupsFile, ProjectConfig, GroupConfig } from '../types/groups';
|
||||||
|
import type { SessionId, ProjectId } from '../types/ids';
|
||||||
|
|
||||||
export type { GroupsFile, ProjectConfig, GroupConfig };
|
export type { GroupsFile, ProjectConfig, GroupConfig };
|
||||||
|
|
||||||
|
|
@ -11,8 +12,8 @@ export interface MdFileEntry {
|
||||||
|
|
||||||
export interface AgentMessageRecord {
|
export interface AgentMessageRecord {
|
||||||
id: number;
|
id: number;
|
||||||
session_id: string;
|
session_id: SessionId;
|
||||||
project_id: string;
|
project_id: ProjectId;
|
||||||
sdk_session_id: string | null;
|
sdk_session_id: string | null;
|
||||||
message_type: string;
|
message_type: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -21,8 +22,8 @@ export interface AgentMessageRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectAgentState {
|
export interface ProjectAgentState {
|
||||||
project_id: string;
|
project_id: ProjectId;
|
||||||
last_session_id: string;
|
last_session_id: SessionId;
|
||||||
sdk_session_id: string | null;
|
sdk_session_id: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
cost_usd: number;
|
cost_usd: number;
|
||||||
|
|
@ -51,8 +52,8 @@ export async function discoverMarkdownFiles(cwd: string): Promise<MdFileEntry[]>
|
||||||
// --- Agent message persistence ---
|
// --- Agent message persistence ---
|
||||||
|
|
||||||
export async function saveAgentMessages(
|
export async function saveAgentMessages(
|
||||||
sessionId: string,
|
sessionId: SessionId,
|
||||||
projectId: string,
|
projectId: ProjectId,
|
||||||
sdkSessionId: string | undefined,
|
sdkSessionId: string | undefined,
|
||||||
messages: AgentMessageRecord[],
|
messages: AgentMessageRecord[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -64,7 +65,7 @@ export async function saveAgentMessages(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAgentMessages(projectId: string): Promise<AgentMessageRecord[]> {
|
export async function loadAgentMessages(projectId: ProjectId): Promise<AgentMessageRecord[]> {
|
||||||
return invoke('agent_messages_load', { projectId });
|
return invoke('agent_messages_load', { projectId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ export async function saveProjectAgentState(state: ProjectAgentState): Promise<v
|
||||||
return invoke('project_agent_state_save', { state });
|
return invoke('project_agent_state_save', { state });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadProjectAgentState(projectId: string): Promise<ProjectAgentState | null> {
|
export async function loadProjectAgentState(projectId: ProjectId): Promise<ProjectAgentState | null> {
|
||||||
return invoke('project_agent_state_load', { projectId });
|
return invoke('project_agent_state_load', { projectId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,8 +83,8 @@ export async function loadProjectAgentState(projectId: string): Promise<ProjectA
|
||||||
|
|
||||||
export interface SessionMetric {
|
export interface SessionMetric {
|
||||||
id: number;
|
id: number;
|
||||||
project_id: string;
|
project_id: ProjectId;
|
||||||
session_id: string;
|
session_id: SessionId;
|
||||||
start_time: number;
|
start_time: number;
|
||||||
end_time: number;
|
end_time: number;
|
||||||
peak_tokens: number;
|
peak_tokens: number;
|
||||||
|
|
@ -99,7 +100,7 @@ export async function saveSessionMetric(metric: Omit<SessionMetric, 'id'>): Prom
|
||||||
return invoke('session_metric_save', { metric: { id: 0, ...metric } });
|
return invoke('session_metric_save', { metric: { id: 0, ...metric } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadSessionMetrics(projectId: string, limit = 20): Promise<SessionMetric[]> {
|
export async function loadSessionMetrics(projectId: ProjectId, limit = 20): Promise<SessionMetric[]> {
|
||||||
return invoke('session_metrics_load', { projectId, limit });
|
return invoke('session_metrics_load', { projectId, limit });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue