Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  export.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import fs from "node:fs";
import path from "node:path";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { FileEntry, SessionEntry, SessionHeader } from "@mariozechner/pi-coding-agent";
import { sanitizeDiagnosticPayload } from "../agents/payload-redaction.js";
import { resolveStateDir } from "../config/paths.js";
import {
  jsonSupportBundleFile,
  jsonlSupportBundleFile,
  supportBundleContents,
  textSupportBundleFile,
  writeSupportBundleDirectory,
  type DiagnosticSupportBundleContent,
  type DiagnosticSupportBundleFile,
} from "../logging/diagnostic-support-bundle.js";
import {
  redactSupportString,
  type SupportRedactionContext,
} from "../logging/diagnostic-support-redaction.js";
import { safeJsonStringify } from "../utils/safe-json.js";
import {
  TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
  resolveTrajectoryFilePath,
  resolveTrajectoryPointerFilePath,
  safeTrajectorySessionFileName,
} from "./runtime.js";
import type {
  TrajectoryBundleManifest,
  TrajectoryEvent,
  TrajectoryToolDefinition,
} from "./types.js";

type BuildTrajectoryBundleParams = {
  outputDir: string;
  sessionFile: string;
  sessionId: string;
  sessionKey?: string;
  workspaceDir: string;
  runtimeFile?: string;
  systemPrompt?: string;
  tools?: TrajectoryToolDefinition[];
  maxTotalEvents?: number;
};

type RuntimeTrajectoryContext = {
  systemPrompt?: string;
  tools?: TrajectoryToolDefinition[];
};

type JsonRecord = Record<string, unknown>;
type TrajectoryExportRedaction = SupportRedactionContext & {
  workspaceDir: string;
};

const MAX_TRAJECTORY_RUNTIME_EVENTS = 200_000;
const MAX_TRAJECTORY_TOTAL_EVENTS = 250_000;
const MAX_TRAJECTORY_SESSION_FILE_BYTES = 50 * 1024 * 1024;

function parseSessionEntries(content: string): FileEntry[] {
  return content
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean)
    .flatMap((line) => {
      try {
        return [JSON.parse(line) as FileEntry];
      } catch {
        return [];
      }
    });
}

function migrateLegacySessionEntries(entries: FileEntry[]): void {
  const header = entries.find((entry): entry is SessionHeader => entry.type === "session");
  const version = header?.version ?? 1;
  if (version < 2) {
    let previousId: string | null = null;
    let index = 0;
    for (const entry of entries) {
      if (entry.type === "session") {
        entry.version = 2;
        continue;
      }
      const mutable = entry as unknown as Record<string, unknown>;
      if (typeof mutable.id !== "string") {
        mutable.id = `legacy-${index++}`;
      }
      mutable.parentId = previousId;
      const entryId = mutable.id;
      previousId = typeof entryId === "string" ? entryId : null;
      if (entry.type === "compaction" && typeof mutable.firstKeptEntryIndex === "number") {
        const target = entries[mutable.firstKeptEntryIndex];
        if (target && target.type !== "session") {
          mutable.firstKeptEntryId = (target as unknown as Record<string, unknown>).id;
        }
        delete mutable.firstKeptEntryIndex;
      }
    }
  }
  if (version < 3) {
    for (const entry of entries) {
      if (entry.type === "session") {
        entry.version = 3;
        continue;
      }
      if (entry.type === "message") {
        const message = (entry as { message?: { role?: string } }).message;
        if (message?.role === "hookMessage") {
          message.role = "custom";
        }
      }
    }
  }
}

function readSessionBranch(filePath: string): {
  header: SessionHeader | null;
  leafId: string | null;
  branchEntries: SessionEntry[];
} {
  const fileEntries = parseSessionEntries(fs.readFileSync(filePath, "utf8"));
  migrateLegacySessionEntries(fileEntries);
  const header =
    fileEntries.find((entry): entry is SessionHeader => entry.type === "session") ?? null;
  const entries = fileEntries.filter(
    (entry): entry is SessionEntry =>
      entry.type !== "session" &&
      typeof (entry as { id?: unknown }).id === "string" &&
      (typeof (entry as { timestamp?: unknown }).timestamp === "string" ||
        typeof (entry as { timestamp?: unknown }).timestamp === "number"),
  );
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
  const leafId = entries.at(-1)?.id ?? null;
  const branchEntries: SessionEntry[] = [];
  let current = leafId ? byId.get(leafId) : undefined;
  while (current) {
    branchEntries.unshift(current);
    current = current.parentId ? byId.get(current.parentId) : undefined;
  }
  return { header, leafId, branchEntries };
}

