Files
Codewalkers/src/events/types.ts
Lukas May 270a5cb21d feat: Add Docker-based preview deployments for phase review
Preview deployments let reviewers spin up the app at a specific branch
in local Docker containers, accessible through a single Caddy reverse
proxy port. Docker is the source of truth — no database table needed.

New module: src/preview/ with config discovery (.cw-preview.yml →
compose → Dockerfile fallback), compose generation, Docker CLI wrapper,
health checking, and port allocation (9100-9200 range).
2026-02-10 13:24:56 +01:00

619 lines
12 KiB
TypeScript

/**
* Event Bus Types
*
* Port interface for the event bus - the backbone of the hexagonal architecture.
* EventBus is the PORT. Implementations (EventEmitterBus, RabbitMQ, etc.) are ADAPTERS.
*/
/**
* Base interface for all domain events.
* Every event in the system extends this.
*/
export interface DomainEvent {
/** Event type identifier (e.g., 'process:spawned', 'server:started') */
type: string;
/** When the event occurred */
timestamp: Date;
/** Event-specific data */
payload: unknown;
}
/**
* Event Bus Port Interface
*
* All modules communicate through this interface.
* Can be swapped for external systems (RabbitMQ, WebSocket forwarding) later.
*/
// =============================================================================
// Domain Event Types - Typed payloads for each event
// =============================================================================
/**
* Process Events
*/
export interface ProcessSpawnedEvent extends DomainEvent {
type: 'process:spawned';
payload: {
processId: string;
pid: number;
command: string;
};
}
export interface ProcessStoppedEvent extends DomainEvent {
type: 'process:stopped';
payload: {
processId: string;
pid: number;
exitCode: number | null;
};
}
export interface ProcessCrashedEvent extends DomainEvent {
type: 'process:crashed';
payload: {
processId: string;
pid: number;
exitCode: number | null;
signal: string | null;
};
}
/**
* Server Events
*/
export interface ServerStartedEvent extends DomainEvent {
type: 'server:started';
payload: {
port: number;
host: string;
pid: number;
};
}
export interface ServerStoppedEvent extends DomainEvent {
type: 'server:stopped';
payload: {
uptime: number;
};
}
/**
* Log Events
*/
export interface LogEntryEvent extends DomainEvent {
type: 'log:entry';
payload: {
processId: string;
stream: 'stdout' | 'stderr';
data: string;
};
}
/**
* Git Worktree Events
*/
export interface WorktreeCreatedEvent extends DomainEvent {
type: 'worktree:created';
payload: {
worktreeId: string;
branch: string;
path: string;
};
}
export interface WorktreeRemovedEvent extends DomainEvent {
type: 'worktree:removed';
payload: {
worktreeId: string;
branch: string;
};
}
export interface WorktreeMergedEvent extends DomainEvent {
type: 'worktree:merged';
payload: {
worktreeId: string;
sourceBranch: string;
targetBranch: string;
};
}
export interface WorktreeConflictEvent extends DomainEvent {
type: 'worktree:conflict';
payload: {
worktreeId: string;
sourceBranch: string;
targetBranch: string;
conflictingFiles: string[];
};
}
/**
* Agent Events
*/
export interface AgentSpawnedEvent extends DomainEvent {
type: 'agent:spawned';
payload: {
agentId: string;
name: string;
taskId: string | null;
worktreeId: string;
provider: string;
};
}
export interface AgentStoppedEvent extends DomainEvent {
type: 'agent:stopped';
payload: {
agentId: string;
name: string;
taskId: string | null;
reason:
| 'user_requested'
| 'task_complete'
| 'error'
| 'waiting_for_input'
| 'context_complete'
| 'plan_complete'
| 'detail_complete'
| 'refine_complete';
};
}
export interface AgentCrashedEvent extends DomainEvent {
type: 'agent:crashed';
payload: {
agentId: string;
name: string;
taskId: string | null;
error: string;
};
}
export interface AgentResumedEvent extends DomainEvent {
type: 'agent:resumed';
payload: {
agentId: string;
name: string;
taskId: string | null;
sessionId: string;
};
}
export interface AgentAccountSwitchedEvent extends DomainEvent {
type: 'agent:account_switched';
payload: {
agentId: string;
name: string;
previousAccountId: string;
newAccountId: string;
reason: 'account_exhausted';
};
}
export interface AgentWaitingEvent extends DomainEvent {
type: 'agent:waiting';
payload: {
agentId: string;
name: string;
taskId: string | null;
sessionId: string;
questions: Array<{
id: string;
question: string;
options?: Array<{ label: string; description?: string }>;
multiSelect?: boolean;
}>;
};
}
export interface AgentDeletedEvent extends DomainEvent {
type: 'agent:deleted';
payload: {
agentId: string;
name: string;
};
}
export interface AgentOutputEvent extends DomainEvent {
type: 'agent:output';
payload: {
agentId: string;
stream: 'stdout' | 'stderr';
data: string;
};
}
/**
* Task Dispatch Events
*/
export interface TaskQueuedEvent extends DomainEvent {
type: 'task:queued';
payload: {
taskId: string;
priority: string;
dependsOn: string[];
};
}
export interface TaskDispatchedEvent extends DomainEvent {
type: 'task:dispatched';
payload: {
taskId: string;
agentId: string;
agentName: string;
};
}
export interface TaskCompletedEvent extends DomainEvent {
type: 'task:completed';
payload: {
taskId: string;
agentId: string;
success: boolean;
message: string;
};
}
export interface TaskBlockedEvent extends DomainEvent {
type: 'task:blocked';
payload: {
taskId: string;
reason: string;
blockedBy?: string[];
};
}
export interface TaskPendingApprovalEvent extends DomainEvent {
type: 'task:pending_approval';
payload: {
taskId: string;
agentId: string;
category: string;
name: string;
};
}
/**
* Phase Events
*/
export interface PhaseQueuedEvent extends DomainEvent {
type: 'phase:queued';
payload: {
phaseId: string;
initiativeId: string;
dependsOn: string[];
};
}
export interface PhaseStartedEvent extends DomainEvent {
type: 'phase:started';
payload: {
phaseId: string;
initiativeId: string;
};
}
export interface PhaseCompletedEvent extends DomainEvent {
type: 'phase:completed';
payload: {
phaseId: string;
initiativeId: string;
success: boolean;
message?: string;
};
}
export interface PhaseBlockedEvent extends DomainEvent {
type: 'phase:blocked';
payload: {
phaseId: string;
reason: string;
};
}
export interface PhasePendingReviewEvent extends DomainEvent {
type: 'phase:pending_review';
payload: {
phaseId: string;
initiativeId: string;
};
}
export interface PhaseMergedEvent extends DomainEvent {
type: 'phase:merged';
payload: {
phaseId: string;
initiativeId: string;
sourceBranch: string;
targetBranch: string;
};
}
export interface TaskMergedEvent extends DomainEvent {
type: 'task:merged';
payload: {
taskId: string;
phaseId: string;
sourceBranch: string;
targetBranch: string;
};
}
/**
* Merge Coordination Events
*/
export interface MergeQueuedEvent extends DomainEvent {
type: 'merge:queued';
payload: {
taskId: string;
agentId: string;
worktreeId: string;
priority: 'low' | 'medium' | 'high';
};
}
export interface MergeStartedEvent extends DomainEvent {
type: 'merge:started';
payload: {
taskId: string;
agentId: string;
worktreeId: string;
targetBranch: string;
};
}
export interface MergeCompletedEvent extends DomainEvent {
type: 'merge:completed';
payload: {
taskId: string;
agentId: string;
worktreeId: string;
targetBranch: string;
};
}
export interface MergeConflictedEvent extends DomainEvent {
type: 'merge:conflicted';
payload: {
taskId: string;
agentId: string;
worktreeId: string;
targetBranch: string;
conflictingFiles: string[];
};
}
/**
* Page Events
*/
export interface PageCreatedEvent extends DomainEvent {
type: 'page:created';
payload: {
pageId: string;
initiativeId: string;
title: string;
};
}
export interface PageUpdatedEvent extends DomainEvent {
type: 'page:updated';
payload: {
pageId: string;
initiativeId: string;
title?: string;
};
}
export interface PageDeletedEvent extends DomainEvent {
type: 'page:deleted';
payload: {
pageId: string;
initiativeId: string;
};
}
/**
* Change Set Events
*/
export interface ChangeSetCreatedEvent extends DomainEvent {
type: 'changeset:created';
payload: {
changeSetId: string;
initiativeId: string;
agentId: string;
mode: string;
entryCount: number;
};
}
export interface ChangeSetRevertedEvent extends DomainEvent {
type: 'changeset:reverted';
payload: {
changeSetId: string;
initiativeId: string;
};
}
/**
* Preview Events
*/
export interface PreviewBuildingEvent extends DomainEvent {
type: 'preview:building';
payload: {
previewId: string;
initiativeId: string;
branch: string;
port: number;
};
}
export interface PreviewReadyEvent extends DomainEvent {
type: 'preview:ready';
payload: {
previewId: string;
initiativeId: string;
branch: string;
port: number;
url: string;
};
}
export interface PreviewStoppedEvent extends DomainEvent {
type: 'preview:stopped';
payload: {
previewId: string;
initiativeId: string;
};
}
export interface PreviewFailedEvent extends DomainEvent {
type: 'preview:failed';
payload: {
previewId: string;
initiativeId: string;
error: string;
};
}
/**
* Account Credential Events
*/
export interface AccountCredentialsRefreshedEvent extends DomainEvent {
type: 'account:credentials_refreshed';
payload: {
accountId: string | null;
expiresAt: number;
previousExpiresAt: number | null;
};
}
export interface AccountCredentialsExpiredEvent extends DomainEvent {
type: 'account:credentials_expired';
payload: {
accountId: string | null;
reason: 'token_expired' | 'refresh_failed' | 'credentials_missing';
error: string | null;
};
}
export interface AccountCredentialsValidatedEvent extends DomainEvent {
type: 'account:credentials_validated';
payload: {
accountId: string | null;
valid: boolean;
expiresAt: number | null;
wasRefreshed: boolean;
};
}
/**
* Union of all domain events - enables type-safe event handling
*/
export type DomainEventMap =
| ProcessSpawnedEvent
| ProcessStoppedEvent
| ProcessCrashedEvent
| ServerStartedEvent
| ServerStoppedEvent
| LogEntryEvent
| WorktreeCreatedEvent
| WorktreeRemovedEvent
| WorktreeMergedEvent
| WorktreeConflictEvent
| AgentSpawnedEvent
| AgentStoppedEvent
| AgentCrashedEvent
| AgentResumedEvent
| AgentAccountSwitchedEvent
| AgentDeletedEvent
| AgentWaitingEvent
| AgentOutputEvent
| TaskQueuedEvent
| TaskDispatchedEvent
| TaskCompletedEvent
| TaskBlockedEvent
| TaskPendingApprovalEvent
| PhaseQueuedEvent
| PhaseStartedEvent
| PhaseCompletedEvent
| PhaseBlockedEvent
| PhasePendingReviewEvent
| PhaseMergedEvent
| TaskMergedEvent
| MergeQueuedEvent
| MergeStartedEvent
| MergeCompletedEvent
| MergeConflictedEvent
| PageCreatedEvent
| PageUpdatedEvent
| PageDeletedEvent
| ChangeSetCreatedEvent
| ChangeSetRevertedEvent
| AccountCredentialsRefreshedEvent
| AccountCredentialsExpiredEvent
| AccountCredentialsValidatedEvent
| PreviewBuildingEvent
| PreviewReadyEvent
| PreviewStoppedEvent
| PreviewFailedEvent;
/**
* Event type literal union for type checking
*/
export type DomainEventType = DomainEventMap['type'];
// =============================================================================
// Event Bus Port Interface
// =============================================================================
/**
* Event Bus Port Interface
*
* All modules communicate through this interface.
* Can be swapped for external systems (RabbitMQ, WebSocket forwarding) later.
*/
export interface EventBus {
/**
* Emit an event to all subscribed handlers
*/
emit<T extends DomainEvent>(event: T): void;
/**
* Subscribe to events of a specific type
*/
on<T extends DomainEvent>(
eventType: T['type'],
handler: (event: T) => void
): void;
/**
* Unsubscribe from events of a specific type
*/
off<T extends DomainEvent>(
eventType: T['type'],
handler: (event: T) => void
): void;
/**
* Subscribe to a single occurrence of an event type
*/
once<T extends DomainEvent>(
eventType: T['type'],
handler: (event: T) => void
): void;
}