chore: add ion package, codesight wiki, work plans, ascli config
New @boocode/ion package (v0.0.1) for inference optimization network. .codesight/ wiki artifacts for codebase documentation. .omo/ work plans for openspec cleanup and enhanced file panel.
This commit is contained in:
214
packages/ion/src/engine/event-emitter.ts
Normal file
214
packages/ion/src/engine/event-emitter.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Typed event emitter for the Ion workflow engine.
|
||||
*
|
||||
* Provides a singleton event bus for workflow lifecycle events.
|
||||
* Supports both global and run-scoped subscriptions.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type WorkflowEventType =
|
||||
| 'workflow_started'
|
||||
| 'workflow_completed'
|
||||
| 'workflow_failed'
|
||||
| 'workflow_cancelled'
|
||||
| 'node_started'
|
||||
| 'node_completed'
|
||||
| 'node_failed'
|
||||
| 'node_skipped'
|
||||
| 'loop_iteration_started'
|
||||
| 'loop_iteration_completed'
|
||||
| 'approval_pending';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event shapes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface WorkflowEventBase {
|
||||
/** Discriminator for the event type. */
|
||||
type: WorkflowEventType;
|
||||
/** The workflow run id. */
|
||||
runId: string;
|
||||
/** The node id (when applicable). */
|
||||
nodeId?: string;
|
||||
/** The workflow name. */
|
||||
workflowName?: string;
|
||||
/** Error message (for failure events). */
|
||||
error?: string;
|
||||
/** Human-readable step name. */
|
||||
stepName?: string;
|
||||
/** Arbitrary metadata attached to the event. */
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Specific shapes for strongly-typed event handling. */
|
||||
export interface WorkflowStartedEvent extends WorkflowEventBase {
|
||||
type: 'workflow_started';
|
||||
workflowName: string;
|
||||
}
|
||||
|
||||
export interface WorkflowCompletedEvent extends WorkflowEventBase {
|
||||
type: 'workflow_completed';
|
||||
workflowName: string;
|
||||
}
|
||||
|
||||
export interface WorkflowFailedEvent extends WorkflowEventBase {
|
||||
type: 'workflow_failed';
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface WorkflowCancelledEvent extends WorkflowEventBase {
|
||||
type: 'workflow_cancelled';
|
||||
}
|
||||
|
||||
export interface NodeStartedEvent extends WorkflowEventBase {
|
||||
type: 'node_started';
|
||||
nodeId: string;
|
||||
stepName?: string;
|
||||
}
|
||||
|
||||
export interface NodeCompletedEvent extends WorkflowEventBase {
|
||||
type: 'node_completed';
|
||||
nodeId: string;
|
||||
stepName?: string;
|
||||
}
|
||||
|
||||
export interface NodeFailedEvent extends WorkflowEventBase {
|
||||
type: 'node_failed';
|
||||
nodeId: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface NodeSkippedEvent extends WorkflowEventBase {
|
||||
type: 'node_skipped';
|
||||
nodeId: string;
|
||||
stepName?: string;
|
||||
}
|
||||
|
||||
export interface LoopIterationStartedEvent extends WorkflowEventBase {
|
||||
type: 'loop_iteration_started';
|
||||
nodeId: string;
|
||||
metadata?: { iteration: number };
|
||||
}
|
||||
|
||||
export interface LoopIterationCompletedEvent extends WorkflowEventBase {
|
||||
type: 'loop_iteration_completed';
|
||||
nodeId: string;
|
||||
metadata?: { iteration: number };
|
||||
}
|
||||
|
||||
export interface ApprovalPendingEvent extends WorkflowEventBase {
|
||||
type: 'approval_pending';
|
||||
nodeId: string;
|
||||
metadata?: { approver?: string; reason?: string };
|
||||
}
|
||||
|
||||
export type WorkflowEvent =
|
||||
| WorkflowStartedEvent
|
||||
| WorkflowCompletedEvent
|
||||
| WorkflowFailedEvent
|
||||
| WorkflowCancelledEvent
|
||||
| NodeStartedEvent
|
||||
| NodeCompletedEvent
|
||||
| NodeFailedEvent
|
||||
| NodeSkippedEvent
|
||||
| LoopIterationStartedEvent
|
||||
| LoopIterationCompletedEvent
|
||||
| ApprovalPendingEvent;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event handler type
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type WorkflowEventHandler = (event: WorkflowEvent) => void;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WorkflowEventEmitter — singleton event bus
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export class WorkflowEventEmitter {
|
||||
private listeners: Set<WorkflowEventHandler> = new Set();
|
||||
private runListeners: Map<string, Set<WorkflowEventHandler>> = new Map();
|
||||
|
||||
/** Subscribe to all workflow events. */
|
||||
subscribe(handler: WorkflowEventHandler): () => void {
|
||||
this.listeners.add(handler);
|
||||
return () => {
|
||||
this.listeners.delete(handler);
|
||||
};
|
||||
}
|
||||
|
||||
/** Unsubscribe a handler from all events. */
|
||||
unsubscribe(handler: WorkflowEventHandler): void {
|
||||
this.listeners.delete(handler);
|
||||
}
|
||||
|
||||
/** Emit an event to all subscribers (global + run-scoped). */
|
||||
emit(event: WorkflowEvent): void {
|
||||
for (const handler of this.listeners) {
|
||||
try {
|
||||
handler(event);
|
||||
} catch {
|
||||
// Swallow handler errors to prevent cascading failures.
|
||||
}
|
||||
}
|
||||
|
||||
// Also notify run-scoped listeners.
|
||||
const runHandlers = this.runListeners.get(event.runId);
|
||||
if (runHandlers) {
|
||||
for (const handler of runHandlers) {
|
||||
try {
|
||||
handler(event);
|
||||
} catch {
|
||||
// Swallow handler errors to prevent cascading failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove all global listeners and run-scoped listeners. */
|
||||
clear(): void {
|
||||
this.listeners.clear();
|
||||
this.runListeners.clear();
|
||||
}
|
||||
|
||||
// -- Run-scoped subscriptions ------------------------------------------------
|
||||
|
||||
/** Register a run-scoped event listener. */
|
||||
registerRun(runId: string, handler: WorkflowEventHandler): () => void {
|
||||
let handlers = this.runListeners.get(runId);
|
||||
if (!handlers) {
|
||||
handlers = new Set();
|
||||
this.runListeners.set(runId, handlers);
|
||||
}
|
||||
handlers.add(handler);
|
||||
|
||||
return () => {
|
||||
handlers!.delete(handler);
|
||||
if (handlers!.size === 0) {
|
||||
this.runListeners.delete(runId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Unregister all listeners for a specific run. */
|
||||
unregisterRun(runId: string): void {
|
||||
this.runListeners.delete(runId);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Singleton factory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let instance: WorkflowEventEmitter | undefined;
|
||||
|
||||
/** Get the singleton WorkflowEventEmitter instance. */
|
||||
export function getWorkflowEventEmitter(): WorkflowEventEmitter {
|
||||
if (!instance) {
|
||||
instance = new WorkflowEventEmitter();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
Reference in New Issue
Block a user