function parseJsonlFile<T>(
  filePath: string,
  params: {
    maxBytes: number;
    maxEvents: number;
    validate?: (value: unknown) => value is T;
  },
): T[] {
  if (!fs.existsSync(filePath)) {
    return [];
  }
  const stat = fs.statSync(filePath);
  if (stat.size > params.maxBytes) {
    throw new Error(
      `Trajectory runtime file is too large to export (${stat.size} bytes; limit ${params.maxBytes})`,
    );
  }
  const content = fs.readFileSync(filePath, "utf8");
  const rows = content
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean);
  const parsed: T[] = [];
  for (const row of rows) {
    if (parsed.length >= params.maxEvents) {
      throw new Error(
        `Trajectory runtime file has too many events to export (limit ${params.maxEvents})`,
      );
    }
    try {
      const value = JSON.parse(row) as unknown;
      if (!params.validate || params.validate(value)) {
        parsed.push(value as T);
      }
    } catch {
      // Keep exports resilient even if a single debug line is malformed.
    }
  }
  return parsed;
}

function isFiniteNumber(value: unknown): value is number {
  return typeof value === "number" && Number.isFinite(value);
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function isRuntimeTrajectoryEventForSession(
  value: unknown,
  sessionId: string,
): value is TrajectoryEvent {
  if (!isRecord(value)) {
    return false;
  }
  return (
    value.traceSchema === "openclaw-trajectory" &&
    value.schemaVersion === 1 &&
    value.source === "runtime" &&
    typeof value.type === "string" &&
    typeof value.ts === "string" &&
    !Number.isNaN(Date.parse(value.ts)) &&
    isFiniteNumber(value.seq) &&
    value.sessionId === sessionId &&
    (!("data" in value) || value.data === undefined || isRecord(value.data))
  );
}

function isRegularNonSymlinkFile(filePath: string): boolean {
  try {
    const linkStat = fs.lstatSync(filePath);
    if (linkStat.isSymbolicLink() || !linkStat.isFile()) {
      return false;
    }
    const stat = fs.statSync(filePath);
    return stat.isFile() && stat.dev === linkStat.dev && stat.ino === linkStat.ino;
  } catch {
    return false;
  }
}

function readRuntimePointerFile(sessionFile: string, sessionId: string): string | undefined {
  const pointerPath = resolveTrajectoryPointerFilePath(sessionFile);
  if (!isRegularNonSymlinkFile(pointerPath)) {
    return undefined;
  }
  try {
    const parsed = JSON.parse(fs.readFileSync(pointerPath, "utf8")) as unknown;
    if (!isRecord(parsed)) {
      return undefined;
    }
    if (parsed.sessionId !== sessionId || typeof parsed.runtimeFile !== "string") {
      return undefined;
    }
    const runtimeFile = path.resolve(parsed.runtimeFile);
    const safeRuntimeFileName = `${safeTrajectorySessionFileName(sessionId)}.jsonl`;
    const defaultRuntimeFile = path.resolve(
      resolveTrajectoryFilePath({
        env: {},
        sessionFile,
        sessionId,
      }),
    );
    if (runtimeFile !== defaultRuntimeFile && path.basename(runtimeFile) !== safeRuntimeFileName) {
      return undefined;
    }
    return runtimeFile;
  } catch {
    return undefined;
  }
}

function resolveTrajectoryRuntimeFile(params: {
  runtimeFile?: string;
  sessionFile: string;
  sessionId: string;
}): string | undefined {
  if (params.runtimeFile) {
    return params.runtimeFile;
  }
  const candidates = [
    readRuntimePointerFile(params.sessionFile, params.sessionId),
    resolveTrajectoryFilePath({
      env: {},
      sessionFile: params.sessionFile,
      sessionId: params.sessionId,
    }),
    resolveTrajectoryFilePath({
      sessionFile: params.sessionFile,
      sessionId: params.sessionId,
    }),
  ].filter((candidate): candidate is string => Boolean(candidate));
  return candidates.find((candidate) => isRegularNonSymlinkFile(candidate));
}

function normalizeTimestamp(value: unknown): string {
  if (typeof value === "number" && Number.isFinite(value)) {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) {
      return parsed.toISOString();
    }
  }
  if (typeof value === "string") {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) {
      return parsed.toISOString();
    }
  }
  return new Date(0).toISOString();
}

