FABRIC/src/types.ts
jedarden f307524b4d feat(analytics): add worker-to-worker comparison mode
Add side-by-side worker comparison analytics to the TUI analytics panel.
Users can now press 'c' to enter comparison mode and view detailed metrics
comparing two workers across performance, error, cost, and efficiency dimensions.

- Add WorkerComparison type with differences, percent differences, and winner per metric
- Add compareWorkers() method to WorkerAnalytics class
- Extend WorkerAnalyticsPanel with comparison view mode
- Add renderComparison() method with formatted comparison rows
- Add keyboard bindings: [c] toggle comparison, [↑/↓] cycle workers, [←/→] swap selection

Related to docs/plan.md Worker Comparison Analytics section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 14:05:00 -04:00

2896 lines
63 KiB
TypeScript

/**
* FABRIC Type Definitions
*
* Core types for NEEDLE log parsing and worker state management.
*/
/**
* Schema version for the NEEDLE event wire format.
* Bumped when the canonical NeedleEvent shape changes.
* Both NEEDLE and FABRIC must agree on this version.
*/
export const NEEDLE_EVENT_SCHEMA_VERSION = 1;
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export type WorkerStatus = 'active' | 'idle' | 'error';
/**
* NEEDLE worker state machine — first-class states emitted by
* worker.state_transition events. These are the canonical states
* that replace the coarse WorkerStatus in the UI.
*
* BOOTING → SELECTING → CLAIMING → WORKING → CLOSING → STOPPED
*/
export type NeedleState =
| 'BOOTING'
| 'SELECTING'
| 'CLAIMING'
| 'WORKING'
| 'CLOSING'
| 'STOPPED';
/**
* All valid state transitions in the NEEDLE worker state machine.
* A worker in BOOTING can only go to SELECTING, etc.
*/
export const VALID_TRANSITIONS: Record<NeedleState, NeedleState[]> = {
BOOTING: ['SELECTING'],
SELECTING: ['CLAIMING', 'STOPPED'],
CLAIMING: ['WORKING', 'SELECTING'],
WORKING: ['CLOSING', 'SELECTING'],
CLOSING: ['SELECTING', 'STOPPED'],
STOPPED: ['BOOTING'],
};
/**
* Map a NeedleState to the coarser WorkerStatus for backward compatibility.
*/
export function needleStateToStatus(state: NeedleState): WorkerStatus {
switch (state) {
case 'BOOTING':
case 'SELECTING':
case 'CLAIMING':
case 'WORKING':
return 'active';
case 'CLOSING':
return 'active';
case 'STOPPED':
return 'idle';
}
}
/**
* All event types emitted by NEEDLE's telemetry pipeline.
* Format: category.action — matches NEEDLE's EventKind::event_type() mapping.
*
* Note: This is a non-exhaustive list of known event types for documentation.
* The parser accepts any string for event_type to forward-compat with new NEEDLE versions.
*/
export type NeedleEventType =
// Worker lifecycle
| 'worker.booting'
| 'worker.started'
| 'worker.stopped'
| 'worker.errored'
| 'worker.exhausted'
| 'worker.idle'
| 'worker.idle_sleep_completed'
| 'worker.idle_sleep_entered'
| 'worker.state_transition'
| 'worker.queue_empty'
| 'worker.boot.timeout'
| 'worker.handling.timeout'
| 'worker.upgrade.detected'
| 'worker.upgrade.completed'
// Initialization
| 'init.step.started'
| 'init.step.completed'
// Strand lifecycle
| 'strand.evaluated'
| 'strand.skipped'
// Bead claim lifecycle
| 'bead.claim.attempted'
| 'bead.claim.succeeded'
| 'bead.claim.race_lost'
| 'bead.claim.race_lost_skipped'
| 'bead.claim.failed'
// Bead lifecycle
| 'bead.released'
| 'bead.release.failed'
| 'bead.completed'
| 'bead.orphaned'
// Bead mitosis
| 'bead.mitosis.evaluated'
| 'bead.mitosis.split'
| 'bead.mitosis.skipped'
// Bead unravel (alternatives)
| 'bead.unravel.analyzed'
| 'bead.unravel.skipped'
// Agent lifecycle
| 'agent.dispatched'
| 'agent.completed'
| 'agent.transform.spawned'
| 'agent.transform.exited'
| 'agent.transform.skipped'
// Build lifecycle
| 'build.timeout'
| 'build.heartbeat'
// Outcome handling
| 'outcome.classified'
| 'outcome.handled'
// Heartbeat & peer detection
| 'heartbeat.emitted'
| 'peer.stale'
| 'peer.crashed'
// Health checks
| 'health.check'
// Mend (maintenance cycle)
| 'mend.orphaned_lock_removed'
| 'mend.dependency_cleaned'
| 'mend.db_repaired'
| 'mend.db_rebuilt'
| 'mend.cycle_summary'
| 'mend.trace_cleanup'
| 'mend.learning_cleanup'
| 'mend.idle_worker_flagged'
| 'mend.worker_deregistered'
| 'mend.orphaned_heartbeat_removed'
| 'mend.dependency_removed'
| 'mend.bead_release_failed'
| 'mend.dependency_cleanup_failed'
| 'mend.lock_remove_failed'
| 'mend.rate_limit_cleaned'
| 'mend.rate_limit_provider_removed'
| 'mend.rate_limit_provider_reset'
| 'mend.zero_activity_log_cleaned'
// Effort & budget
| 'effort.recorded'
| 'budget.warning'
| 'budget.stop'
// Rate limiting
| 'rate_limit.wait'
| 'rate_limit.allowed'
// Verification
| 'verification.failed'
| 'verification.passed'
// Reflect (drift detection & decision mining)
| 'reflect.started'
| 'reflect.consolidated'
| 'reflect.skipped'
| 'reflect.transcripts_read'
| 'reflect.drift_detected'
| 'reflect.drift_promoted'
| 'reflect.decision_extracted'
| 'reflect.adr_created'
| 'reflect.learning_promoted'
| 'reflect.learning_deduplicated'
| 'reflect.claudemd_written'
// Drift detection
| 'drift.started'
| 'drift.completed'
| 'drift.skipped'
| 'drift.report_written'
// Decision detection
| 'decision.started'
| 'decision.completed'
| 'decision.skipped'
// Pulse (health monitoring)
| 'pulse.scanner_started'
| 'pulse.scanner_completed'
| 'pulse.scanner_failed'
| 'pulse.bead_created'
| 'pulse.skipped'
// Rollback
| 'rollback.completed'
// Canary deployment
| 'canary.started'
| 'canary.suite_completed'
| 'canary.promoted'
| 'canary.rejected'
// Transform lifecycle
| 'transform.started'
| 'transform.completed'
| 'transform.failed'
| 'transform.skipped'
// Telemetry internal
| 'telemetry.sink_error'
| 'telemetry.otlp.dropped'
| 'telemetry.otlp.shutdown_timeout';
// ============================================
// Canonical NeedleEvent (wire schema)
// ============================================
/**
* Canonical event shape emitted by NEEDLE over JSONL and OTLP.
* This is the contract between NEEDLE and FABRIC — versioned via NEEDLE_EVENT_SCHEMA_VERSION.
*
* Ordering: sort by (worker_id, sequence), NOT by timestamp.
* Wall clocks skew across hosts; sequence is the worker's monotonic counter.
*/
export interface NeedleEvent {
/** RFC3339 timestamp — display only, NOT authoritative for ordering */
timestamp: string;
/** Event taxonomy string, e.g. "worker.started", "bead.claimed" */
event_type: NeedleEventType | string;
/** Worker identifier (e.g. "tcb-alpha") */
worker_id: string;
/** Session identifier grouping a worker's lifetime events */
session_id: string;
/** Per-worker monotonic counter — authoritative for ordering */
sequence: number;
/** Present when event pertains to a specific bead */
bead_id?: string;
/** Event-specific payload */
data: Record<string, unknown>;
}
// ============================================
// Conversation Event Types
// ============================================
/**
* Role in a conversation
*/
export type ConversationRole = 'system' | 'user' | 'assistant' | 'tool';
/**
* Type of conversation event
*/
export type ConversationEventType =
| 'prompt' // User input/prompt
| 'response' // Assistant response text
| 'thinking' // Internal reasoning/thinking block
| 'tool_call' // Tool being called with arguments
| 'tool_result'; // Result from a tool call
/**
* Base interface for all conversation events
*/
export interface ConversationEventBase {
/** Unique event identifier */
id: string;
/** Type of conversation event */
type: ConversationEventType;
/** Role in conversation */
role: ConversationRole;
/** Unix timestamp in milliseconds */
ts: number;
/** Worker identifier */
worker: string;
/** Associated bead/task ID (if any) */
bead?: string;
/** Sequence number within the conversation */
sequence: number;
/** Token count for this event (if available) */
tokens?: number;
}
/**
* User prompt event
*/
export interface PromptEvent extends ConversationEventBase {
type: 'prompt';
role: 'user';
/** The user's prompt text */
content: string;
/** Whether this is a continuation of a previous prompt */
isContinuation?: boolean;
}
/**
* Assistant response event
*/
export interface ResponseEvent extends ConversationEventBase {
type: 'response';
role: 'assistant';
/** The response text */
content: string;
/** Whether the response is truncated */
isTruncated?: boolean;
/** Model used for this response */
model?: string;
/** Stop reason (if available) */
stopReason?: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use';
}
/**
* Thinking/reasoning block event
*/
export interface ThinkingEvent extends ConversationEventBase {
type: 'thinking';
role: 'assistant';
/** The thinking content */
content: string;
/** Whether thinking is truncated */
isTruncated?: boolean;
/** Duration of thinking in ms (if available) */
durationMs?: number;
}
/**
* Tool argument types
*/
export type ToolArgValue = string | number | boolean | null | ToolArgValue[] | { [key: string]: ToolArgValue };
/**
* Tool call event
*/
export interface ToolCallEvent extends ConversationEventBase {
type: 'tool_call';
role: 'assistant';
/** Tool name */
tool: string;
/** Tool arguments */
args: Record<string, ToolArgValue>;
/** Tool call ID (for correlating with results) */
toolCallId?: string;
/** Human-readable summary of the call */
summary?: string;
}
/**
* Tool result event
*/
export interface ToolResultEvent extends ConversationEventBase {
type: 'tool_result';
role: 'tool';
/** Tool name */
tool: string;
/** Tool call ID this is a response to */
toolCallId?: string;
/** Result content (may be truncated) */
content: string;
/** Whether the tool call succeeded */
success: boolean;
/** Error message if failed */
error?: string;
/** Duration of tool call in ms */
durationMs?: number;
/** Whether the result is truncated */
isTruncated?: boolean;
/** Size of full result in bytes (for context) */
resultSize?: number;
}
/**
* Union type for all conversation events
*/
export type ConversationEvent =
| PromptEvent
| ResponseEvent
| ThinkingEvent
| ToolCallEvent
| ToolResultEvent;
/**
* A complete conversation session
*/
export interface ConversationSession {
/** Session identifier */
id: string;
/** Worker ID */
workerId: string;
/** Associated bead ID (if any) */
beadId?: string;
/** Start timestamp */
startTime: number;
/** End timestamp (if complete) */
endTime?: number;
/** All events in chronological order */
events: ConversationEvent[];
/** Total token count */
totalTokens: number;
/** Number of turns */
turnCount: number;
/** Tools used in this session */
toolsUsed: string[];
/** Whether the session is still active */
isActive: boolean;
}
/**
* Options for parsing conversation events
*/
export interface ConversationParseOptions {
/** Maximum content length before truncation */
maxContentLength?: number;
/** Include thinking blocks */
includeThinking?: boolean;
/** Include tool results */
includeToolResults?: boolean;
/** Truncate tool results longer than this */
maxToolResultLength?: number;
}
// ============================================
// Core Log Event Types
// ============================================
export interface LogEvent {
/** Unix timestamp in milliseconds — display only, NOT authoritative for ordering */
ts: number;
/** Worker identifier (e.g., 'w-abc123') */
worker: string;
/** Per-worker monotonic counter — authoritative for ordering (from NeedleEvent.sequence) */
sequence?: number;
/** Log level */
level: LogLevel;
/** Log message */
msg: string;
/** Optional: Tool that was called */
tool?: string;
/** Optional: File path being operated on */
path?: string;
/** Optional: Bead/task identifier */
bead?: string;
/** Optional: Duration in milliseconds */
duration_ms?: number;
/** Optional: Error details */
error?: string;
/** NEEDLE session identifier (e.g. 'needle-claude-anthropic-sonnet-alpha') */
session?: string;
/** AI provider extracted from NEEDLE worker string (e.g. 'anthropic', 'openai') */
provider?: string;
/** AI model identifier extracted from NEEDLE worker string (e.g. 'sonnet', 'gpt-4o') */
model?: string;
/** Any additional fields */
[key: string]: unknown;
}
/**
* Compare two LogEvents by (worker, sequence), falling back to ts.
*
* Sequence is the authoritative ordering key within a worker (monotonic counter
* from NEEDLE). Timestamp is used only as a display fallback for legacy events
* that lack a sequence number.
*/
export function compareEventsBySequence(a: LogEvent, b: LogEvent): number {
const seqA = a.sequence != null && a.sequence >= 0 ? a.sequence : null;
const seqB = b.sequence != null && b.sequence >= 0 ? b.sequence : null;
if (seqA !== null && seqB !== null) {
if (a.worker !== b.worker) return a.worker.localeCompare(b.worker);
return seqA - seqB;
}
return a.ts - b.ts;
}
export interface WorkerInfo {
/** Worker identifier */
id: string;
/** Current status (coarse — derived from needleState) */
status: WorkerStatus;
/** Current NEEDLE state machine state (fine-grained) */
needleState?: NeedleState;
/** Timestamp of the last state transition (ms since epoch) */
lastStateTransition?: number;
/** Last event received */
lastEvent?: LogEvent;
/** Total beads completed */
beadsCompleted: number;
/** First seen timestamp */
firstSeen: number;
/** Last activity timestamp */
lastActivity: number;
/** Files currently being modified by this worker */
activeFiles: string[];
/** Whether this worker is involved in any collisions */
hasCollision: boolean;
/** Current bead/task being worked on */
activeBead?: string;
/** Directories this worker is active in */
activeDirectories: string[];
/** All collision types this worker is involved in */
collisionTypes: ('file' | 'bead' | 'task')[];
/** Total number of events received for this worker */
eventCount: number;
/** Whether the worker appears stuck (gap-based detection) */
stuck?: boolean;
/** Human-readable reason the worker is stuck */
stuckReason?: string;
}
export interface EventFilter {
/** Filter by worker ID */
worker?: string;
/** Filter by log level */
level?: LogLevel;
/** Filter by bead ID */
bead?: string;
/** Filter by event type (glob pattern, e.g. "bead.*") */
eventType?: string;
/** Filter by file path */
path?: string;
/** Time range start (Unix timestamp) */
since?: number;
/** Time range end (Unix timestamp) */
until?: number;
}
/**
* File collision event - when multiple workers modify the same file concurrently
*/
export interface FileCollision {
/** File path being contested */
path: string;
/** Workers involved in the collision */
workers: string[];
/** Timestamp when collision was detected */
detectedAt: number;
/** Events that triggered the collision */
events: LogEvent[];
/** Whether the collision is still active */
isActive: boolean;
}
/**
* Bead collision - when multiple workers work on the same bead/task
*/
export interface BeadCollision {
/** Bead ID being contested */
beadId: string;
/** Workers working on this bead */
workers: string[];
/** Timestamp when collision was detected */
detectedAt: number;
/** Events that triggered the collision */
events: LogEvent[];
/** Whether the collision is still active */
isActive: boolean;
/** Collision severity based on operation types */
severity: 'warning' | 'critical';
}
/**
* Task collision - when workers work on tasks that may conflict
*/
export interface TaskCollision {
/** Type of collision */
type: 'directory' | 'related_files' | 'dependency';
/** Human-readable description */
description: string;
/** Workers involved */
workers: string[];
/** Affected paths/beads */
affectedResources: string[];
/** Timestamp when collision was detected */
detectedAt: number;
/** Whether the collision is still active */
isActive: boolean;
/** Risk level */
riskLevel: 'low' | 'medium' | 'high';
}
/**
* Collision alert for user notification
*/
export interface CollisionAlert {
/** Unique alert ID */
id: string;
/** Alert type */
type: 'file' | 'bead' | 'task';
/** Severity level */
severity: 'info' | 'warning' | 'error' | 'critical';
/** Human-readable title */
title: string;
/** Detailed description */
description: string;
/** Workers involved */
workers: string[];
/** Timestamp when alert was generated */
timestamp: number;
/** Whether the alert has been acknowledged */
acknowledged: boolean;
/** Related collision data */
collision: FileCollision | BeadCollision | TaskCollision;
/** Suggested resolution */
suggestion?: string;
}
export interface EventStore {
/** Add an event to the store */
add(event: LogEvent): void;
/** Query events with optional filter */
query(filter?: EventFilter): LogEvent[];
/** Query events sorted by (worker, sequence), falling back to ts */
queryOrdered(filter?: EventFilter): LogEvent[];
/** Get worker info */
getWorker(workerId: string): WorkerInfo | undefined;
/** Get all workers */
getWorkers(): WorkerInfo[];
/** Clear all events */
clear(): void;
/** Get all active collisions */
getCollisions(): FileCollision[];
/** Get collisions for a specific worker */
getWorkerCollisions(workerId: string): FileCollision[];
}
// ============================================
// Error Grouping Types
// ============================================
/**
* Error fingerprint - used to identify similar errors
*/
export interface ErrorFingerprint {
/** Normalized pattern signature (e.g., "ECONNREFUSED_*:*" for connection errors) */
signature: string;
/** Category of error (network, permission, validation, etc.) */
category: ErrorCategory;
/** Original error message (first occurrence) */
sampleMessage: string;
/** Hash of the signature for quick comparison */
hash: string;
}
/**
* Error categories for grouping
*/
export type ErrorCategory =
| 'network' // Connection errors, timeouts, DNS issues
| 'permission' // Auth failures, access denied
| 'validation' // Invalid input, schema errors
| 'resource' // Out of memory, disk full, quota exceeded
| 'not_found' // File not found, 404 errors
| 'timeout' // Operation timed out
| 'syntax' // Parse errors, malformed data
| 'tool' // Tool-specific failures
| 'unknown'; // Uncategorized errors
/**
* Grouped error - clusters similar errors together
*/
export interface ErrorGroup {
/** Unique group ID */
id: string;
/** Fingerprint identifying this group */
fingerprint: ErrorFingerprint;
/** All error events in this group */
events: LogEvent[];
/** First occurrence timestamp */
firstSeen: number;
/** Most recent occurrence timestamp */
lastSeen: number;
/** Number of occurrences */
count: number;
/** Workers that have encountered this error */
affectedWorkers: string[];
/** Whether this error group is currently active (seen recently) */
isActive: boolean;
/** Severity based on frequency and recency */
severity: 'low' | 'medium' | 'high' | 'critical';
}
/**
* Options for error grouping
*/
export interface ErrorGroupingOptions {
/** Time window to consider errors as active (ms), default 5 minutes */
activeWindowMs?: number;
/** Minimum occurrences for high severity */
highSeverityThreshold?: number;
/** Minimum occurrences for critical severity */
criticalSeverityThreshold?: number;
/** Maximum groups to track */
maxGroups?: number;
}
// ============================================
// Session Replay Types
// ============================================
export type ReplaySpeed = 0.5 | 1 | 2 | 5 | 10;
export type ReplayState = 'idle' | 'playing' | 'paused' | 'ended';
export interface ReplaySession {
/** Unique session identifier */
id: string;
/** Source log file path */
sourcePath: string;
/** Total events in the session */
totalEvents: number;
/** Current playback position (event index) */
currentIndex: number;
/** Playback state */
state: ReplayState;
/** Playback speed multiplier */
speed: ReplaySpeed;
/** Start timestamp of session (first event) */
startTime: number;
/** End timestamp of session (last event) */
endTime: number;
/** Filter applied during replay */
filter?: EventFilter;
}
export interface ReplayControls {
/** Start or resume playback */
play(): void;
/** Pause playback */
pause(): void;
/** Toggle play/pause */
toggle(): void;
/** Step to next event */
stepForward(): void;
/** Step to previous event */
stepBackward(): void;
/** Jump to specific event index */
seekTo(index: number): void;
/** Set playback speed */
setSpeed(speed: ReplaySpeed): void;
/** Stop and reset replay */
reset(): void;
}
// ============================================
// File Heatmap Types
// ============================================
/**
* Heat level for a file based on modification frequency
*/
export type HeatLevel = 'cold' | 'warm' | 'hot' | 'critical';
/**
* Worker contribution to a file's modification history
*/
export interface WorkerFileContribution {
/** Worker ID */
workerId: string;
/** Number of modifications by this worker */
modifications: number;
/** Last modification timestamp */
lastModified: number;
/** Percentage of total modifications (0-100) */
percentage: number;
}
/**
* Single file entry in the heatmap
*/
export interface FileHeatmapEntry {
/** File path */
path: string;
/** Total modification count */
modifications: number;
/** Heat level based on frequency */
heatLevel: HeatLevel;
/** Workers who have modified this file */
workers: WorkerFileContribution[];
/** First modification timestamp */
firstModified: number;
/** Most recent modification timestamp */
lastModified: number;
/** Whether this file is currently being modified by multiple workers */
hasCollision: boolean;
/** Number of workers currently active on this file */
activeWorkers: number;
/** Average time between modifications (ms) */
avgModificationInterval: number;
}
/**
* Options for heatmap generation
*/
export interface HeatmapOptions {
/** Minimum modifications to be included in heatmap */
minModifications?: number;
/** Maximum entries to return */
maxEntries?: number;
/** Sort by: 'modifications' | 'recent' | 'workers' | 'collisions' */
sortBy?: 'modifications' | 'recent' | 'workers' | 'collisions';
/** Filter by directory prefix */
directoryFilter?: string;
/** Only show files with collisions */
collisionsOnly?: boolean;
}
/**
* Statistics for the entire file heatmap
*/
export interface FileHeatmapStats {
/** Total files being tracked */
totalFiles: number;
/** Total modifications across all files */
totalModifications: number;
/** Files with collisions */
collisionFiles: number;
/** Files currently being modified */
activeFiles: number;
/** Heat level distribution */
heatDistribution: Record<HeatLevel, number>;
/** Most active directory */
mostActiveDirectory: string;
/** Average modifications per file */
avgModificationsPerFile: number;
}
/**
* Heatmap snapshot at a specific point in time
*/
export interface HeatmapSnapshot {
/** Timestamp of this snapshot */
timestamp: number;
/** Heatmap entries at this point in time */
entries: FileHeatmapEntry[];
/** Statistics at this point in time */
stats: FileHeatmapStats;
}
/**
* Time-series heatmap data for animation
*/
export interface HeatmapTimelapse {
/** Start timestamp of the timelapse */
startTimestamp: number;
/** End timestamp of the timelapse */
endTimestamp: number;
/** Time interval between snapshots (ms) */
interval: number;
/** Total number of snapshots */
totalSnapshots: number;
/** Array of heatmap snapshots */
snapshots: HeatmapSnapshot[];
}
/**
* Options for generating timelapse data
*/
export interface TimelapseOptions {
/** Start timestamp (defaults to oldest modification) */
startTimestamp?: number;
/** End timestamp (defaults to now) */
endTimestamp?: number;
/** Number of snapshots to generate (default 30) */
snapshotCount?: number;
/** Minimum modifications to be included */
minModifications?: number;
/** Maximum entries per snapshot */
maxEntries?: number;
/** Sort mode for snapshots */
sortBy?: 'modifications' | 'recent' | 'workers' | 'collisions';
/** Filter by directory prefix */
directoryFilter?: string;
/** Only show files with collisions */
collisionsOnly?: boolean;
}
// ============================================
// File Anomaly Detection Types
// ============================================
/**
* Types of file activity anomalies
*/
export type AnomalyType =
| 'config_modification' // Config file modified outside expected context
| 'high_frequency' // Unusually high modification rate
| 'burst_activity' // Sudden burst of modifications
| 'unusual_pattern' // Activity pattern outside normal bounds
| 'sensitive_file'; // Sensitive file (secrets, credentials) touched
/**
* Severity level for anomalies
*/
export type AnomalySeverity = 'info' | 'warning' | 'critical';
/**
* Detected file anomaly
*/
export interface FileAnomaly {
/** File path with anomaly */
path: string;
/** Type of anomaly detected */
type: AnomalyType;
/** Severity level */
severity: AnomalySeverity;
/** Human-readable description */
message: string;
/** When the anomaly was detected */
detectedAt: number;
/** Supporting data for the anomaly */
details: {
/** Modification count involved */
modifications?: number;
/** Workers involved */
workers?: string[];
/** Time span of anomalous activity (ms) */
timeSpan?: number;
/** Expected normal value */
expectedValue?: number;
/** Actual observed value */
actualValue?: number;
/** Additional context */
context?: Record<string, unknown>;
};
}
/**
* Options for anomaly detection
*/
export interface AnomalyDetectionOptions {
/** Minimum modifications to consider for anomaly detection */
minModifications?: number;
/** Threshold multiplier for high-frequency detection (e.g., 3 = 3x average) */
frequencyThreshold?: number;
/** Time window for burst detection (ms) */
burstWindow?: number;
/** Minimum burst count to trigger anomaly */
burstThreshold?: number;
/** Custom patterns for sensitive files */
sensitivePatterns?: string[];
}
/**
* Statistics for anomaly detection
*/
export interface AnomalyStats {
/** Total anomalies detected */
totalAnomalies: number;
/** Count by type */
byType: Record<AnomalyType, number>;
/** Count by severity */
bySeverity: Record<AnomalySeverity, number>;
/** Files with most anomalies */
topAnomalyFiles: Array<{
path: string;
count: number;
types: AnomalyType[];
}>;
}
// ============================================
// Dependency DAG Types
// ============================================
/**
* Bead status type
*/
export type BeadStatus = 'open' | 'in_progress' | 'blocked' | 'completed' | 'closed' | 'deferred';
/**
* Single node in the dependency graph
*/
export interface BeadNode {
/** Bead ID (e.g., 'bd-abc123') */
id: string;
/** Bead title */
title: string;
/** Current status */
status: BeadStatus;
/** Priority level (0-4) */
priority: number;
/** Depth in the dependency tree (0 = root) */
depth: number;
/** Number of dependents (beads that depend on this) */
dependentCount: number;
/** Number of dependencies (beads this depends on) */
dependencyCount: number;
/** Whether this is on the critical path */
isCriticalPath: boolean;
/** Estimated effort (if available) */
estimatedEffort?: number;
}
/**
* Edge in the dependency graph
*/
export interface DependencyEdge {
/** Source bead ID (the one that depends) */
from: string;
/** Target bead ID (the dependency) */
to: string;
/** Whether this edge is part of the critical path */
isCritical: boolean;
}
/**
* Connected component in the dependency graph
*/
export interface DagComponent {
/** All nodes in this component */
nodes: BeadNode[];
/** All edges in this component */
edges: DependencyEdge[];
/** Root nodes (no incoming edges) */
roots: string[];
/** Whether this component contains cycles */
hasCycle: boolean;
/** Critical path through this component (bead IDs) */
criticalPath: string[];
/** Total depth of the component */
maxDepth: number;
}
/**
* Full dependency graph
*/
export interface DependencyGraph {
/** All connected components */
components: DagComponent[];
/** Total nodes across all components */
totalNodes: number;
/** Total edges across all components */
totalEdges: number;
/** Total components */
totalComponents: number;
/** Overall critical path (longest path across all components) */
globalCriticalPath: string[];
/** Timestamp when graph was generated */
generatedAt: number;
}
/**
* Options for DAG visualization
*/
export interface DagOptions {
/** Filter by status */
status?: BeadStatus | 'all';
/** Filter by priority range */
minPriority?: number;
maxPriority?: number;
/** Show only critical path */
criticalOnly?: boolean;
/** Maximum depth to display */
maxDepth?: number;
/** Sort order: 'priority' | 'depth' | 'dependents' */
sortBy?: 'priority' | 'depth' | 'dependents';
/** Include closed/completed beads */
includeClosed?: boolean;
}
/**
* Statistics about the dependency graph
*/
export interface DagStats {
/** Total beads tracked */
totalBeads: number;
/** Blocked beads count */
blockedCount: number;
/** Ready beads (unblocked, open) */
readyCount: number;
/** Average dependencies per bead */
avgDependencies: number;
/** Average dependents per bead */
avgDependents: number;
/** Maximum depth found */
maxDepth: number;
/** Number of cycles detected */
cycleCount: number;
/** Critical path length */
criticalPathLength: number;
/** Beads on critical path */
criticalPathBeads: number;
}
// ============================================
// Span-based DAG Types (OTLP span hierarchy)
// ============================================
/**
* A node in the span DAG, built from paired .started/.finished NeedleEvents
* derived from OTLP spans. Parent-child relationships come from
* parent_span_id rather than bead dependencies.
*/
export interface SpanNode {
/** OTLP span ID */
span_id: string;
/** OTLP trace ID */
trace_id: string;
/** Parent span ID (undefined for root spans) */
parent_span_id?: string;
/** Span name (e.g. "bead.lifecycle", "tool.call", "llm.request") */
name: string;
/** Worker that emitted this span */
worker_id: string;
/** Associated bead ID (if this is a bead lifecycle span) */
bead_id?: string;
/** Start timestamp (ms from .started event) */
start_ts?: number;
/** End timestamp (ms from .finished event) */
end_ts?: number;
/** Duration in ms */
duration_ms?: number;
/** Span completion status */
status: 'ok' | 'error' | 'unknown';
/** Child spans (populated during tree construction) */
children: SpanNode[];
/** Remaining span attributes */
attributes: Record<string, unknown>;
}
/**
* A span hierarchy DAG built from OTLP span events.
* Unlike the bead DependencyGraph (which uses br graph --json),
* the SpanDag is built from the live event stream using parent_span_id
* for parent-child linkage.
*/
export interface SpanDag {
/** Root spans (no parent_span_id or orphaned) */
roots: SpanNode[];
/** All spans indexed by span_id */
allSpans: Map<string, SpanNode>;
/** Spans grouped by trace_id */
traces: Map<string, SpanNode[]>;
}
// ============================================
// Git Event Types
// ============================================
/**
* Type of git event
*/
export type GitEventType =
| 'status' // Git status output (staged, unstaged, untracked)
| 'commit' // Git commit
| 'branch' // Branch information
| 'diff'; // Git diff output
/**
* File status in git
*/
export type GitFileStatus =
| 'added'
| 'modified'
| 'deleted'
| 'renamed'
| 'copied'
| 'untracked'
| 'unmerged';
/**
* Single file change in git
*/
export interface GitFileChange {
/** File path */
path: string;
/** Status of the file */
status: GitFileStatus;
/** Original path (for renames) */
originalPath?: string;
/** Staging area status */
staged: boolean;
}
/**
* Base interface for all git events
*/
export interface GitEventBase {
/** Unique event identifier */
id: string;
/** Type of git event */
type: GitEventType;
/** Unix timestamp in milliseconds */
ts: number;
/** Worker identifier */
worker: string;
/** Associated bead/task ID (if any) */
bead?: string;
}
/**
* Git status event
*/
export interface GitStatusEvent extends GitEventBase {
type: 'status';
/** Current branch name */
branch: string;
/** Commit hash (HEAD) */
commit?: string;
/** Staged file changes */
staged: GitFileChange[];
/** Unstaged file changes */
unstaged: GitFileChange[];
/** Untracked files */
untracked: string[];
/** Commits ahead of remote */
ahead?: number;
/** Commits behind remote */
behind?: number;
/** Remote tracking branch */
tracking?: string;
}
/**
* Git commit event
*/
export interface GitCommitEvent extends GitEventBase {
type: 'commit';
/** Commit hash */
hash: string;
/** Commit message */
message: string;
/** Branch name */
branch?: string;
/** Author name */
author?: string;
/** Author email */
email?: string;
/** Parent commit hash(es) */
parents?: string[];
/** Files changed in this commit */
files?: GitFileChange[];
}
/**
* Git branch event
*/
export interface GitBranchEvent extends GitEventBase {
type: 'branch';
/** Current branch name */
current: string;
/** All local branches */
branches?: string[];
/** Remote tracking branch */
tracking?: string;
/** Commits ahead of tracking */
ahead?: number;
/** Commits behind tracking */
behind?: number;
}
/**
* Git diff event
*/
export interface GitDiffEvent extends GitEventBase {
type: 'diff';
/** Diff target (e.g., 'HEAD', 'origin/main') */
target: string;
/** Files with changes */
files: GitFileChange[];
/** Total lines added */
linesAdded: number;
/** Total lines deleted */
linesDeleted: number;
/** Diff content (may be truncated) */
content?: string;
/** Whether diff content is truncated */
isTruncated?: boolean;
}
/**
* Union type for all git events
*/
export type GitEvent =
| GitStatusEvent
| GitCommitEvent
| GitBranchEvent
| GitDiffEvent;
/**
* Options for parsing git events
*/
export interface GitParseOptions {
/** Maximum diff content length before truncation */
maxDiffLength?: number;
/** Include file change details */
includeFileChanges?: boolean;
/** Maximum files to track in a single event */
maxFiles?: number;
}
// ============================================
// PR Preview Types
// ============================================
/**
* File change with diff statistics
*/
export interface PRFileChange extends GitFileChange {
/** Lines added in this file */
linesAdded: number;
/** Lines deleted in this file */
linesDeleted: number;
/** Worker who made the change */
worker?: string;
}
/**
* Upstream commit that might conflict
*/
export interface UpstreamCommit {
/** Commit hash */
hash: string;
/** Commit message */
message: string;
/** Author name */
author?: string;
/** Files changed in this commit */
files: string[];
/** Timestamp */
ts: number;
}
/**
* Potential conflict information
*/
export interface PotentialConflict {
/** Whether there are upstream commits */
hasUpstreamCommits: boolean;
/** Number of upstream commits */
upstreamCommitCount: number;
/** Upstream commits that might conflict */
upstreamCommits: UpstreamCommit[];
/** Files that might have conflicts */
conflictingFiles: string[];
/** Whether rebase is recommended */
rebaseRecommended: boolean;
/** Reason for rebase recommendation */
rebaseReason?: string;
}
/**
* PR Preview data
*/
export interface PRPreview {
/** Generated PR title */
title: string;
/** Generated PR description */
description: string;
/** Commit message preview */
commitMessage: string;
/** All files changed */
files: PRFileChange[];
/** Total lines added */
totalLinesAdded: number;
/** Total lines deleted */
totalLinesDeleted: number;
/** Number of files changed */
filesChanged: number;
/** Potential conflicts with upstream */
conflicts: PotentialConflict;
/** Source branch */
sourceBranch: string;
/** Target branch (usually main) */
targetBranch: string;
/** Number of commits ahead */
ahead: number;
/** Number of commits behind */
behind: number;
/** Whether there are uncommitted changes */
hasUncommittedChanges: boolean;
/** Timestamp when preview was generated */
generatedAt: number;
}
// ============================================
// Cross-Reference Types
// ============================================
/**
* Type of entity that can be cross-referenced
*/
export type CrossReferenceEntityType = 'event' | 'bead' | 'file' | 'worker' | 'session';
/**
* A single cross-reference link
*/
export interface CrossReferenceLink {
/** Unique link ID */
id: string;
/** Source entity type */
sourceType: CrossReferenceEntityType;
/** Source entity ID */
sourceId: string;
/** Target entity type */
targetType: CrossReferenceEntityType;
/** Target entity ID */
targetId: string;
/** Relationship type */
relationship: CrossReferenceRelationship;
/** Strength of the relationship (0-1) */
strength: number;
/** When this link was detected */
detectedAt: number;
/** Optional context about why this link exists */
context?: string;
}
/**
* Types of relationships between entities
*/
export type CrossReferenceRelationship =
| 'same_bead' // Events working on the same bead/task
| 'same_file' // Events modifying the same file
| 'same_worker' // Events from the same worker
| 'temporal_proximity' // Events happening close together in time
| 'same_session' // Events in the same worker session
| 'dependency' // One bead depends on another
| 'collision' // Workers colliding on the same file
| 'parent_child' // Hierarchical relationship
| 'error_related' // Events related to the same error
| 'tool_sequence'; // Tool calls that form a logical sequence
/**
* A cross-reference entity with its links
*/
export interface CrossReferenceEntity {
/** Entity type */
type: CrossReferenceEntityType;
/** Entity ID */
id: string;
/** Human-readable label */
label: string;
/** All links from this entity */
outgoingLinks: CrossReferenceLink[];
/** All links to this entity */
incomingLinks: CrossReferenceLink[];
/** Related entities grouped by type */
relatedEntities: Map<CrossReferenceEntityType, CrossReferenceLink[]>;
/** Total link count */
linkCount: number;
/** Most recent link timestamp */
lastLinkedAt: number;
/** First seen timestamp */
firstSeen: number;
/** Number of occurrences */
occurrenceCount: number;
}
/**
* Options for cross-reference queries
*/
export interface CrossReferenceQueryOptions {
/** Filter by source entity type */
sourceType?: CrossReferenceEntityType;
/** Filter by target entity type */
targetType?: CrossReferenceEntityType;
/** Filter by relationship type */
relationship?: CrossReferenceRelationship;
/** Minimum relationship strength */
minStrength?: number;
/** Time range start */
since?: number;
/** Time range end */
until?: number;
/** Maximum results */
limit?: number;
}
/** Alias for backward compatibility */
export type CrossReferenceFilter = CrossReferenceQueryOptions;
/**
* Statistics about cross-references
*/
export interface CrossReferenceStats {
/** Total links tracked */
totalLinks: number;
/** Total entities tracked */
totalEntities: number;
/** Links by relationship type */
byRelationship: Record<CrossReferenceRelationship, number>;
/** Entities by type */
byEntityType: Record<CrossReferenceEntityType, number>;
/** Most linked entities */
mostLinked: CrossReferenceEntity[];
/** Recent links */
recentLinks: CrossReferenceLink[];
}
/**
* A navigation path through cross-references
*/
export interface CrossReferencePath {
/** Starting entity */
start: CrossReferenceEntity;
/** Ending entity */
end: CrossReferenceEntity;
/** Path steps */
steps: CrossReferenceLink[];
/** Total path length */
length: number;
/** Path description */
description: string;
}
// ============================================
// Recovery Playbook Types
// ============================================
/**
* Priority level for recovery actions
*/
export type RecoveryPriority = 'immediate' | 'high' | 'normal' | 'low';
/**
* Type of recovery action
*/
export type RecoveryActionType =
| 'retry' // Simple retry the operation
| 'backoff' // Retry with exponential backoff
| 'alternative' // Use alternative approach
| 'escalate' // Escalate to human
| 'skip' // Skip and continue
| 'fix_config' // Fix configuration
| 'install_dep' // Install missing dependency
| 'fix_permissions' // Fix file permissions
| 'cleanup' // Clean up resources
| 'restart' // Restart service/process
| 'investigate'; // Requires further investigation
/**
* A single recovery action step
*/
export interface RecoveryAction {
/** Unique action ID */
id: string;
/** Action type */
type: RecoveryActionType;
/** Human-readable title */
title: string;
/** Detailed description of the action */
description: string;
/** Priority for ordering actions */
priority: RecoveryPriority;
/** Whether this action can be automated */
automated: boolean;
/** Command or code snippet to execute (if applicable) */
command?: string;
/** Expected outcome */
expectedOutcome?: string;
/** Prerequisites before this action */
prerequisites?: string[];
/** Risk level of this action */
riskLevel?: 'safe' | 'moderate' | 'risky';
/** Estimated time to complete (seconds) */
estimatedTime?: number;
}
/**
* A recovery playbook entry mapping error patterns to actions
*/
export interface RecoveryPlaybookEntry {
/** Unique playbook ID */
id: string;
/** Error category this applies to */
category: ErrorCategory;
/** Title for this recovery playbook */
title: string;
/** Description of the error pattern */
description: string;
/** Pattern to match error messages */
patterns: RegExp[];
/** Recovery actions in order */
actions: RecoveryAction[];
/** When this playbook was created */
createdAt: number;
/** When this playbook was last updated */
updatedAt: number;
/** Tags for categorization */
tags: string[];
}
/**
* A specific recovery suggestion for an error
*/
export interface RecoverySuggestion {
/** Unique suggestion ID */
id: string;
/** The error group this suggestion is for */
errorGroupId: string;
/** The playbook entry this came from (if any) */
playbookId?: string;
/** Error category */
category: ErrorCategory;
/** Title for the suggestion */
title: string;
/** Summary of the error */
errorSummary: string;
/** Recommended actions in order */
actions: RecoveryAction[];
/** When this suggestion was generated */
generatedAt: number;
/** Confidence level (0-1) */
confidence: number;
/** Related workers affected */
affectedWorkers: string[];
/** Similar past errors (if any) */
relatedErrors?: string[];
/** Whether this is still relevant */
isActive: boolean;
}
/**
* Options for recovery suggestion generation
*/
export interface RecoveryOptions {
/** Maximum actions to include per suggestion */
maxActions?: number;
/** Only include automated actions */
automatedOnly?: boolean;
/** Minimum confidence threshold */
minConfidence?: number;
/** Filter by category */
category?: ErrorCategory;
/** Filter by worker */
workerId?: string;
}
/**
* Statistics about recovery suggestions
*/
export interface RecoveryStats {
/** Total suggestions generated */
totalSuggestions: number;
/** Active suggestions */
activeSuggestions: number;
/** Suggestions by category */
byCategory: Record<ErrorCategory, number>;
/** Automated vs manual actions */
automatedActions: number;
manualActions: number;
/** Average confidence */
avgConfidence: number;
/** Most common recovery action types */
topActionTypes: Array<{ type: RecoveryActionType; count: number }>;
}
// ============================================
// Session Digest Types
// ============================================
/**
* Bead completion summary
*/
export interface BeadCompletion {
/** Bead ID */
beadId: string;
/** Worker that completed the bead */
workerId: string;
/** Completion timestamp */
completedAt: number;
/** Duration in milliseconds */
durationMs?: number;
}
/**
* File modification summary
*/
export interface FileModificationSummary {
/** File path */
path: string;
/** Number of modifications */
modifications: number;
/** Workers who modified this file */
workers: string[];
/** Tools used */
tools: string[];
}
/**
* Error occurrence in session
*/
export interface ErrorOccurrence {
/** Error message */
message: string;
/** Error category */
category: ErrorCategory;
/** Worker that encountered the error */
workerId: string;
/** Timestamp */
timestamp: number;
/** Error fingerprint */
fingerprint?: string;
}
/**
* Worker session summary
*/
export interface WorkerSessionSummary {
/** Worker ID */
workerId: string;
/** Beads completed */
beadsCompleted: number;
/** Files modified */
filesModified: number;
/** Errors encountered */
errorsEncountered: number;
/** Total events */
totalEvents: number;
/** Active time in milliseconds */
activeTimeMs: number;
/** First activity timestamp */
firstActivity: number;
/** Last activity timestamp */
lastActivity: number;
}
/**
* Complete session digest
*/
export interface SessionDigest {
/** Session ID or identifier */
sessionId: string;
/** Session start timestamp */
startTime: number;
/** Session end timestamp */
endTime: number;
/** Total duration in milliseconds */
durationMs: number;
/** Beads completed */
beadsCompleted: BeadCompletion[];
/** Files modified */
filesModified: FileModificationSummary[];
/** Errors encountered */
errors: ErrorOccurrence[];
/** Worker summaries */
workers: WorkerSessionSummary[];
/** Token usage and cost */
cost: {
totalTokens: number;
inputTokens: number;
outputTokens: number;
estimatedCostUsd: number;
};
/** Overall statistics */
stats: {
totalEvents: number;
totalWorkers: number;
totalBeads: number;
totalFiles: number;
totalErrors: number;
avgEventsPerWorker: number;
avgBeadsPerWorker: number;
};
}
/**
* Options for session digest generation
*/
export interface SessionDigestOptions {
/** Start time filter */
startTime?: number;
/** End time filter */
endTime?: number;
/** Include only specific workers */
workers?: string[];
/** Include error details */
includeErrors?: boolean;
/** Include cost breakdown */
includeCost?: boolean;
/** Maximum files to list */
maxFiles?: number;
/** Maximum errors to list */
maxErrors?: number;
}
// ============================================
// Worker Analytics Types
// ============================================
/**
* Time window for aggregation
*/
export type TimeWindow = 'hour' | 'day' | 'week' | 'all';
/**
* Worker analytics metrics for a specific time period
*/
export interface WorkerMetrics {
/** Worker ID */
workerId: string;
/** Time period start (Unix timestamp) */
periodStart: number;
/** Time period end (Unix timestamp) */
periodEnd: number;
/** Total beads completed in this period */
beadsCompleted: number;
/** Beads per hour (rate) */
beadsPerHour: number;
/** Average completion time per bead (milliseconds) */
avgCompletionTimeMs: number;
/** Total errors encountered */
errorCount: number;
/** Error rate (errors per bead) */
errorRate: number;
/** Total cost incurred (USD) */
totalCostUsd: number;
/** Cost per bead (USD) */
costPerBead: number;
/** Total active time (milliseconds) */
activeTimeMs: number;
/** Total idle time (milliseconds) */
idleTimeMs: number;
/** Idle percentage (0-100) */
idlePercentage: number;
/** Total events processed */
totalEvents: number;
/** Total tokens used */
totalTokens: number;
/** Tokens per bead */
tokensPerBead: number;
/** Efficiency score (0-1, calculated from active time vs idle time) */
efficiencyScore: number;
/** Performance trend data */
trend?: {
direction: 'improving' | 'declining' | 'stable';
confidence: number;
factors: string[];
};
}
/**
* Time-series data point for worker metrics
*/
export interface MetricsDataPoint {
/** Timestamp of this data point */
timestamp: number;
/** Worker ID */
workerId: string;
/** Metrics snapshot at this time */
metrics: Partial<WorkerMetrics>;
}
/**
* Worker performance trend
*/
export interface PerformanceTrend {
/** Worker ID */
workerId: string;
/** Metric being tracked */
metric: keyof WorkerMetrics;
/** Time-series data points */
dataPoints: MetricsDataPoint[];
/** Trend direction: 'improving' | 'declining' | 'stable' */
trend: 'improving' | 'declining' | 'stable';
/** Percentage change from first to last data point */
changePercent: number;
/** Average value across all data points */
average: number;
/** Minimum value */
min: number;
/** Maximum value */
max: number;
}
/**
* Aggregated analytics across all workers
*/
export interface AggregatedAnalytics {
/** Time period covered */
periodStart: number;
periodEnd: number;
/** Total workers tracked */
totalWorkers: number;
/** Total beads completed */
totalBeadsCompleted: number;
/** Average beads per hour across all workers */
avgBeadsPerHour: number;
/** Average completion time across all workers */
avgCompletionTimeMs: number;
/** Total errors across all workers */
totalErrors: number;
/** Overall error rate */
overallErrorRate: number;
/** Total cost across all workers */
totalCostUsd: number;
/** Average cost per bead */
avgCostPerBead: number;
/** Top performers (sorted by beads completed) */
topPerformers: WorkerMetrics[];
/** Workers with highest error rates */
highErrorRateWorkers: WorkerMetrics[];
/** Most cost-efficient workers (lowest cost per bead) */
costEfficientWorkers: WorkerMetrics[];
/** Number of currently active workers */
activeWorkerCount: number;
/** Total tokens used across all workers */
totalTokens: number;
/** Average efficiency across all workers */
avgEfficiency: number;
/** Underperforming workers (high error rate or low efficiency) */
underperformers: WorkerMetrics[];
}
/**
* Options for worker analytics
*/
export interface WorkerAnalyticsOptions {
/** Time window for aggregation */
timeWindow?: TimeWindow;
/** Custom start time (overrides timeWindow) */
startTime?: number;
/** Custom end time (overrides timeWindow) */
endTime?: number;
/** Filter by specific worker IDs */
workerIds?: string[];
/** Minimum beads completed to be included */
minBeadsCompleted?: number;
/** Maximum workers to return in rankings */
maxWorkers?: number;
/** Include time-series data */
includeTimeSeries?: boolean;
/** Time-series data point interval (milliseconds) */
timeSeriesInterval?: number;
}
/**
* Worker analytics store interface
*/
export interface WorkerAnalyticsStore {
/** Process an event and update analytics */
processEvent(event: LogEvent): void;
/** Get metrics for a specific worker */
getWorkerMetrics(workerId: string, options?: WorkerAnalyticsOptions): WorkerMetrics | undefined;
/** Get metrics for all workers */
getAllWorkerMetrics(options?: WorkerAnalyticsOptions): WorkerMetrics[];
/** Get aggregated analytics */
getAggregatedAnalytics(options?: WorkerAnalyticsOptions): AggregatedAnalytics;
/** Get performance trends */
getPerformanceTrends(workerId: string, metric: keyof WorkerMetrics, options?: WorkerAnalyticsOptions): PerformanceTrend;
/** Get time-series data */
getTimeSeriesData(workerId: string, options?: WorkerAnalyticsOptions): MetricsDataPoint[];
/** Clear all analytics data */
clear(): void;
/** Get analytics summary as formatted string */
getSummary(options?: WorkerAnalyticsOptions): string;
}
/**
* Worker comparison result - side-by-side comparison of two workers
*/
export interface WorkerComparison {
/** First worker metrics */
worker1: WorkerMetrics;
/** Second worker metrics */
worker2: WorkerMetrics;
/** Metric differences (worker1 - worker2, positive means worker1 is higher) */
differences: {
beadsCompleted: number;
beadsPerHour: number;
avgCompletionTimeMs: number;
errorRate: number;
costPerBead: number;
efficiencyScore: number;
};
/** Percentage differences (worker1 relative to worker2) */
percentDifferences: {
beadsCompleted: number;
beadsPerHour: number;
avgCompletionTimeMs: number;
errorRate: number;
costPerBead: number;
efficiencyScore: number;
};
/** Which worker is better for each metric */
betterWorker: {
beadsCompleted: 'worker1' | 'worker2' | 'tie';
beadsPerHour: 'worker1' | 'worker2' | 'tie';
avgCompletionTimeMs: 'worker1' | 'worker2' | 'tie';
errorRate: 'worker1' | 'worker2' | 'tie';
costPerBead: 'worker1' | 'worker2' | 'tie';
efficiencyScore: 'worker1' | 'worker2' | 'tie';
};
/** Overall winner based on majority of metrics */
overallWinner: 'worker1' | 'worker2' | 'tie';
/** Score tally for determining overall winner */
score: { worker1: number; worker2: number };
}
// ============================================
// Semantic Narrative Types
// ============================================
/**
* Narrative style for summarization
*/
export type NarrativeStyle = 'brief' | 'detailed' | 'timeline' | 'technical';
/**
* Event pattern types that drive narrative generation
*/
export type EventPattern =
| 'bead_started' // Worker started working on a bead
| 'bead_completed' // Worker completed a bead
| 'file_editing' // Editing files
| 'file_created' // Creating new files
| 'testing' // Running tests
| 'debugging' // Debugging errors
| 'git_operations' // Git commits, pushes, etc.
| 'dependency_install' // Installing dependencies
| 'collision_detected' // Workers colliding
| 'error_recovery' // Recovering from errors
| 'iteration' // Iterative refinement
| 'investigation' // Investigating/researching
| 'tool_usage' // Tool usage patterns
| 'error_handling' // Error handling patterns
| 'task_completion' // Task completion patterns
| 'exploration' // Exploration patterns
| 'planning' // Planning patterns
| 'research'; // Research patterns
/**
* A single narrative segment describing a sequence of events
*/
export interface NarrativeSegment {
/** Unique segment ID */
id: string;
/** Event pattern this segment describes */
pattern: EventPattern;
/** Natural language summary */
summary: string;
/** Detailed narrative (if available) */
details?: string;
/** Start timestamp */
startTime: number;
/** End timestamp */
endTime: number;
/** Duration in milliseconds */
durationMs: number;
/** Worker ID */
workerId: string;
/** Associated bead (if any) */
beadId?: string;
/** Events that comprise this segment */
events: LogEvent[];
/** Key entities mentioned (files, tools, etc.) */
entities: {
files?: string[];
tools?: string[];
beads?: string[];
errors?: string[];
};
/** Confidence in pattern detection (0-1) */
confidence: number;
/** Whether this segment is still active/ongoing */
isActive: boolean;
}
/**
* A complete narrative for a worker session or time period
*/
export interface SemanticNarrative {
/** Narrative ID */
id: string;
/** Worker ID (or 'all' for multi-worker narratives) */
workerId: string;
/** Narrative title */
title: string;
/** High-level summary (1-2 sentences) */
summary: string;
/** All narrative segments in chronological order */
segments: NarrativeSegment[];
/** Full narrative text */
fullNarrative: string;
/** Timeline of key events */
timeline: string[];
/** Start timestamp */
startTime: number;
/** End timestamp */
endTime: number;
/** Total duration */
durationMs: number;
/** Key accomplishments */
accomplishments: string[];
/** Challenges encountered */
challenges: string[];
/** Overall sentiment: 'productive' | 'struggling' | 'mixed' | 'idle' */
sentiment: 'productive' | 'struggling' | 'mixed' | 'idle';
/** Statistics */
stats: {
totalEvents: number;
segmentCount: number;
beadsWorked: number;
filesModified: number;
errorsEncountered: number;
toolsUsed: number;
};
/** When this narrative was generated */
generatedAt: number;
/** Whether this narrative is still being updated */
isLive: boolean;
}
/**
* Options for narrative generation
*/
export interface NarrativeOptions {
/** Narrative style */
style?: NarrativeStyle;
/** Filter by worker ID */
workerId?: string;
/** Filter by bead ID */
beadId?: string;
/** Time range start */
startTime?: number;
/** Time range end */
endTime?: number;
/** Minimum confidence for pattern detection */
minConfidence?: number;
/** Maximum segments to generate */
maxSegments?: number;
/** Include technical details */
includeTechnicalDetails?: boolean;
/** Include timeline */
includeTimeline?: boolean;
/** Group events by time window (ms) */
segmentWindowMs?: number;
/** Minimum events per segment */
minEventsPerSegment?: number;
}
/**
* Narrative update event - emitted when narrative changes
*/
export interface NarrativeUpdate {
/** Narrative ID */
narrativeId: string;
/** Update type */
type: 'segment_added' | 'segment_updated' | 'segment_completed' | 'narrative_completed';
/** Updated segment (if applicable) */
segment?: NarrativeSegment;
/** Timestamp of update */
timestamp: number;
/** New summary text */
summary?: string;
}
/**
* Semantic narrative manager interface
*/
export interface SemanticNarrativeManager {
/** Process an event and update narratives */
processEvent(event: LogEvent): void;
/** Generate narrative for a worker */
generateNarrative(workerId: string, options?: NarrativeOptions): SemanticNarrative;
/** Generate narrative for all workers */
generateAggregatedNarrative(options?: NarrativeOptions): SemanticNarrative;
/** Get current active narratives */
getActiveNarratives(): SemanticNarrative[];
/** Get narrative by ID */
getNarrative(narrativeId: string): SemanticNarrative | undefined;
/** Subscribe to narrative updates */
onUpdate(callback: (update: NarrativeUpdate) => void): () => void;
/** Clear all narratives */
clear(): void;
/** Get narrative as formatted string */
formatNarrative(narrative: SemanticNarrative, style?: NarrativeStyle): string;
}
// ============================================
// Worker Fleet Analytics Types
// ============================================
/**
* Duration bucket for bead completion histogram
*/
export type DurationBucket = '<5s' | '5-30s' | '30-120s' | '2-10m' | '10m+';
/**
* Model performance metrics
*/
export interface ModelPerformanceMetrics {
/** Model identifier (e.g., 'sonnet', 'glm-4.7', 'gpt-4o') */
model: string;
/** Provider (e.g., 'anthropic', 'openai', 'glm') */
provider: string;
/** Total beads completed */
beadsCompleted: number;
/** Average bead duration in ms */
avgDurationMs: number;
/** Median bead duration in ms */
medianDurationMs: number;
/** Duration distribution histogram */
durationDistribution: Record<DurationBucket, number>;
/** Success rate (0-1) */
successRate: number;
/** Total errors */
errorCount: number;
/** Total cost in USD */
totalCostUsd: number;
/** Average cost per bead */
avgCostPerBead: number;
}
/**
* Strand utilization metrics
*/
export interface StrandMetrics {
/** Strand name (pluck, mend, explore, weave, pulse, unravel, knot) */
strand: string;
/** Number of invocations */
invocations: number;
/** Number of successful invocations (found work) */
successCount: number;
/** Success rate (0-1) */
successRate: number;
/** Total time spent in ms */
totalTimeMs: number;
/** Average time per invocation in ms */
avgTimeMs: number;
/** Beads created by this strand (for weave, explore, etc.) */
beadsCreated?: number;
}
/**
* Completion quality indicator
*/
export interface CompletionQualityIndicator {
/** Bead ID */
beadId: string;
/** Worker that completed */
workerId: string;
/** Model used */
model: string;
/** Duration in ms */
durationMs: number;
/** Quality flags */
flags: {
/** Completed in <10s - suspicious shallow */
isShallow: boolean;
/** Had git commits */
hadCommits: boolean;
/** Was reopened after closure */
wasReopened: boolean;
/** Was claimed by multiple workers (race) */
hadClaimRace: boolean;
};
/** Completion timestamp */
completedAt: number;
}
/**
* Fleet time series data point
*/
export interface FleetTimeSeriesPoint {
/** Timestamp */
ts: number;
/** Active worker count */
activeWorkers: number;
/** Beads completed in this bucket */
beadsCompleted: number;
/** Errors in this bucket */
errors: number;
/** Cost in this bucket */
costUsd: number;
}
/**
* Workspace coverage info
*/
export interface WorkspaceCoverage {
/** Workspace path */
path: string;
/** Active workers in this workspace */
activeWorkers: number;
/** Total beads worked on */
beadsWorked: number;
/** Last activity timestamp */
lastActivity: number;
/** Worker IDs */
workers: string[];
}
/**
* Complete fleet analytics data
*/
export interface FleetAnalytics {
/** Time range covered */
timeRange: {
start: number;
end: number;
windowLabel: string;
};
/** Model performance breakdown */
modelPerformance: ModelPerformanceMetrics[];
/** Strand utilization */
strandMetrics: StrandMetrics[];
/** Quality indicators */
qualityIndicators: {
/** Total shallow completions (<10s) */
shallowCompletions: number;
/** Completions with git commits */
completionsWithCommits: number;
/** Reopened beads */
reopenedBeads: number;
/** Claim races */
claimRaces: number;
/** Recent suspicious completions */
suspiciousCompletions: CompletionQualityIndicator[];
};
/** Fleet overview */
fleetOverview: {
/** Total unique workers seen */
totalWorkers: number;
/** Currently active workers */
activeWorkers: number;
/** Total beads completed */
totalBeadsCompleted: number;
/** Total errors */
totalErrors: number;
/** Bead throughput per hour */
beadsPerHour: number;
/** Watchdog relaunch count */
watchdogRelaunchCount: number;
/** Workspace coverage */
workspaces: WorkspaceCoverage[];
};
/** Time series data */
timeSeries: FleetTimeSeriesPoint[];
}
/**
* Options for fleet analytics queries
*/
export interface FleetAnalyticsOptions {
/** Time window */
timeWindow?: TimeWindow;
/** Custom start time */
startTime?: number;
/** Custom end time */
endTime?: number;
/** Include time series data */
includeTimeSeries?: boolean;
/** Time series bucket size in minutes */
timeSeriesBucketMinutes?: number;
/** Filter by model */
modelFilter?: string;
/** Maximum suspicious completions to return */
maxSuspiciousCompletions?: number;
}