← Files AixcutARCHIVED FILE

scripts/workflow-trace-adapter.mjs

5.24 KB · Nov 6, 2026 · 07:28 UTC

↓ Download file

/**
 * Convert the MCP E2E journal shape into the host-neutral workflow trace shape.
 * This is deliberately conservative: incomplete requests and missing evidence
 * are omitted rather than being reported as successful workflow steps.
 */

const READ_ONLY_FRONTEND_ACTIONS = new Set([
  'page_status', 'capture_timeline_frames', 'capture_source_frames',
  'import_status', 'media.list', 'media.inspect', 'media.local_status',
  'media.resolve_cloud_url', 'operation.get', 'export_timeline_status',
]);
const READ_ONLY_GENERATE_ACTIONS = new Set(['catalog', 'get', 'list', 'quote', 'speech_read', 'speech_quote']);
const READ_ONLY_DOCUMENT_ACTIONS = new Set(['list', 'get', 'status']);
const MUTATION_TOOLS = new Set([
  'aixcut_primary', 'aixcut_speech', 'aixcut_caption', 'aixcut_clip',
  'aixcut_text', 'aixcut_audio', 'aixcut_track', 'aixcut_project',
  'aixcut_timeline', 'aixcut_generate', 'aixcut_document', 'aixcut_frontend',
]);

const resultOf = (envelope) => envelope?.result && typeof envelope.result === 'object'
  ? envelope.result : {};
const errorOf = (envelope) => envelope?.error && typeof envelope.error === 'object'
  ? envelope.error : {};
const actionOf = (args) => args?.action ?? args?.view;
const identityOf = (args, envelope, error = {}) => {
  const result = resultOf(envelope);
  const details = error?.details && typeof error.details === 'object' ? error.details : {};
  return args?.idempotency_key || args?.mutation_id || args?.operation_id
    || result.mutation_id || result.operation_id || result.job_id
    || details.mutation_id || details.operation_id || details.job_id;
};
const planOf = (args) => args?.plan_context?.plan_id || args?.plan_id;
const isMutation = (tool, args = {}) => {
  if (!MUTATION_TOOLS.has(tool)) return false;
  if (tool === 'aixcut_frontend') return !READ_ONLY_FRONTEND_ACTIONS.has(args.action);
  if (tool === 'aixcut_generate') return !READ_ONLY_GENERATE_ACTIONS.has(args.action);
  if (tool === 'aixcut_document') return !READ_ONLY_DOCUMENT_ACTIONS.has(args.action);
  return tool !== 'aixcut_read' && tool !== 'aixcut_review';
};

export function normalizeMcpE2eTrace(raw = {}, metadata = {}) {
  const sourceEvents = Array.isArray(raw.events) ? raw.events : [];
  const starts = new Map();
  const output = [];
  for (const event of sourceEvents) {
    if (event?.kind === 'tool:start' && event.requestId) {
      starts.set(event.requestId, event);
      continue;
    }
    if (!['tool:end', 'tool:error'].includes(event?.kind) || !event.requestId) continue;
    const start = starts.get(event.requestId);
    starts.delete(event.requestId);
    if (!start) continue;
    const args = start.arguments && typeof start.arguments === 'object' ? start.arguments : {};
    const envelope = event.envelope && typeof event.envelope === 'object' ? event.envelope : {};
    const tool = event.tool || start.tool;
    const action = actionOf(args);
    const identity = identityOf(args, envelope, errorOf(envelope));
    const result = resultOf(envelope);
    const status = envelope.status;

    if (isMutation(tool, args)) {
      const unknown = event.kind === 'tool:error'
        || event.isError === true
        || envelope.isError === true
        || errorOf(envelope).type === 'status_unknown'
        || errorOf(envelope).code === 'MCP_MUTATION_STATUS_UNKNOWN'
        || result.status === 'status_unknown';
      if (unknown && identity) {
        output.push({ type: 'unknown', mutation_id: identity, operation_id: identity, tool, action });
      } else if (!unknown && ['ok', 'async'].includes(status)) {
        const plan = planOf(args);
        const mutation = {
          type: 'mutation', tool, action,
          ...(identity ? { mutation_id: identity } : {}),
          ...(plan ? { plan_id: plan } : {}),
          ...(Array.isArray(args.plan_context?.preserve) ? { preserve: args.plan_context.preserve } : {}),
          ...(Number.isSafeInteger(result.revision_before) ? { revision_before: result.revision_before } : {}),
          ...(Number.isSafeInteger(result.revision_after) ? { revision_after: result.revision_after } : {}),
        };
        output.push(mutation);
      }
      continue;
    }

    if (tool === 'aixcut_read' || tool === 'aixcut_review') {
      const revision = result.revision ?? result.graph?.revision ?? result.timeline?.revision;
      if (action === 'timeline' && Number.isSafeInteger(revision)) output.push({ type: 'read_back', revision });
      if (action === 'mutation_receipt' || action === 'job') {
        const recoveredIdentity = identity || result.mutation_id || result.operation_id || result.job_id;
        const recoveredStatus = result.status || result.state;
        if (recoveredIdentity && ['applied', 'duplicate', 'noop', 'rejected', 'succeeded', 'failed'].includes(recoveredStatus)) {
          output.push({ type: 'recovered', mutation_id: recoveredIdentity, operation_id: recoveredIdentity, status: recoveredStatus });
        }
      }
      continue;
    }

    if (tool === 'aixcut_frontend' && action === 'capture_timeline_frames' && ['ok', 'async'].includes(status)) {
      output.push({ type: 'evidence_collected', layer: 'visual', visual: true });
    }
  }
  return {
    ...metadata,
    source: 'mcp-e2e',
    synthetic: metadata.synthetic ?? true,
    events: output,
  };
}

export default normalizeMcpE2eTrace;

SHA-256: 43419c257bb4646f33ce23f58982e49192c44ef3ba4b9c724ffad79322bc7f9b