function resolveMessageEventType(message: AgentMessage): string {
  if (message.role === "user") {
    return "user.message";
  }
  if (message.role === "assistant") {
    return "assistant.message";
  }
  if (message.role === "toolResult") {
    return "tool.result";
  }
  return `message.${message.role}`;
}

function extractAssistantToolCalls(
  message: AgentMessage,
): Array<{ id?: string; name?: string; arguments?: unknown; index: number }> {
  if (message.role !== "assistant" || !Array.isArray(message.content)) {
    return [];
  }
  return message.content.flatMap((block, index) => {
    if (!block || typeof block !== "object") {
      return [];
    }
    const typedBlock = block as {
      type?: unknown;
      id?: unknown;
      name?: unknown;
      arguments?: unknown;
      input?: unknown;
      parameters?: unknown;
    };
    const blockType =
      typeof typedBlock.type === "string" ? typedBlock.type.trim().toLowerCase() : "";
    if (blockType !== "toolcall" && blockType !== "tooluse" && blockType !== "functioncall") {
      return [];
    }
    return [
      {
        id: typeof typedBlock.id === "string" ? typedBlock.id : undefined,
        name: typeof typedBlock.name === "string" ? typedBlock.name : undefined,
        arguments: typedBlock.arguments ?? typedBlock.input ?? typedBlock.parameters,
        index,
      },
    ];
  });
}

function buildTranscriptEvents(params: {
  entries: SessionEntry[];
  sessionId: string;
  sessionKey?: string;
  workspaceDir: string;
  traceId: string;
}): TrajectoryEvent[] {
  const events: TrajectoryEvent[] = [];
  let seq = 0;
  for (const entry of params.entries) {
    const push = (type: string, data?: Record<string, unknown>) => {
      events.push({
        traceSchema: "openclaw-trajectory",
        schemaVersion: 1,
        traceId: params.traceId,
        source: "transcript",
        type,
        ts: normalizeTimestamp(entry.timestamp),
        seq: 0,
        sourceSeq: (seq += 1),
        sessionId: params.sessionId,
        sessionKey: params.sessionKey,
        workspaceDir: params.workspaceDir,
        entryId: entry.id,
        parentEntryId: entry.parentId,
        data,
      });
    };

    switch (entry.type) {
      case "message": {
        push(resolveMessageEventType(entry.message), {
          message: sanitizeDiagnosticPayload(entry.message),
        });
        for (const toolCall of extractAssistantToolCalls(entry.message)) {
          push("tool.call", {
            toolCallId: toolCall.id,
            name: toolCall.name,
            arguments: sanitizeDiagnosticPayload(toolCall.arguments),
            assistantEntryId: entry.id,
            blockIndex: toolCall.index,
          });
        }
        break;
      }
      case "compaction":
        push("session.compaction", {
          summary: entry.summary,
          firstKeptEntryId: entry.firstKeptEntryId,
          tokensBefore: entry.tokensBefore,
          details: sanitizeDiagnosticPayload(entry.details),
          fromHook: entry.fromHook ?? false,
        });
        break;
      case "branch_summary":
        push("session.branch_summary", {
          fromId: entry.fromId,
          summary: entry.summary,
          details: sanitizeDiagnosticPayload(entry.details),
          fromHook: entry.fromHook ?? false,
        });
        break;
      case "custom":
        push("session.custom", {
          customType: entry.customType,
          data: sanitizeDiagnosticPayload(entry.data),
        });
        break;
      case "custom_message":
        push("session.custom_message", {
          customType: entry.customType,
          content: sanitizeDiagnosticPayload(entry.content),
          details: sanitizeDiagnosticPayload(entry.details),
          display: entry.display,
        });
        break;
      case "thinking_level_change":
        push("session.thinking_level_change", {
          thinkingLevel: entry.thinkingLevel,
        });
        break;
      case "model_change":
        push("session.model_change", {
          provider: entry.provider,
          modelId: entry.modelId,
        });
        break;
      case "label":
        push("session.label", {
          targetId: entry.targetId,
          label: entry.label,
        });
        break;
      case "session_info":
        push("session.info", {
          name: entry.name,
        });
        break;
    }
  }
  return events;
}

