Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/renderer/src/lib/ipc-mock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import {
classifyBrokerEvent,
type AgentReleasedEvent,
type AgentSpawnedEvent,
type RelayInboundEvent
} from '@shared/schemas/broker-events'
import type {
AiHistEntry,
AiHistRecentOptions,
Expand Down Expand Up @@ -72,7 +78,7 @@ import type {
import { getTerminalRuntime } from '@/lib/terminal-runtime-registry'

type BrokerEventLike = Record<string, unknown> & {
kind?: string
kind: string
projectId?: string
name?: string
from?: string
Expand Down Expand Up @@ -642,6 +648,10 @@ function addReconciledMessage(event: BrokerEventLike): void {
function handleInjectedBrokerEvent(event: BrokerEventLike): void {
const projectId = event.projectId || state.activeId || defaultProject.id
const normalized: BrokerEventLike = { ...event, projectId }
const classification = classifyBrokerEvent(normalized)
if (classification.status === 'malformed') {
throw new Error(`ipc-mock: malformed broker event (kind=${classification.kind ?? 'none'}): ${classification.reason}`)
}
if (normalized.kind === 'agent_spawned' && normalized.name) {
upsertAgent({
name: normalized.name,
Expand Down Expand Up @@ -797,13 +807,14 @@ export const pearMock: PearAPI = {
const agent = upsertAgent({ ...input, projectId, runtime: 'mock', current_state: 'idle' })
handleInjectedBrokerEvent({
kind: 'agent_spawned',
projectId,
name: agent.name,
runtime: agent.runtime || 'mock',
cli: agent.cli,
model: agent.model,
projectId,
channels: agent.channels,
event_id: `${projectId}:agent:${agent.name}`
})
} satisfies AgentSpawnedEvent)
return { name: agent.name, runtime: agent.runtime || 'mock', cli: agent.cli }
},
listPersonas: async (): Promise<WorkforcePersona[]> => [],
Expand All @@ -828,12 +839,12 @@ export const pearMock: PearAPI = {
sendMessage: async (projectId: string | undefined, input: BrokerSendMessageInput) => {
handleInjectedBrokerEvent({
kind: 'relay_inbound',
projectId,
event_id: `${projectId || 'mock'}:human:${++seq}`,
from: input.from || 'human',
target: input.to,
body: input.text,
event_id: `${projectId || 'mock'}:human:${++seq}`
})
projectId
} satisfies RelayInboundEvent)
},
reconcileMessages: async (input: BrokerReconcileMessagesInput) =>
clone(state.messages.filter((message) => message.projectId === input.projectId)),
Expand All @@ -848,7 +859,7 @@ export const pearMock: PearAPI = {
subscribeAgentChannel: async () => undefined,
unsubscribeAgentChannel: async () => undefined,
releaseAgent: async (projectId: string | undefined, name: string) => {
handleInjectedBrokerEvent({ kind: 'agent_released', projectId, name, event_id: `${projectId || 'mock'}:released:${name}` })
handleInjectedBrokerEvent({ kind: 'agent_released', name, projectId, event_id: `${projectId || 'mock'}:released:${name}` } satisfies AgentReleasedEvent)
},
listAgents: async (projectId?: string) =>
clone(projectId ? state.agents.filter((agent) => agent.projectId === projectId) : state.agents),
Expand Down Expand Up @@ -1124,13 +1135,14 @@ export const pearMockHarness: PearMockHarness = {
const name = `${prefix}-${String(index + 1).padStart(4, '0')}`
events.push({
kind: 'agent_spawned',
projectId,
name,
runtime: 'mock',
cli: index % 2 === 0 ? 'codex' : 'claude',
projectId,
channels: [channel],
event_id: `${projectId}:agent_spawned:${name}`,
seq: ++seq
})
} satisfies AgentSpawnedEvent)
}
pearMockHarness.injectBrokerEvents(events)
},
Expand Down
34 changes: 34 additions & 0 deletions src/shared/schemas/broker-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,40 @@ export const BrokerEventSchema = z.discriminatedUnion('kind', [

export type ValidatedBrokerEvent = z.infer<typeof BrokerEventSchema>

/** Per-variant inferred types — use with `satisfies` to catch payload drift at compile time. */
export type AgentSpawnedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_spawned' }>
export type AgentReleasedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_released' }>
export type AgentExitEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_exit' }>
export type AgentExitedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_exited' }>
export type AgentContextLowEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_context_low' }>
export type RelayInboundEvent = Extract<ValidatedBrokerEvent, { kind: 'relay_inbound' }>
export type WorkerStreamEvent = Extract<ValidatedBrokerEvent, { kind: 'worker_stream' }>
export type DeliveryRetryEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_retry' }>
export type DeliveryDroppedEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_dropped' }>
export type DeliveryQueuedEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_queued' }>
export type AgentPendingDrainedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_pending_drained' }>
export type AgentInboundDeliveryModeChangedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_inbound_delivery_mode_changed' }>
export type DeliveryInjectedEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_injected' }>
export type DeliveryVerifiedEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_verified' }>
export type DeliveryFailedEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_failed' }>
export type MessageDeliveryConfirmedEvent = Extract<ValidatedBrokerEvent, { kind: 'message_delivery_confirmed' }>
export type MessageDeliveryFailedEvent = Extract<ValidatedBrokerEvent, { kind: 'message_delivery_failed' }>
export type DeliveryActiveEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_active' }>
export type DeliveryAckEvent = Extract<ValidatedBrokerEvent, { kind: 'delivery_ack' }>
export type ChannelSubscribedEvent = Extract<ValidatedBrokerEvent, { kind: 'channel_subscribed' }>
export type ChannelUnsubscribedEvent = Extract<ValidatedBrokerEvent, { kind: 'channel_unsubscribed' }>
export type WorkerReadyEvent = Extract<ValidatedBrokerEvent, { kind: 'worker_ready' }>
export type WorkerErrorEvent = Extract<ValidatedBrokerEvent, { kind: 'worker_error' }>
export type RelaycastPublishedEvent = Extract<ValidatedBrokerEvent, { kind: 'relaycast_published' }>
export type RelaycastPublishFailedEvent = Extract<ValidatedBrokerEvent, { kind: 'relaycast_publish_failed' }>
export type AclDeniedEvent = Extract<ValidatedBrokerEvent, { kind: 'acl_denied' }>
export type AgentIdleEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_idle' }>
export type AgentResultEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_result' }>
export type AgentBlockedOnSendEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_blocked_on_send' }>
export type AgentRestartingEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_restarting' }>
export type AgentRestartedEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_restarted' }>
export type AgentPermanentlyDeadEvent = Extract<ValidatedBrokerEvent, { kind: 'agent_permanently_dead' }>

/**
* The shape every forwarded broker event satisfies: an object carrying a
* string `kind`. Both validated and (kind-only-validated) unknown events are
Expand Down
Loading