feat(ui): add operator UI server, stores, and insights

This commit is contained in:
2026-02-23 18:49:53 -05:00
parent 8100f4d1c6
commit cf386e1aaa
18 changed files with 3252 additions and 17 deletions

View File

@@ -3,7 +3,7 @@ import {
type DomainEvent,
type DomainEventType,
} from "./domain-events.js";
import type { PipelineNode } from "./manifest.js";
import type { NodeTopologyKind, PipelineNode } from "./manifest.js";
import { type ProjectContextPatch, type FileSystemProjectContextStore } from "./project-context.js";
import { PersonaRegistry } from "./persona-registry.js";
import {
@@ -23,6 +23,10 @@ export type PipelineNodeAttemptObservedEvent = {
attempt: number;
result: ActorExecutionResult;
domainEvents: DomainEvent[];
executionContext: JsonObject;
fromNodeId?: string;
retrySpawned: boolean;
topologyKind: NodeTopologyKind;
};
function toBehaviorEvent(status: ActorResultStatus): "onTaskComplete" | "onValidationFail" | undefined {
@@ -149,6 +153,41 @@ function extractUsageMetrics(result: ActorExecutionResult): RuntimeEventUsage |
return undefined;
}
function extractSubtasks(result: ActorExecutionResult): string[] {
const candidates = [
result.payload?.subtasks,
result.stateMetadata?.subtasks,
];
for (const candidate of candidates) {
if (!Array.isArray(candidate)) {
continue;
}
const subtasks: string[] = [];
for (const item of candidate) {
if (typeof item !== "string") {
continue;
}
const normalized = item.trim();
if (!normalized) {
continue;
}
subtasks.push(normalized);
}
if (subtasks.length > 0) {
return subtasks;
}
}
return [];
}
function hasSecurityViolation(result: ActorExecutionResult): boolean {
return isRecord(result.payload) && result.payload.security_violation === true;
}
export interface PipelineLifecycleObserver {
onNodeAttempt(event: PipelineNodeAttemptObservedEvent): Promise<void>;
}
@@ -213,6 +252,12 @@ export class PersistenceLifecycleObserver implements PipelineLifecycleObserver {
status: event.result.status,
...(event.result.failureKind ? { failureKind: event.result.failureKind } : {}),
...(event.result.failureCode ? { failureCode: event.result.failureCode } : {}),
executionContext: event.executionContext,
topologyKind: event.topologyKind,
retrySpawned: event.retrySpawned,
...(event.fromNodeId ? { fromNodeId: event.fromNodeId } : {}),
subtasks: extractSubtasks(event.result),
securityViolation: hasSecurityViolation(event.result),
},
});