function sortTrajectoryEvents(events: TrajectoryEvent[]): TrajectoryEvent[] {
  const sourceOrder: Record<TrajectoryEvent["source"], number> = {
    runtime: 0,
    transcript: 1,
    export: 2,
  };
  const sorted = events.toSorted((left, right) => {
    const byTs = left.ts.localeCompare(right.ts);
    if (byTs !== 0) {
      return byTs;
    }
    const bySource = sourceOrder[left.source] - sourceOrder[right.source];
    if (bySource !== 0) {
      return bySource;
    }
    return (left.sourceSeq ?? left.seq) - (right.sourceSeq ?? right.seq);
  });
  for (const [index, event] of sorted.entries()) {
    event.seq = index + 1;
  }
  return sorted;
}

function trajectoryJsonlFile(
  pathName: string,
  events: TrajectoryEvent[],
): DiagnosticSupportBundleFile {
  const lines = events
    .map((event) => safeJsonStringify(event))
    .filter((line): line is string => Boolean(line));
  return jsonlSupportBundleFile(pathName, lines);
}

function buildTrajectoryExportRedaction(params: {
  workspaceDir: string;
}): TrajectoryExportRedaction {
  const env = process.env;
  return {
    env,
    stateDir: resolveStateDir(env),
    workspaceDir: path.resolve(params.workspaceDir),
  };
}

function redactWorkspacePathString(value: string, redaction: TrajectoryExportRedaction): string {
  const workspaceDir = redaction.workspaceDir;
  if (!workspaceDir) {
    return value;
  }
  const normalizedWorkspaceDir = workspaceDir.replaceAll("\\", "/");
  let next = value;
  for (const candidate of new Set([workspaceDir, normalizedWorkspaceDir])) {
    if (!candidate) {
      continue;
    }
    const escaped = candidate.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
    next = next.replace(new RegExp(`${escaped}(?=$|[\\\\/])`, "gu"), "$WORKSPACE_DIR");
  }
  return next;
}

function maybeRedactPathString(value: string, redaction: TrajectoryExportRedaction): string {
  const workspaceRedacted = redactWorkspacePathString(value, redaction);
  if (
    workspaceRedacted !== value ||
    path.isAbsolute(workspaceRedacted) ||
    workspaceRedacted.includes(redaction.stateDir) ||
    (redaction.env.HOME ? workspaceRedacted.includes(redaction.env.HOME) : false) ||
    (redaction.env.USERPROFILE ? workspaceRedacted.includes(redaction.env.USERPROFILE) : false)
  ) {
    return redactSupportString(workspaceRedacted, redaction);
  }
  return workspaceRedacted;
}

function redactLocalPathValues(value: unknown, redaction: TrajectoryExportRedaction): unknown {
  if (typeof value === "string") {
    return maybeRedactPathString(value, redaction);
  }
  if (Array.isArray(value)) {
    return value.map((entry) => redactLocalPathValues(entry, redaction));
  }
  if (!value || typeof value !== "object") {
    return value;
  }
  const record = value as Record<string, unknown>;
  const next: Record<string, unknown> = {};
  for (const [key, entry] of Object.entries(record)) {
    next[key] = redactLocalPathValues(entry, redaction);
  }
  return next;
}

