refactor(types): add GroupId and AgentId branded types to ids.ts
Extend the branded type system with two new domain types for btmsg/bttask agent and group identifiers. Apply to groups.ts interfaces including agentToProject() domain crossing cast.
This commit is contained in:
parent
46df7949a7
commit
f928abd6ce
3 changed files with 70 additions and 8 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import type { ProviderId } from '../providers/types';
|
||||
import type { AnchorBudgetScale } from './anchors';
|
||||
import type { ProjectId, GroupId, AgentId } from './ids';
|
||||
|
||||
export interface ProjectConfig {
|
||||
id: string;
|
||||
id: ProjectId;
|
||||
name: string;
|
||||
identifier: string;
|
||||
description: string;
|
||||
|
|
@ -35,8 +36,9 @@ export const AGENT_ROLE_ICONS: Record<string, string> = {
|
|||
|
||||
/** Convert a GroupAgentConfig to a ProjectConfig for unified rendering */
|
||||
export function agentToProject(agent: GroupAgentConfig, groupCwd: string): ProjectConfig {
|
||||
// Agent IDs serve as project IDs in the workspace (agents render as project boxes)
|
||||
return {
|
||||
id: agent.id,
|
||||
id: agent.id as unknown as ProjectId,
|
||||
name: agent.name,
|
||||
identifier: agent.role,
|
||||
description: `${agent.role.charAt(0).toUpperCase() + agent.role.slice(1)} agent`,
|
||||
|
|
@ -58,7 +60,7 @@ export type GroupAgentStatus = 'active' | 'sleeping' | 'stopped';
|
|||
|
||||
/** Group-level agent configuration */
|
||||
export interface GroupAgentConfig {
|
||||
id: string;
|
||||
id: AgentId;
|
||||
name: string;
|
||||
role: GroupAgentRole;
|
||||
model?: string;
|
||||
|
|
@ -70,7 +72,7 @@ export interface GroupAgentConfig {
|
|||
}
|
||||
|
||||
export interface GroupConfig {
|
||||
id: string;
|
||||
id: GroupId;
|
||||
name: string;
|
||||
projects: ProjectConfig[];
|
||||
/** Group-level orchestration agents (Tier 1) */
|
||||
|
|
@ -80,7 +82,7 @@ export interface GroupConfig {
|
|||
export interface GroupsFile {
|
||||
version: number;
|
||||
groups: GroupConfig[];
|
||||
activeGroupId: string;
|
||||
activeGroupId: GroupId;
|
||||
}
|
||||
|
||||
/** Derive a project identifier from a name: lowercase, spaces to dashes */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { SessionId, ProjectId, type SessionId as SessionIdType, type ProjectId as ProjectIdType } from './ids';
|
||||
import {
|
||||
SessionId, ProjectId, GroupId, AgentId,
|
||||
type SessionId as SessionIdType,
|
||||
type ProjectId as ProjectIdType,
|
||||
type GroupId as GroupIdType,
|
||||
type AgentId as AgentIdType,
|
||||
} from './ids';
|
||||
|
||||
describe('branded types', () => {
|
||||
describe('SessionId', () => {
|
||||
|
|
@ -41,12 +47,50 @@ describe('branded types', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('GroupId', () => {
|
||||
it('creates a GroupId from a string', () => {
|
||||
const id = GroupId('grp-abc');
|
||||
expect(id).toBe('grp-abc');
|
||||
});
|
||||
|
||||
it('is usable as a Map key', () => {
|
||||
const map = new Map<GroupIdType, string>();
|
||||
const id = GroupId('grp-1');
|
||||
map.set(id, 'test-group');
|
||||
expect(map.get(id)).toBe('test-group');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AgentId', () => {
|
||||
it('creates an AgentId from a string', () => {
|
||||
const id = AgentId('agent-manager');
|
||||
expect(id).toBe('agent-manager');
|
||||
});
|
||||
|
||||
it('is usable as a Map key', () => {
|
||||
const map = new Map<AgentIdType, number>();
|
||||
const id = AgentId('a1');
|
||||
map.set(id, 99);
|
||||
expect(map.get(id)).toBe(99);
|
||||
});
|
||||
|
||||
it('equality works between two AgentIds with same value', () => {
|
||||
const a = AgentId('a1');
|
||||
const b = AgentId('a1');
|
||||
expect(a === b).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type safety (compile-time)', () => {
|
||||
it('both types are strings at runtime', () => {
|
||||
it('all four types are strings at runtime', () => {
|
||||
const sid = SessionId('s1');
|
||||
const pid = ProjectId('p1');
|
||||
const gid = GroupId('g1');
|
||||
const aid = AgentId('a1');
|
||||
expect(typeof sid).toBe('string');
|
||||
expect(typeof pid).toBe('string');
|
||||
expect(typeof gid).toBe('string');
|
||||
expect(typeof aid).toBe('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Branded types for domain identifiers — prevents accidental swapping of sessionId/projectId
|
||||
// Branded types for domain identifiers — prevents accidental swapping of IDs across domains.
|
||||
// These are compile-time only; at runtime they are plain strings.
|
||||
|
||||
/** Unique identifier for an agent session */
|
||||
|
|
@ -7,6 +7,12 @@ export type SessionId = string & { readonly __brand: 'SessionId' };
|
|||
/** Unique identifier for a project */
|
||||
export type ProjectId = string & { readonly __brand: 'ProjectId' };
|
||||
|
||||
/** Unique identifier for a project group */
|
||||
export type GroupId = string & { readonly __brand: 'GroupId' };
|
||||
|
||||
/** Unique identifier for an agent in the btmsg/bttask system */
|
||||
export type AgentId = string & { readonly __brand: 'AgentId' };
|
||||
|
||||
/** Create a SessionId from a raw string */
|
||||
export function SessionId(value: string): SessionId {
|
||||
return value as SessionId;
|
||||
|
|
@ -16,3 +22,13 @@ export function SessionId(value: string): SessionId {
|
|||
export function ProjectId(value: string): ProjectId {
|
||||
return value as ProjectId;
|
||||
}
|
||||
|
||||
/** Create a GroupId from a raw string */
|
||||
export function GroupId(value: string): GroupId {
|
||||
return value as GroupId;
|
||||
}
|
||||
|
||||
/** Create an AgentId from a raw string */
|
||||
export function AgentId(value: string): AgentId {
|
||||
return value as AgentId;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue