Ctrl K
ComunidadGuías…

src/observability/run-summary.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { workerStatus, type RunState } from '../runner/state.ts';
import type { ObservableEvent } from './types.ts';
import type { PublicationView } from './publication.ts';
import type { CommandFacts } from './run-reader.ts';
import type { RunManifest, RunResult } from './run-resolver.ts';

export interface RunSummary {
  runId: string;
  executionId?: number;
  repo?: string;
  prNumber?: number;
  task?: string;
  permission?: string;
  commandExecutionType?: string;
  phase?: string;
  status: string;
  worker: 'idle' | 'running' | 'interrupted' | 'unknown';
  currentActivity: string;
  startedAt?: string;
  lastEventAt?: string;
  provider?: string;
  model?: string;
  requests: number;
  attempts: number;
  retries: number;
  tools: number;
  toolErrors: number;
  errors: number;
  validations: number;
  ciPolls: number;
  commits: number;
  pushes: number;
  remoteConfirmations: number;
  publications: number;
  workspace?: string;
  head?: string;
  lastCommit?: string;
  lastPush?: string;
  result?: RunResult;
  publication?: PublicationView;
}

export interface RunSummaryInput {
  runId: string;
  manifest?: RunManifest;
  result?: RunResult | null;
  state?: RunState | null;
  events: ObservableEvent[];
  publication?: PublicationView;
  command?: CommandFacts;
}

function detailText(event: ObservableEvent, key: string) {
  const value = event.detail?.[key];
  return typeof value === 'string' ? value : undefined;
}

function detailIdentifier(event: ObservableEvent, key: string) {
  const value = event.detail?.[key];
  return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined;
}

function detailNumber(event: ObservableEvent, key: string) {
  const value = event.detail?.[key];
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}

function detailIndex(event: ObservableEvent) {
  const value = detailNumber(event, 'index');
  return value === undefined ? undefined : String(value);
}

function manifestTask(manifest: RunManifest | undefined) {
  const chain = manifest?.task_chain;
  return Array.isArray(chain) && typeof chain.at(-1) === 'string' ? chain.at(-1) as string : undefined;
}

export function summarizeRun(input: RunSummaryInput): RunSummary {
  const result = input.result ?? undefined;
  const state = input.state ?? null;
  const summary: RunSummary = {
    runId: input.runId, executionId: typeof input.manifest?.execution_id === 'number' ? input.manifest.execution_id : state?.execution_id,
    repo: typeof input.manifest?.repo === 'string' ? input.manifest.repo : state?.repo,
    prNumber: typeof input.manifest?.pr_number === 'number' ? input.manifest.pr_number : state?.pr_number,
    task: manifestTask(input.manifest), permission: input.command?.permission, commandExecutionType: input.command?.executionType,
    phase: state?.phase, status: typeof result?.status === 'string' ? result.status : state?.active ? 'active' : state?.phase ?? 'unknown',
    worker: state ? workerStatus(state) : result ? 'idle' : 'unknown', currentActivity: 'idle',
    startedAt: typeof input.manifest?.started_at === 'string' ? input.manifest.started_at : undefined,
    requests: 0, attempts: 0, retries: 0, tools: 0, toolErrors: 0, errors: 0, validations: 0, ciPolls: 0, commits: 0, pushes: 0, remoteConfirmations: 0, publications: 0,
    head: typeof result?.final_head_sha === 'string' && result.final_head_sha ? result.final_head_sha : state?.current_head_sha || undefined,
    result,
    ...(input.publication ? { publication: input.publication } : {}),
  };
  const activeTools = new Map<string, string>();
  const pendingProviders = new Set<string>();
  let validationPending = false;
  let waitingForCi = false;
  let lastTitle = 'idle';

  for (const event of input.events) {
    summary.lastEventAt = event.time;
    summary.executionId = event.executionId ?? summary.executionId;
    lastTitle = event.title;
    const source = event.sourceEvent ?? '';
    const request = detailIdentifier(event, 'request');
    const index = detailIndex(event);
    if (source === 'phase' && detailText(event, 'phase')) summary.phase = detailText(event, 'phase');
    if (source === 'task_turn_start' || source === 'task_turn_end' || source === 'repair_started') summary.task = detailText(event, 'task') ?? summary.task;
    if (source === 'model_request') {
      summary.requests++;
      summary.provider = detailText(event, 'provider') ?? summary.provider;
      summary.model = detailText(event, 'model') ?? summary.model;
      pendingProviders.add(request ?? `request-${summary.requests}`);
    }
    if (source === 'provider_attempt') {
      summary.attempts++;
      pendingProviders.add(request ?? `attempt-${summary.attempts}`);
    }
    if (source === 'provider_retry') {
      summary.retries++;
      if (request !== undefined) pendingProviders.add(request);
    }
    if (source === 'model_response' || source === 'provider_error') if (request !== undefined) pendingProviders.delete(request);
    if (source === 'tool_start') {
      summary.tools++;
      activeTools.set(index ?? `tool-${summary.tools}`, detailText(event, 'tool') ?? 'unknown');
    }
    if (source === 'tool_end') {
      const tool = detailText(event, 'tool');
      const activeKey = index ?? [...activeTools.entries()].reverse().find(([, name]) => name === tool)?.[0];
      if (activeKey) activeTools.delete(activeKey);
      const exitCode = detailNumber(event, 'exit_code');
      if (event.detail?.error || (exitCode !== undefined && exitCode !== 0)) summary.toolErrors++;
    }
    if (event.kind === 'error') summary.errors++;
    if (source === 'validation' || source === 'repair_verification') summary.validations++;
    if (source === 'ci_poll') summary.ciPolls++;
    if (source === 'repair_verification_requested') validationPending = true;
    if (source === 'repair_verification') validationPending = false;
    if (source === 'ci_observation_wait') waitingForCi = true;
    if (source === 'ci_observation_complete') waitingForCi = false;
    if (source === 'repair_commit') {
      summary.commits++;
      summary.lastCommit = detailText(event, 'sha') ?? summary.lastCommit;
      summary.head = summary.lastCommit ?? summary.head;
    }
    if (source === 'repair_push') {
      summary.pushes++;
      summary.lastPush = detailText(event, 'sha') ?? summary.lastPush;
      summary.head = summary.lastPush ?? summary.head;
    }
    if (source.endsWith('push_confirmed')) summary.remoteConfirmations++;
    if (event.kind === 'publication') summary.publications++;
    summary.workspace = detailText(event, 'workspace') ?? summary.workspace;
  }

  const activeTool = activeTools.values().next().value as string | undefined;
  if (activeTool) summary.currentActivity = `running tool ${activeTool}`;
  else if (pendingProviders.size) summary.currentActivity = 'waiting for provider';
  else if (state?.waiting_for_ci || waitingForCi) summary.currentActivity = 'waiting for CI';
  else if (validationPending) summary.currentActivity = 'waiting for validation';
  else if (summary.worker === 'interrupted') summary.currentActivity = 'worker interrupted';
  else summary.currentActivity = lastTitle;
  if (summary.worker === 'idle' && result) summary.currentActivity = 'terminal';
  return summary;
}