function redactEventForExport(
  event: TrajectoryEvent,
  redaction: TrajectoryExportRedaction,
): TrajectoryEvent {
  return {
    ...event,
    workspaceDir: event.workspaceDir
      ? maybeRedactPathString(event.workspaceDir, redaction)
      : undefined,
    data: event.data
      ? (redactLocalPathValues(event.data, redaction) as Record<string, unknown>)
      : undefined,
  };
}

function resolveRuntimeContext(runtimeEvents: TrajectoryEvent[]): RuntimeTrajectoryContext {
  const latestContext = runtimeEvents
    .slice()
    .toReversed()
    .find((event) => event.type === "context.compiled");
  const runtimeData = latestContext?.data;
  const toolsValue = Array.isArray(runtimeData?.tools)
    ? (runtimeData.tools as TrajectoryToolDefinition[])
    : undefined;
  return {
    systemPrompt:
      typeof runtimeData?.systemPrompt === "string" ? runtimeData.systemPrompt : undefined,
    tools: toolsValue,
  };
}

function resolveLatestRuntimeEventData(
  runtimeEvents: TrajectoryEvent[],
  type: string,
): JsonRecord | undefined {
  const event = runtimeEvents
    .slice()
    .toReversed()
    .find((candidate) => candidate.type === type);
  return event?.data;
}

function normalizePathForMatch(value: string): string {
  return value.replaceAll("\\", "/").trim().toLowerCase();
}

function collectPotentialPathStrings(value: unknown): string[] {
  const found = new Set<string>();
  const visit = (input: unknown) => {
    if (!input || typeof input !== "object") {
      return;
    }
    if (Array.isArray(input)) {
      for (const entry of input) {
        visit(entry);
      }
      return;
    }
    for (const [key, entry] of Object.entries(input)) {
      if (
        typeof entry === "string" &&
        (key.toLowerCase().includes("path") ||
          entry.endsWith("SKILL.md") ||
          entry.endsWith("skill.md"))
      ) {
        found.add(entry);
      } else {
        visit(entry);
      }
    }
  };
  visit(value);
  return [...found];
}

function markInvokedSkills(params: { skills: unknown; events: TrajectoryEvent[] }): unknown {
  if (!params.skills || typeof params.skills !== "object") {
    return params.skills;
  }
  const skillsRecord = params.skills as {
    entries?: Array<Record<string, unknown>>;
  };
  if (!Array.isArray(skillsRecord.entries) || skillsRecord.entries.length === 0) {
    return params.skills;
  }
  const invokedPaths = new Set(
    params.events.flatMap((event) => {
      if (event.type !== "tool.call") {
        return [];
      }
      return collectPotentialPathStrings(event.data?.arguments);
    }),
  );
  const normalizedInvokedPaths = new Set(
    [...invokedPaths].map((value) => normalizePathForMatch(value)),
  );
  const entries = skillsRecord.entries.map((entry) => {
    const rawPath = typeof entry.filePath === "string" ? entry.filePath : undefined;
    const normalizedPath = rawPath ? normalizePathForMatch(rawPath) : undefined;
    const skillDirName =
      rawPath?.replaceAll("\\", "/").split("/").slice(-2, -1)[0]?.toLowerCase() ?? undefined;
    const invoked = normalizedPath
      ? [...normalizedInvokedPaths].some(
          (candidate) =>
            candidate === normalizedPath ||
            candidate.endsWith(normalizedPath) ||
            (skillDirName ? candidate.endsWith(`/${skillDirName}/skill.md`) : false),
        )
      : false;
    return invoked
      ? {
          ...entry,
          invoked,
          invocationDetectedBy: "tool-call-file-path",
        }
      : {
          ...entry,
          invoked: false,
        };
  });
  return {
    ...skillsRecord,
    entries,
  };
}

function buildMetadataCapture(params: {
  manifest: TrajectoryBundleManifest;
  runtimeEvents: TrajectoryEvent[];
  events: TrajectoryEvent[];
}): JsonRecord | undefined {
  const runtimeMetadata = resolveLatestRuntimeEventData(params.runtimeEvents, "trace.metadata");
  if (!runtimeMetadata) {
    return undefined;
  }
  const modelFallback = (() => {
    const latest = params.runtimeEvents
      .slice()
      .toReversed()
      .find((event) => event.provider || event.modelId || event.modelApi);
    if (!latest?.provider && !latest?.modelId && !latest?.modelApi) {
      return undefined;
    }
    return {
      provider: latest.provider,
      name: latest.modelId,
      api: latest.modelApi,
    };
  })();
  return {
    traceSchema: "openclaw-trajectory",
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    traceId: params.manifest.traceId,
    sessionId: params.manifest.sessionId,
    sessionKey: params.manifest.sessionKey,
    harness: runtimeMetadata.harness,
    model: runtimeMetadata.model ?? modelFallback,
    config: runtimeMetadata.config,
    plugins: runtimeMetadata.plugins,
    skills: markInvokedSkills({
      skills: runtimeMetadata.skills,
      events: params.events,
    }),
    prompting: runtimeMetadata.prompting,
    redaction: runtimeMetadata.redaction,
    metadata: runtimeMetadata.metadata,
  };
}

function buildArtifactsCapture(params: {
  manifest: TrajectoryBundleManifest;
  runtimeEvents: TrajectoryEvent[];
}): JsonRecord | undefined {
  const runtimeArtifacts = resolveLatestRuntimeEventData(params.runtimeEvents, "trace.artifacts");
  const runtimeCompletion = resolveLatestRuntimeEventData(params.runtimeEvents, "model.completed");
  const runtimeEnd = resolveLatestRuntimeEventData(params.runtimeEvents, "session.ended");
  if (!runtimeArtifacts && !runtimeCompletion && !runtimeEnd) {
    return undefined;
  }
  return {
    traceSchema: "openclaw-trajectory",
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    traceId: params.manifest.traceId,
    sessionId: params.manifest.sessionId,
    sessionKey: params.manifest.sessionKey,
    finalStatus: runtimeArtifacts?.finalStatus ?? runtimeEnd?.status,
    aborted: runtimeArtifacts?.aborted ?? runtimeEnd?.aborted,
    externalAbort: runtimeArtifacts?.externalAbort ?? runtimeEnd?.externalAbort,
    timedOut: runtimeArtifacts?.timedOut ?? runtimeEnd?.timedOut,
    idleTimedOut: runtimeArtifacts?.idleTimedOut ?? runtimeEnd?.idleTimedOut,
    timedOutDuringCompaction:
      runtimeArtifacts?.timedOutDuringCompaction ?? runtimeEnd?.timedOutDuringCompaction,
    promptError:
      runtimeArtifacts?.promptError ?? runtimeEnd?.promptError ?? runtimeCompletion?.promptError,
    promptErrorSource: runtimeArtifacts?.promptErrorSource ?? runtimeCompletion?.promptErrorSource,
    usage: runtimeArtifacts?.usage ?? runtimeCompletion?.usage,
    promptCache: runtimeArtifacts?.promptCache ?? runtimeCompletion?.promptCache,
    compactionCount: runtimeArtifacts?.compactionCount ?? runtimeCompletion?.compactionCount,
    assistantTexts: runtimeArtifacts?.assistantTexts ?? runtimeCompletion?.assistantTexts,
    finalPromptText: runtimeArtifacts?.finalPromptText ?? runtimeCompletion?.finalPromptText,
    itemLifecycle: runtimeArtifacts?.itemLifecycle,
    toolMetas: runtimeArtifacts?.toolMetas,
    didSendViaMessagingTool: runtimeArtifacts?.didSendViaMessagingTool,
    successfulCronAdds: runtimeArtifacts?.successfulCronAdds,
    messagingToolSentTexts: runtimeArtifacts?.messagingToolSentTexts,
    messagingToolSentMediaUrls: runtimeArtifacts?.messagingToolSentMediaUrls,
    messagingToolSentTargets: runtimeArtifacts?.messagingToolSentTargets,
    lastToolError: runtimeArtifacts?.lastToolError,
  };
}

function buildPromptsCapture(params: {
  manifest: TrajectoryBundleManifest;
  runtimeEvents: TrajectoryEvent[];
  runtimeContext: RuntimeTrajectoryContext;
}): JsonRecord | undefined {
  const runtimeMetadata = resolveLatestRuntimeEventData(params.runtimeEvents, "trace.metadata");
  const latestCompiled = resolveLatestRuntimeEventData(params.runtimeEvents, "context.compiled");
  const submittedPrompts = params.runtimeEvents
    .filter((event) => event.type === "prompt.submitted")
    .map((event) => event.data?.prompt)
    .filter((prompt): prompt is string => typeof prompt === "string");
  const systemPrompt =
    (typeof latestCompiled?.systemPrompt === "string" ? latestCompiled.systemPrompt : undefined) ??
    params.runtimeContext.systemPrompt;
  const skillsPrompt =
    runtimeMetadata?.prompting &&
    typeof runtimeMetadata.prompting === "object" &&
    typeof (runtimeMetadata.prompting as JsonRecord).skillsPrompt === "string"
      ? ((runtimeMetadata.prompting as JsonRecord).skillsPrompt as string)
      : undefined;
  const userPromptPrefixText =
    runtimeMetadata?.prompting &&
    typeof runtimeMetadata.prompting === "object" &&
    typeof (runtimeMetadata.prompting as JsonRecord).userPromptPrefixText === "string"
      ? ((runtimeMetadata.prompting as JsonRecord).userPromptPrefixText as string)
      : undefined;
  const promptReport =
    runtimeMetadata?.prompting &&
    typeof runtimeMetadata.prompting === "object" &&
    typeof (runtimeMetadata.prompting as JsonRecord).systemPromptReport === "object"
      ? (runtimeMetadata.prompting as JsonRecord).systemPromptReport
      : undefined;
  if (!systemPrompt && submittedPrompts.length === 0 && !skillsPrompt && !userPromptPrefixText) {
    return undefined;
  }
  return {
    traceSchema: "openclaw-trajectory",
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    traceId: params.manifest.traceId,
    sessionId: params.manifest.sessionId,
    sessionKey: params.manifest.sessionKey,
    system: systemPrompt,
    submittedPrompts,
    latestSubmittedPrompt: submittedPrompts.at(-1),
    skillsPrompt,
    userPromptPrefixText,
    systemPromptReport: promptReport,
  };
}

export function resolveDefaultTrajectoryExportDir(params: {
  workspaceDir: string;
  sessionId: string;
  now?: Date;
}): string {
  const timestamp = (params.now ?? new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
  const sessionFileName = safeTrajectorySessionFileName(params.sessionId);
  return path.join(
    params.workspaceDir,
    ".openclaw",
    "trajectory-exports",
    `openclaw-trajectory-${sessionFileName.slice(0, 8)}-${timestamp}`,
  );
}

export function exportTrajectoryBundle(params: BuildTrajectoryBundleParams): {
  manifest: TrajectoryBundleManifest;
  outputDir: string;
  events: TrajectoryEvent[];
  header: SessionHeader | null;
  runtimeFile?: string;
  supplementalFiles: string[];
} {
  const redaction = buildTrajectoryExportRedaction({
    workspaceDir: params.workspaceDir,
  });
  const sessionStat = fs.statSync(params.sessionFile);
  if (sessionStat.size > MAX_TRAJECTORY_SESSION_FILE_BYTES) {
    throw new Error(
      `Trajectory session file is too large to export (${sessionStat.size} bytes; limit ${MAX_TRAJECTORY_SESSION_FILE_BYTES})`,
    );
  }
  const { header, leafId, branchEntries } = readSessionBranch(params.sessionFile);
  const runtimeFile = resolveTrajectoryRuntimeFile({
    runtimeFile: params.runtimeFile,
    sessionFile: params.sessionFile,
    sessionId: params.sessionId,
  });
  const runtimeEvents = runtimeFile
    ? parseJsonlFile<TrajectoryEvent>(runtimeFile, {
        maxBytes: TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
        maxEvents: MAX_TRAJECTORY_RUNTIME_EVENTS,
        validate: (value): value is TrajectoryEvent =>
          isRuntimeTrajectoryEventForSession(value, params.sessionId),
      })
    : [];
  const transcriptEvents = buildTranscriptEvents({
    entries: branchEntries,
    sessionId: params.sessionId,
    sessionKey: params.sessionKey,
    workspaceDir: params.workspaceDir,
    traceId: params.sessionId,
  });
  const maxTotalEvents = params.maxTotalEvents ?? MAX_TRAJECTORY_TOTAL_EVENTS;
  const totalEventCount = runtimeEvents.length + transcriptEvents.length;
  if (totalEventCount > maxTotalEvents) {
    throw new Error(
      `Trajectory export has too many events (${totalEventCount}; limit ${maxTotalEvents})`,
    );
  }
  const rawEvents = sortTrajectoryEvents([...runtimeEvents, ...transcriptEvents]);
  const events = rawEvents.map((event) => redactEventForExport(event, redaction));
  const manifest: TrajectoryBundleManifest = {
    traceSchema: "openclaw-trajectory",
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    traceId: params.sessionId,
    sessionId: params.sessionId,
    sessionKey: params.sessionKey,
    workspaceDir: maybeRedactPathString(params.workspaceDir, redaction),
    leafId,
    eventCount: events.length,
    runtimeEventCount: runtimeEvents.length,
    transcriptEventCount: transcriptEvents.length,
    sourceFiles: {
      session: maybeRedactPathString(params.sessionFile, redaction),
      runtime:
        runtimeFile && isRegularNonSymlinkFile(runtimeFile)
          ? maybeRedactPathString(runtimeFile, redaction)
          : undefined,
    },
  };

  const bundleRuntimeContext = resolveRuntimeContext(runtimeEvents);
  const files: DiagnosticSupportBundleFile[] = [];
  const supplementalFiles: string[] = [];
  const metadataCapture = buildMetadataCapture({
    manifest,
    runtimeEvents,
    events: rawEvents,
  });
  const artifactsCapture = buildArtifactsCapture({
    manifest,
    runtimeEvents,
  });
  const promptsCapture = buildPromptsCapture({
    manifest,
    runtimeEvents,
    runtimeContext: bundleRuntimeContext,
  });
  if (metadataCapture) {
    files.push(
      jsonSupportBundleFile("metadata.json", redactLocalPathValues(metadataCapture, redaction)),
    );
    supplementalFiles.push("metadata.json");
  }
  if (artifactsCapture) {
    files.push(
      jsonSupportBundleFile("artifacts.json", redactLocalPathValues(artifactsCapture, redaction)),
    );
    supplementalFiles.push("artifacts.json");
  }
  if (promptsCapture) {
    files.push(
      jsonSupportBundleFile("prompts.json", redactLocalPathValues(promptsCapture, redaction)),
    );
    supplementalFiles.push("prompts.json");
  }
  if (supplementalFiles.length > 0) {
    manifest.supplementalFiles = supplementalFiles;
  }

  files.push(trajectoryJsonlFile("events.jsonl", events));
  files.push(
    jsonSupportBundleFile(
      "session-branch.json",
      redactLocalPathValues(
        sanitizeDiagnosticPayload({
          header,
          leafId,
          entries: branchEntries,
        }),
        redaction,
      ),
    ),
  );
  if (bundleRuntimeContext.systemPrompt) {
    files.push(
      textSupportBundleFile(
        "system-prompt.txt",
        redactLocalPathValues(bundleRuntimeContext.systemPrompt, redaction) as string,
      ),
    );
  }
  if (bundleRuntimeContext.tools) {
    files.push(
      jsonSupportBundleFile(
        "tools.json",
        redactLocalPathValues(bundleRuntimeContext.tools, redaction),
      ),
    );
  }

  const contents: DiagnosticSupportBundleContent[] = [...supportBundleContents(files)];
  manifest.contents = contents;

  writeSupportBundleDirectory({
    outputDir: params.outputDir,
    files: [jsonSupportBundleFile("manifest.json", manifest), ...files],
  });

  return {
    manifest,
    outputDir: params.outputDir,
    events,
    header,
    runtimeFile: runtimeFile && isRegularNonSymlinkFile(runtimeFile) ? runtimeFile : undefined,
    supplementalFiles,
  };
}

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge